<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Kevin Cox</title>
    <description>The latest articles on Forem by Kevin Cox (@kevincox).</description>
    <link>https://forem.com/kevincox</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F502541%2Ff4be015b-c612-4b11-9af5-151c459e5358.png</url>
      <title>Forem: Kevin Cox</title>
      <link>https://forem.com/kevincox</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/kevincox"/>
    <language>en</language>
    <item>
      <title>Losslessly Changing Video Framerate</title>
      <dc:creator>Kevin Cox</dc:creator>
      <pubDate>Wed, 06 Aug 2025 00:05:00 +0000</pubDate>
      <link>https://forem.com/kevincox/losslessly-changing-video-framerate-3n7e</link>
      <guid>https://forem.com/kevincox/losslessly-changing-video-framerate-3n7e</guid>
      <description>&lt;p&gt;I’ve been taking a few timelapse videos recently and as an ammeter I’ve just been using the default phone camera apps. Google Camera defaults to “Auto” for the speedup, which keeps the resulting video between 15 and 30s no matter how long you record for (with minimum of 5x speed). It also offers explicit 5, 10, 30 and 120x speeds. The iPhone forgoes explicit options and only offers an auto mode with 20-40s output target and a 20x minimum speed.&lt;/p&gt;

&lt;p&gt;Both of these apps have a limitation that they can only output 30 fps video. The seems like a missed opportunity since the faster than real time playback already tends to produce jumpy video. Getting 60 fps (or even 120 fps) makes a huge improvement to smoothness. The hardware can easily support these output framerates as the capture framerate will still be lower than regular speed videos.&lt;/p&gt;

&lt;p&gt;My preferred approach is to record the video at “twice the duration” as I actually want, then to speed it up after the fact. Recording at a higher input framerate always gives you more flexibility as you can always speed it up later, and if you carefully pick your input framerate you can do this losslessly to avoid degrading the video quality.&lt;/p&gt;

&lt;p&gt;For example, if I want to end up with a 1 minute 60 fps video covering 1 hour of real time. I need to record at &lt;span&gt;60 s×60 Hz÷1 h60\u{s} \times 60\u{Hz} \div 1\u{h}&lt;/span&gt;which is 1 fps. Google Camera doesn’t show you the framerate directly, but you can easily convert the input speeds to framerates. Since it always expects to output 30 fps the calculation is just &lt;span&gt;30 fps÷speed30\u{fps} \div \v{speed}&lt;/span&gt;, so to capture at 1 fps I need to select 30x speed.&lt;/p&gt;

&lt;p&gt;To losslessly convert the video you can use ffmpeg bitstream filters.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;ffmpeg
    -i input-video.mp4 &lt;span&gt;# Input file.&lt;/span&gt;
    -c copy &lt;span&gt;# Don't transcode, remain lossless.&lt;/span&gt;
    -r 60 &lt;span&gt;# Output framerate, this is mostly informational for the whole-video metadata.&lt;/span&gt;
    -fps_mode vfr &lt;span&gt;# Don't drop frames to match the expected framerate, just preserve all input frames.&lt;/span&gt;
    -bsf:v &lt;span&gt;&lt;span&gt;"&lt;/span&gt;setts=ts=PTS/2&lt;span&gt;"&lt;/span&gt;&lt;/span&gt; &lt;span&gt;# Update the presentation time to 1/2 the previous one (play twice as fast).&lt;/span&gt;
    output.mp4 &lt;span&gt;# Output file.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This can easily be adjusted to other framerates by changing the &lt;code&gt;-r&lt;/code&gt; and &lt;code&gt;-bsf:v&lt;/code&gt; values.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Note&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;The ffmpeg wiki has a page &lt;a href="https://trac.ffmpeg.org/wiki/How%20to%20speed%20up%20/%20slow%20down%20a%20video" rel="noopener noreferrer"&gt;How to speed up / slow down a video&lt;/a&gt;. However this page &lt;a href="https://trac.ffmpeg.org/wiki/How%20to%20speed%20up%20/%20slow%20down%20a%20video#rawbitstreammethod" rel="noopener noreferrer"&gt;only has a lossless method for h264 and h265&lt;/a&gt;. Furthermore this method makes it very difficult to preserve metadata, don’t handle variable frame rate properly and is multiple steps. I have found my method to be much easier and more effective.&lt;/p&gt;

</description>
      <category>android</category>
      <category>tooling</category>
      <category>ios</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>I Don't Like Imports</title>
      <dc:creator>Kevin Cox</dc:creator>
      <pubDate>Sun, 20 Jul 2025 22:15:00 +0000</pubDate>
      <link>https://forem.com/kevincox/i-dont-like-imports-5fg8</link>
      <guid>https://forem.com/kevincox/i-dont-like-imports-5fg8</guid>
      <description>&lt;p&gt;I think this is an unusual opinion, so I thought I would share.&lt;/p&gt;

&lt;p&gt;I prefer not to import external symbols into the local scope. For example, I prefer:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span&gt;let&lt;/span&gt; &lt;span&gt;body&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;reqwest&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;(&lt;span&gt;&lt;span&gt;"&lt;/span&gt;https://kevincox.ca/feed.atom&lt;span&gt;"&lt;/span&gt;&lt;/span&gt;)&lt;span&gt;.await?.&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;()&lt;span&gt;.await?&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;over&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span&gt;use&lt;/span&gt; &lt;span&gt;reqwest&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;get; &lt;span&gt;// At the top of the file.&lt;/span&gt;

&lt;span&gt;// Inside some function.&lt;/span&gt;
&lt;span&gt;let&lt;/span&gt; &lt;span&gt;body&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;get&lt;/span&gt;(&lt;span&gt;&lt;span&gt;"&lt;/span&gt;https://kevincox.ca/feed.atom&lt;span&gt;"&lt;/span&gt;&lt;/span&gt;)&lt;span&gt;.await?.&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;()&lt;span&gt;.await?&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="unambiguous"&gt;&lt;a href="https://kevincox.ca/2025/07/20/no-imports/#unambiguous" rel="noopener noreferrer"&gt;Unambiguous&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;There are various reasons for this, but all the most important ones boil down to being unambiguous. When I am reading code I see &lt;code&gt;&lt;span&gt;library&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Foo&lt;/span&gt;&lt;/code&gt; and know that it comes from &lt;code&gt;&lt;span&gt;library&lt;/span&gt;&lt;/code&gt;. I see &lt;code&gt;&lt;span&gt;crate::&lt;/span&gt;&lt;span&gt;Foo&lt;/span&gt;&lt;/code&gt; and know it comes from elsewhere in my code and if I see just &lt;code&gt;&lt;span&gt;Foo&lt;/span&gt;&lt;/code&gt; I know it is from the current file (and possibly private, so I could be a bit more careful with how I use the API). I don’t need to guess or lookup what &lt;code&gt;&lt;span&gt;Foo&lt;/span&gt;&lt;/code&gt; I am working with. (And when I do inevitably guess I don’t need to have that background process asking “did I guess wrong?”)&lt;/p&gt;

&lt;p&gt;I find when I am working on codebases where the convention is to import things I am very frequently tracking down the definition of some type or function as I don’t know whether it is local or from which library. This is especially painful when reading or reviewing code in a web interface. If you are reading a patch the imports are likely not expanded (if they didn’t change) and even if you have the whole file expanded you need to check if the referenced value is the global import from the top of the file or some local shadow.&lt;/p&gt;

&lt;h2 id="copy-paste"&gt;&lt;a href="https://kevincox.ca/2025/07/20/no-imports/#copy-paste" rel="noopener noreferrer"&gt;Copy + Paste&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;Another thing I love about fully qualified names is that I can just copy and paste code around or move it from file to file without needing to figure out which imports are needed in the new file (and inevitably importing the wrong &lt;code&gt;&lt;span&gt;Error&lt;/span&gt;&lt;/code&gt; at least a few times). For the most part code in other files and examples online can just be pasted into the file and work.&lt;/p&gt;

&lt;p&gt;It’s not some dramatic productivity boon, but it certainly makes cleanups and refactors much smoother and a frustrating tedious step that is necessary when working with import-heavy code.&lt;/p&gt;


&lt;p&gt;This would be a great editor feature. If I am copying from one file to another in the same editor it should be able to also pull in the required imports. Technically it should be fairly straightforward to any editor with enough language services for precise autocomplete. The only real challenge would be coming up with a new name in the case of a identifier conflict.&lt;/p&gt;
&lt;h2 id="long-names"&gt;&lt;a href="https://kevincox.ca/2025/07/20/no-imports/#long-names" rel="noopener noreferrer"&gt;But The Names Are So Long!&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;In some cases they are. But more often than not this is just a poor choice by the library author (in my opinion). For example in Rust the standard hash-map is &lt;code&gt;&lt;span&gt;std&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;collections&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;HashMap&lt;/span&gt;&lt;/code&gt;. But even that is just an alias. If you want to work on an &lt;a href="https://doc.rust-lang.org/stable/std/collections/hash_map/enum.Entry.html" rel="noopener noreferrer"&gt;&lt;code&gt;&lt;span&gt;Entry&lt;/span&gt;&lt;/code&gt;&lt;/a&gt; you are now typing &lt;code&gt;&lt;span&gt;std&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;collections&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;hash_map&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Entry&lt;/span&gt;&lt;/code&gt;. I see no reason it couldn’t have been &lt;code&gt;&lt;span&gt;std&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;HashMap&lt;/span&gt;&lt;/code&gt; and &lt;code&gt;&lt;span&gt;std&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;HashMapEntry&lt;/span&gt;&lt;/code&gt; or &lt;code&gt;&lt;span&gt;std&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;HashMap&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Entry&lt;/span&gt;&lt;/code&gt;. (I don’t mind a small amount of nesting when it is very strongly associated, but still don’t really see much point.)&lt;/p&gt;

&lt;p&gt;I much prefer C++‘s style where almost everything is just under &lt;code&gt;&lt;span&gt;std&lt;/span&gt;&lt;/code&gt;. For example &lt;code&gt;&lt;span&gt;std&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;unordered_map&lt;/span&gt;&lt;/code&gt;. An iterator is slightly nested at &lt;code&gt;&lt;span&gt;std&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;unordered_map&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;iterator&lt;/span&gt;&lt;/code&gt; but if you ask me that is still a pretty reasonable name. And I would much rather see that in code than wonder what &lt;code&gt;&lt;span&gt;iterator&lt;/span&gt;&lt;/code&gt; we are working with.&lt;/p&gt;

&lt;p&gt;If it hurts stop hitting yourself, don’t find a way to numb the pain.&lt;/p&gt;

&lt;h2 id="conflicts"&gt;&lt;a href="https://kevincox.ca/2025/07/20/no-imports/#conflicts" rel="noopener noreferrer"&gt;Conflicts&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;I see this come up as an argument against, and I am pretty dumbfounded. Some people see that you have &lt;code&gt;&lt;span&gt;std&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;io&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Error&lt;/span&gt;&lt;/code&gt; and &lt;code&gt;&lt;span&gt;std&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;fmt&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Error&lt;/span&gt;&lt;/code&gt;. So they can’t both be &lt;code&gt;&lt;span&gt;std&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Error&lt;/span&gt;&lt;/code&gt;. But that is kind of my point! When I see code referencing &lt;code&gt;&lt;span&gt;Error&lt;/span&gt;&lt;/code&gt; I don’t know which of error type from the dozens libraries that the code might be using it is! That is what makes the &lt;code&gt;{&lt;span&gt;library&lt;/span&gt;}&lt;span&gt;::&lt;/span&gt;{&lt;span&gt;name&lt;/span&gt;}&lt;/code&gt; pattern so great, it is conflict free. If I use that throughout my code I never have to worry if &lt;code&gt;&lt;span&gt;Write&lt;/span&gt;&lt;/code&gt; is &lt;code&gt;&lt;span&gt;std&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;fmt&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Write&lt;/span&gt;&lt;/code&gt;, &lt;code&gt;&lt;span&gt;std&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;io&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Write&lt;/span&gt;&lt;/code&gt; or &lt;code&gt;&lt;span&gt;crate::&lt;/span&gt;&lt;span&gt;Write&lt;/span&gt;&lt;/code&gt; because it is spelt out! I would much rather the types be actually named &lt;code&gt;&lt;span&gt;std&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;FmtWrite&lt;/span&gt;&lt;/code&gt; and have everyone “agree” on the name (because they have no choice) than half the time having it called &lt;code&gt;&lt;span&gt;Write&lt;/span&gt;&lt;/code&gt; and half the time having it called &lt;code&gt;&lt;span&gt;FmtWrite&lt;/span&gt;&lt;/code&gt;, &lt;code&gt;&lt;span&gt;WriteFmt&lt;/span&gt;&lt;/code&gt; or &lt;code&gt;&lt;span&gt;DebugWrite&lt;/span&gt;&lt;/code&gt; (because some other &lt;code&gt;&lt;span&gt;Write&lt;/span&gt;&lt;/code&gt; was in scope and the programmer needed to come up with their own name).&lt;/p&gt;

&lt;p&gt;This isn’t an imagined problem. I regularly find myself reading &lt;a href="https://doc.rust-lang.org/rustdoc/what-is-rustdoc.html" rel="noopener noreferrer"&gt;rustdoc&lt;/a&gt; documentation and needing to hover over a type name so that my browser displays the fully qualified path in the link target! I feel that which &lt;code&gt;&lt;span&gt;Error&lt;/span&gt;&lt;/code&gt; or which &lt;code&gt;&lt;span&gt;Write&lt;/span&gt;&lt;/code&gt; is being used is important enough that it be immediately visible, in both code and generated documentation.&lt;/p&gt;

&lt;p&gt;Avoiding imports means that the library authors are responsible for solving conflicts. And we are back to the fact that when I see a type or function it is unambiguous and I don’t have to waste brain cycles figuring out what &lt;code&gt;&lt;span&gt;Error&lt;/span&gt;&lt;/code&gt; I am looking at.&lt;/p&gt;

&lt;h2 id="exceptions"&gt;&lt;a href="https://kevincox.ca/2025/07/20/no-imports/#exceptions" rel="noopener noreferrer"&gt;But Sometimes They Are &lt;strong&gt;Really&lt;/strong&gt; Long&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;I’m not a stickler to this rule. There are some cases where I will use imports. Sometimes the names are just too long and repetitive. In that case I strongly prefer a local &lt;code&gt;&lt;span&gt;use&lt;/span&gt;&lt;/code&gt; with a short scope. This ensures that I don’t need to look far to resolve the ambiguity.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span&gt;use&lt;/span&gt; &lt;span&gt;sentry&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;integrations&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;tracing&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;EventFilter&lt;/span&gt;;
&lt;span&gt;match&lt;/span&gt; &lt;span&gt;*&lt;/span&gt;&lt;span&gt;meta&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;level&lt;/span&gt;() {
    &lt;span&gt;tracing&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Level&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;ERROR&lt;/span&gt; &lt;span&gt;=&amp;gt;&lt;/span&gt; &lt;span&gt;EventFilter&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Event&lt;/span&gt;,
    &lt;span&gt;tracing&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Level&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;WARN&lt;/span&gt; &lt;span&gt;=&amp;gt;&lt;/span&gt; {
        &lt;span&gt;if&lt;/span&gt; &lt;span&gt;module&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;starts_with&lt;/span&gt;(&lt;span&gt;&lt;span&gt;"&lt;/span&gt;hickory_proto::&lt;span&gt;"&lt;/span&gt;&lt;/span&gt;) {
            &lt;span&gt;EventFilter&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Ignore&lt;/span&gt;
        } &lt;span&gt;else&lt;/span&gt; {
            &lt;span&gt;EventFilter&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Event&lt;/span&gt;
        }
    }
    &lt;span&gt;tracing&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Level&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;INFO&lt;/span&gt; &lt;span&gt;=&amp;gt;&lt;/span&gt; &lt;span&gt;EventFilter&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Ignore&lt;/span&gt;,
    &lt;span&gt;tracing&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Level&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;DEBUG&lt;/span&gt; &lt;span&gt;=&amp;gt;&lt;/span&gt; &lt;span&gt;EventFilter&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Ignore&lt;/span&gt;,
    &lt;span&gt;tracing&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Level&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;TRACE&lt;/span&gt; &lt;span&gt;=&amp;gt;&lt;/span&gt; &lt;span&gt;EventFilter&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Ignore&lt;/span&gt;,
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;(And more often than not when I run into this case it isn’t because the name is intrinsically high-entropy, but because someone got carried away with hierarchy. &lt;em&gt;Stop hitting yourself.&lt;/em&gt;)&lt;/p&gt;

&lt;h2 id="rust"&gt;&lt;a href="https://kevincox.ca/2025/07/20/no-imports/#rust" rel="noopener noreferrer"&gt;Rust-Specific Notes&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;I’ve been using Rust as an example but been trying to avoid being Rust-specific. But it is my primary language, so I do have some thoughts to share. If you don’t care about Rust you can stop reading now.&lt;/p&gt;

&lt;p&gt;Avoiding imports in Rust is going against the grain. I would recommend that all crates &lt;a href="https://kevincox.ca/2025/07/20/no-imports/#rust-flat-api" rel="noopener noreferrer"&gt;provide a flat API&lt;/a&gt;. However, for many projects avoiding &lt;code&gt;&lt;span&gt;use&lt;/span&gt;&lt;/code&gt; isn’t the right choice. Just follow the common coding convention and be happy. To see a real-world example of both a flat API and avoiding &lt;code&gt;&lt;span&gt;use&lt;/span&gt;&lt;/code&gt; you can check out &lt;a href="https://gitlab.com/kevincox/rl-core" rel="noopener noreferrer"&gt;rl-core&lt;/a&gt; which is a small project where I am quite glad I chose to break convention. A lot of my “for-me” or “internal” code is written in a similar style.&lt;/p&gt;

&lt;h3 id="rust-flat-api"&gt;&lt;a href="https://kevincox.ca/2025/07/20/no-imports/#rust-flat-api" rel="noopener noreferrer"&gt;Provide a Flat API&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;By default, Rust wants to ship your file structure as your exports (&lt;a href="https://en.wikipedia.org/wiki/Conway%27s_law" rel="noopener noreferrer"&gt;Conway’s law&lt;/a&gt;). So for example &lt;code&gt;&lt;span&gt;mylib&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Foo&lt;/span&gt;&lt;/code&gt; needs to be defined in &lt;code&gt;src/lib.rs&lt;/code&gt; and &lt;code&gt;&lt;span&gt;mylib&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;submodule&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Bar&lt;/span&gt;&lt;/code&gt; needs to be defined in &lt;code&gt;src/submodule.rs&lt;/code&gt; or &lt;code&gt;src/submodule/mod.rs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With a single re-export per module this can be solved!&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span&gt;mod&lt;/span&gt; &lt;span&gt;config&lt;/span&gt;; &lt;span&gt;pub&lt;/span&gt; &lt;span&gt;use&lt;/span&gt; &lt;span&gt;config&lt;/span&gt;&lt;span&gt;::*&lt;/span&gt;;
&lt;span&gt;mod&lt;/span&gt; &lt;span&gt;tracker&lt;/span&gt;; &lt;span&gt;pub&lt;/span&gt; &lt;span&gt;use&lt;/span&gt; &lt;span&gt;tracker&lt;/span&gt;&lt;span&gt;::*&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;One extra statement to free you from hierarchy. Re-exports are well-supported by Rust so rustdoc and the compiler will pick &lt;code&gt;&lt;span&gt;mylib&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Config&lt;/span&gt;&lt;/code&gt; as the path to show users rather than &lt;code&gt;&lt;span&gt;mylib&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Config&lt;/span&gt;&lt;/code&gt;. (I don’t know the exact heuristics but when working like this there will be a single public export, so there isn’t much cleverness needed.)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;span&gt;pub&lt;/span&gt; &lt;span&gt;use&lt;/span&gt;&lt;/code&gt; also only sets the &lt;strong&gt;maximum&lt;/strong&gt; export visibility. This means that &lt;code&gt;&lt;span&gt;pub&lt;/span&gt;&lt;/code&gt;, &lt;code&gt;&lt;span&gt;pub&lt;/span&gt;(&lt;span&gt;crate&lt;/span&gt;)&lt;/code&gt; and private things in the module will have the expected visibility. So the definitions in the files look the same as they normally would and the export lines only need to be added when you add or remove modules.&lt;/p&gt;

&lt;p&gt;The only real downside of this approach is the &lt;code&gt;src/lib.rs&lt;/code&gt; will see everything as local and can reference &lt;code&gt;&lt;span&gt;Config&lt;/span&gt;&lt;/code&gt; rather than &lt;code&gt;&lt;span&gt;crate::&lt;/span&gt;&lt;span&gt;Config&lt;/span&gt;&lt;/code&gt;. But this is a very minor issue and the solution is not to put anything into &lt;code&gt;lib.rs&lt;/code&gt;. (Keeping only module definitions and re-exports in &lt;code&gt;lib.rs&lt;/code&gt; is a very common pattern anyways.)&lt;/p&gt;

&lt;p&gt;The nice thing about just re-exporting everything is that it also frees you from deciding where to put things based on what you want the exported name to be. You don’t need to put &lt;code&gt;&lt;span&gt;std&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;collections&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;hash_map&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;HashMap&lt;/span&gt;&lt;/code&gt; and &lt;code&gt;&lt;span&gt;std&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;collections&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;hash_map&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Entry&lt;/span&gt;&lt;/code&gt; in the same file to have them appear at the same place, you can put them into separate files if you wish or the same file if you need access to non-public APIs. (The Rust &lt;code&gt;&lt;span&gt;std&lt;/span&gt;&lt;/code&gt; implementation does actually re-export these from various places, re-exports are very common and not something to be feared.)&lt;/p&gt;

&lt;p&gt;Personally I just put every single type and most functions into separate files. I find that it keeps everything simple and organized and I prefer switching between different files than different parts of a file when going back and forth. So for example in rl-core I define &lt;code&gt;&lt;span&gt;rl_core&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Config&lt;/span&gt;&lt;/code&gt; in &lt;code&gt;src/config.rs&lt;/code&gt; and &lt;code&gt;&lt;span&gt;rl_core&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Tracker&lt;/span&gt;&lt;/code&gt; in &lt;code&gt;lib/tracker.rs&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id="rust-traits"&gt;&lt;a href="https://kevincox.ca/2025/07/20/no-imports/#rust-traits" rel="noopener noreferrer"&gt;Traits&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;Traits are where things start to get hairy. To call a trait method as a method the trait needs to be in scope. So if you want to call &lt;code&gt;&lt;span&gt;foo&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;a_method&lt;/span&gt;()&lt;/code&gt; where &lt;code&gt;&lt;span&gt;a_method&lt;/span&gt;&lt;/code&gt; is &lt;code&gt;&lt;span&gt;somelib&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;TheType&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;a_method&lt;/span&gt;&lt;/code&gt; you need to &lt;code&gt;&lt;span&gt;use&lt;/span&gt; &lt;span&gt;somelib&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;TheType&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;However you always have the option to call the method using a fully-qualified path. For example &lt;code&gt;&lt;span&gt;somelib&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;TheType&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;a_method&lt;/span&gt;(&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;foo&lt;/span&gt;)&lt;/code&gt; or &lt;code&gt;&amp;lt;&lt;span&gt;Foo&lt;/span&gt; &lt;span&gt;as&lt;/span&gt; &lt;span&gt;somelib&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;TheType&lt;/span&gt;&amp;gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;static_func&lt;/span&gt;()&lt;/code&gt;. For the occasional method call I often do this because I do like seeing where &lt;code&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;a_method&lt;/span&gt;()&lt;/code&gt; comes from, it isn’t always clear if it is a method directly on &lt;code&gt;&lt;span&gt;foo&lt;/span&gt;&lt;/code&gt; or which trait defined it. However nesting a few calls with this quickly becomes messy and sometimes I make a practical choice to just use the &lt;code&gt;&lt;span&gt;use&lt;/span&gt;&lt;/code&gt;. I prefer to do this just for a short scope to help keep the origin information close to the code, but for very common traits I will sometimes add a global import like &lt;code&gt;&lt;span&gt;use&lt;/span&gt; &lt;span&gt;std&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;io&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Write&lt;/span&gt; &lt;span&gt;as&lt;/span&gt; _&lt;/code&gt; (make the trait methods available but don’t actually pull the name into scope).&lt;/p&gt;

&lt;h3 id="rust-std"&gt;&lt;a href="https://kevincox.ca/2025/07/20/no-imports/#rust-std" rel="noopener noreferrer"&gt;&lt;code&gt;&lt;span&gt;std&lt;/span&gt;&lt;/code&gt;&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;The elephant in the room is that in Rust &lt;code&gt;&lt;span&gt;std&lt;/span&gt;&lt;/code&gt; (and &lt;code&gt;&lt;span&gt;core&lt;/span&gt;&lt;/code&gt;) love &lt;em&gt;love&lt;/em&gt; &lt;strong&gt;love&lt;/strong&gt; hierarchy and lots of names are super long. I wish I could have a long talk with whoever decided it was really important that &lt;code&gt;&lt;span&gt;std&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;sync&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;atomic&lt;/span&gt;&lt;/code&gt; really needed to be nested under &lt;code&gt;&lt;span&gt;sync&lt;/span&gt;&lt;/code&gt;. I think we would have been fine to reserve the top level &lt;code&gt;&lt;span&gt;std&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;atomic&lt;/span&gt;&lt;/code&gt; for these operations even though they are synchronization related.&lt;/p&gt;


&lt;p&gt;It is what it is. I’m not going to advocate for The Great std Naming Reform even if I would prefer it. We have a pattern, and we can deal with it. Most of the names aren’t so long that they need to be imported so most of the time I just spell them out. Occasionally I find myself repeating a long name frequently and just import it. I have yet to be stricken by a bolt of lightning from the heavens.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>programminglanguages</category>
      <category>rust</category>
    </item>
    <item>
      <title>Dead Bird Features</title>
      <dc:creator>Kevin Cox</dc:creator>
      <pubDate>Sat, 03 May 2025 12:00:00 +0000</pubDate>
      <link>https://forem.com/kevincox/dead-bird-features-iek</link>
      <guid>https://forem.com/kevincox/dead-bird-features-iek</guid>
      <description>&lt;p&gt;Anyone who has had an outdoor cat will have experienced a thoughtful gift left on your doorstep in the morning, usually a dead bird or mouse. These gifts are made with love, and take effort to acquire and return to your doorstep. However, us humans generally do not appreciate them.&lt;/p&gt;

&lt;p&gt;I often find similar features in software. For example, the wiki that our company uses recently released a new feature: when you copy from a document it would format the copied text as Markdown. For example if I copied part of a code snippet inside a second-level list it would be formatted as:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;
    &lt;span&gt;1&lt;/span&gt;&lt;span&gt;.&lt;/span&gt; &lt;span&gt;

```&lt;/span&gt;&lt;span&gt;bash&lt;/span&gt;
       -funsafe-math-optimizations
       &lt;span&gt;```

&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Whoever implemented this feature clearly worked really hard to ensure that parent context like the two lists above were included in this copying. However ultimately it isn’t what users want. When copying the entire list keeping the formatting could be useful, however when copying just one word the context is typically not desired. In this case the result is that I can’t paste it into my terminal.&lt;/p&gt;

&lt;p&gt;Often these features are technically interesting. It is a fun coding project to take a snippet of the document and turn it into accurate Markdown. It is easy to forget to take a step back and think if the user actually wants this.&lt;/p&gt;


&lt;p&gt;I find that these features often occur when overriding default behaviours. Web apps are a common culprit. Overriding copying is a common example, but also overriding spell checking, form filling, link following or undo. Many times there is a key feature that the developer wanted to provide, and they made impressive effort re-implementing browser behaviour. At best the feature is useful, but the reimplemented browser behaviour falls short of native. But very often the feature that was provided isn’t actually even helpful.&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>softwaredevelopment</category>
      <category>ux</category>
    </item>
    <item>
      <title>CORS is Stupid</title>
      <dc:creator>Kevin Cox</dc:creator>
      <pubDate>Sat, 24 Aug 2024 20:00:00 +0000</pubDate>
      <link>https://forem.com/kevincox/cors-is-stupid-480f</link>
      <guid>https://forem.com/kevincox/cors-is-stupid-480f</guid>
      <description>&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" rel="noopener noreferrer"&gt;CORS&lt;/a&gt;, and the browser’s &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy#cross-origin_network_access" rel="noopener noreferrer"&gt;same-origin policy&lt;/a&gt; are often misunderstood. I’m going to explain what they are and what you need to do to stop worrying about them.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: I’m going to talk about CORS and the same-origin policy as one thing and use the terms mostly interchangeably. This is because they are basically one system, they work together to decide what you can do with what cross-origin resources. Basically if your requests are not same-origin then you need to deal with CORS rules, policies and mechanisms.&lt;/p&gt;
&lt;p&gt;First and foremost CORS is a giant hack to mitigate legacy mistakes. It provides both opt-out protections as an attempt to mitigate CSRF attacks against unaware or unmodified sites and opt-in protections for sites to actively protect themselves. But none of these protections are actually sufficient to solve the intended problem. If your site uses &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies" rel="noopener noreferrer"&gt;cookies&lt;/a&gt; you &lt;strong&gt;must&lt;/strong&gt; take action to be safe. (Ok, not &lt;em&gt;every&lt;/em&gt; site, but you better not rely on it. Carefully audit your site or follow these simple steps. Very reasonable seeming patterns can expose you to CSRF vulnerabilities.)&lt;/p&gt;

&lt;h2 id="the-problem"&gt;&lt;a href="https://kevincox.ca/2024/08/24/cors/#the-problem" rel="noopener noreferrer"&gt;The Problem&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;The key problem is how &lt;strong&gt;implicit credentials&lt;/strong&gt; are handled in the web. In the past browsers made the disastrous decision that these credentials could be included in cross-origin requests. This opened up the following attack vector.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Log in to &lt;code&gt;https://your-bank.example&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Visit &lt;code&gt;https://fun-games.example&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;https://fun-games.example&lt;/code&gt; runs &lt;code&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;span&gt;https://your-bank.example/profile&lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/code&gt; to read sensitive information about you like your address and current balance.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This worked because when you logged into your bank it issued you a cookie to access your account details. While &lt;code&gt;fun-games.example&lt;/code&gt; can’t just steal that cookie, it could make its own requests to your bank’s API and your browser would &lt;em&gt;helpfully&lt;/em&gt; attach the cookie to authenticate &lt;em&gt;you&lt;/em&gt;.&lt;/p&gt;

&lt;h2 id="the-solution"&gt;&lt;a href="https://kevincox.ca/2024/08/24/cors/#the-solution" rel="noopener noreferrer"&gt;The Solution&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;This is where CORS comes in. It describes a policy for how cross-origin requests can be made and used. It is both incredibly flexible and completely insufficient.&lt;/p&gt;

&lt;p&gt;The default policy allows making requests, but you can’t read the results. So &lt;code&gt;fun-games.example&lt;/code&gt; is blocked from reading your address from &lt;code&gt;https://your-bank.example/profile&lt;/code&gt;. It can also use side channels such as latency and if the request succeeded or failed to learn things.&lt;/p&gt;

&lt;p&gt;But despite being incredibly annoying this doesn’t actually solve the problem! While &lt;code&gt;fun-games.example&lt;/code&gt; can’t read the result, the request is still sent. This means that it can execute &lt;code&gt;POST https://your-bank.example/transfer?to=fungames&amp;amp;amount=1000000000&lt;/code&gt; to transfer one billion dollars to their account.&lt;/p&gt;

&lt;p&gt;This must be one of the biggest security compromises ever made in the name of backwards compatibility. The TL;DR is that the automatically provided cross-origin protections are completely broken. Every single site that uses cookies needs to explicitly handle it.&lt;/p&gt;

&lt;p&gt;Yes, &lt;strong&gt;every single site&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id="actually-solving-the-problem"&gt;&lt;a href="https://kevincox.ca/2024/08/24/cors/#actually-solving-the-problem" rel="noopener noreferrer"&gt;Actually Solving the Problem&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;The key defence against these cross-site attacks is ensuring that implicit credentials are not inappropriately used. It is best to start by ignoring all implicit credentials on cross-site requests, then you can add specific exceptions as required.&lt;/p&gt;


&lt;p&gt;&lt;strong&gt;Warning&lt;/strong&gt;: There is no combination of &lt;code&gt;Access-Control-Allow-&lt;em&gt;&lt;/em&gt;&lt;/code&gt; headers that you can set that solves simple requests, they are made before any policy is checked. You &lt;strong&gt;need&lt;/strong&gt; to handle them in another way. Do &lt;strong&gt;not&lt;/strong&gt; try to fix this by setting a CORS policy.&lt;/p&gt;
&lt;p&gt;The best solution is to set up server-wide middleware that ignores implicit credentials on all cross-origin requests. This example strips cookies, if you use HTTP Authentication or TLS client certificates be sure to ignore those too. Thankfully the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-Fetch-Site" rel="noopener noreferrer"&gt;&lt;code&gt;Sec-Fetch-&lt;/code&gt; headers&lt;/a&gt; are now available on all modern browsers. This makes cross-site requests easy to identify.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span&gt;def&lt;/span&gt; &lt;span&gt;no_cross_origin_cookies&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;
    &lt;span&gt;if&lt;/span&gt; &lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;headers&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;"sec-fetch-site"&lt;/span&gt;&lt;span&gt;]&lt;/span&gt; &lt;span&gt;==&lt;/span&gt; &lt;span&gt;"same-origin"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
        &lt;span&gt;# Same origin, OK
&lt;/span&gt; &lt;span&gt;return&lt;/span&gt;

    &lt;span&gt;if&lt;/span&gt; &lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;headers&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;"sec-fetch-mode"&lt;/span&gt;&lt;span&gt;]&lt;/span&gt; &lt;span&gt;==&lt;/span&gt; &lt;span&gt;"navigate"&lt;/span&gt; &lt;span&gt;and&lt;/span&gt; &lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;method&lt;/span&gt; &lt;span&gt;==&lt;/span&gt; &lt;span&gt;"GET"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
        &lt;span&gt;# GET requests shouldn't mutate state so this is safe.
&lt;/span&gt; &lt;span&gt;return&lt;/span&gt;

    &lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;headers&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;delete&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;"cookie"&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This provides a safe baseline. If needed you can add specific exceptions for endpoints that are prepared to handle cross-origin implicitly authenticated requests. I would strongly recommend against wide exceptions.&lt;/p&gt;

&lt;h2 id="defence-in-depth"&gt;&lt;a href="https://kevincox.ca/2024/08/24/cors/#defence-in-depth" rel="noopener noreferrer"&gt;Defence in Depth&lt;/a&gt;&lt;/h2&gt;

&lt;h3 id="explicit-credentials"&gt;&lt;a href="https://kevincox.ca/2024/08/24/cors/#explicit-credentials" rel="noopener noreferrer"&gt;Explicit Credentials&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;One of the best ways to avoid this whole problem is by not using implicit credentials. If all authentication is done via explicit credentials then you don’t need to worry about when the browser may add a cookie that you didn’t expect. Explicit credentials can be obtained by signing up for an API token or via an OAuth flow. But either way the most important thing is that logging into one site won’t allow other sites to use these credentials.&lt;/p&gt;

&lt;p&gt;The best way to do this is to pass an authentication token in the &lt;code&gt;Authorization&lt;/code&gt; header.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span&gt;Authorization&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;Bearer chiik5TieeDoh0af&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Using the &lt;code&gt;Authorization&lt;/code&gt; header is a standardized behaviour and will be handled well by many tools. For example this header is likely redacted from logs by default.&lt;/p&gt;

&lt;p&gt;But most importantly it must be set explicitly by all clients. This not only solves the CSRF problem but makes multi-account support a breeze.&lt;/p&gt;

&lt;p&gt;The major downside is that explicit credentials are unsuitable for server-rendered sites as they aren’t included in top-level navigation. Server-rendering is great for performance so this technique is often unsuitable.&lt;/p&gt;

&lt;h3 id="samesite-cookies"&gt;&lt;a href="https://kevincox.ca/2024/08/24/cors/#samesite-cookies" rel="noopener noreferrer"&gt;&lt;code&gt;SameSite&lt;/code&gt; Cookies&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;Even though our server should be ignoring cookies in cross-origin requests it is good to practice avoid including them in the requests in the first place. You should set the &lt;code&gt;SameSite=Lax&lt;/code&gt; attribute on all of your cookies which will cause the browser to omit them for cross-origin requests.&lt;/p&gt;


&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: When I say “top-level” I am talking about the URL that you see in the address bar. So if you load &lt;code&gt;fun-games.example&lt;/code&gt; in your URL bar and it makes a request to &lt;code&gt;your-bank.example&lt;/code&gt; then &lt;code&gt;fun-games.example&lt;/code&gt; is the &lt;strong&gt;top-level&lt;/strong&gt; site.&lt;/p&gt;
&lt;p&gt;It is important to remember that &lt;strong&gt;cookies are still included in top-level navigation including form posts&lt;/strong&gt;. You can use &lt;code&gt;SameSite=Strict&lt;/code&gt; which avoids this, but will make the user appear logged out for the first page load after following a cross-origin link (as that request will lack cookies).&lt;/p&gt;

&lt;h2 id="cors-policy"&gt;&lt;a href="https://kevincox.ca/2024/08/24/cors/#cors-policy" rel="noopener noreferrer"&gt;CORS Policy&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;A simple copy-pastable policy is this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span&gt;Access-Control-Allow-Origin&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;*&lt;/span&gt;
&lt;span&gt;Access-Control-Allow-Methods&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That’s it, you’re done.&lt;/p&gt;


&lt;p&gt;&lt;strong&gt;Warning&lt;/strong&gt;: This policy is different than mirroring the &lt;code&gt;Origin&lt;/code&gt; header in the &lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt; header. The &lt;code&gt;*&lt;/code&gt; is more than just a wildcard, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin#sect1" rel="noopener noreferrer"&gt;it also disables implicit credentials&lt;/a&gt;. This ensures that other origins can’t make authenticated requests except via an explicit flow such as attaching an &lt;code&gt;Authorization&lt;/code&gt; header.&lt;/p&gt;
&lt;p&gt;The effect of this policy is that other sites can only make anonymous requests. This means that you are just as secure as if these requests were made via a CORS Proxy.&lt;/p&gt;

&lt;h3 id="shouldnt-i-be-more-specific"&gt;&lt;a href="https://kevincox.ca/2024/08/24/cors/#shouldnt-i-be-more-specific" rel="noopener noreferrer"&gt;Shouldn’t I be more Specific?&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;Probably not. There are a few reasons for this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You may create a false sense of security. Just because a different webpage running in a “correctly functioning” browser can’t make these requests doesn’t mean that they can’t be made. For example CORS proxies are very common.&lt;/li&gt;
&lt;li&gt;It prevents read-only access to your site. This can be useful for URL previews, &lt;a href="https://kevincox.ca/2022/05/06/rss-feed-best-practices/#cors" rel="noopener noreferrer"&gt;feed fetching&lt;/a&gt; or other features. This results in more CORS proxies being run which just harms performance and user privacy.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Remember, CORS is not about blocking access, it is about preventing the accidental reuse of implicit credentials.&lt;/p&gt;

&lt;h2 id="rant"&gt;&lt;a href="https://kevincox.ca/2024/08/24/cors/#rant" rel="noopener noreferrer"&gt;Rant&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;Why do I need to know all of this stuff, why isn’t the web safe by default? Why do I have to deal with an ineffective policy that makes everything annoying by default without actually solving anything?&lt;/p&gt;

&lt;p&gt;IDK, it’s quite annoying. I think most of the reason goes back to backwards compatibility. Sites built features on these security holes, so browsers tried to close them as much as they could without breaking existing sites.&lt;/p&gt;

&lt;p&gt;Luckily there may be some sanity on the horizon, with browsers finally willing to break some sites for the good of the user. Major browsers are moving toward top-level domain isolation. They call it different things, Firefox calls it &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Privacy/State_Partitioning" rel="noopener noreferrer"&gt;State Partitioning&lt;/a&gt;, Safari calls it &lt;a href="https://webkit.org/tracking-prevention/#intelligent-tracking-prevention-itp" rel="noopener noreferrer"&gt;Tracking Prevention&lt;/a&gt; and Google likes cross-site tracking cookies, so they have implemented an opt-in &lt;a href="https://developers.google.com/privacy-sandbox/cookies/chips" rel="noopener noreferrer"&gt;CHIPS&lt;/a&gt; system.&lt;/p&gt;

&lt;p&gt;The main problem is that these approaches are being implemented as privacy features, not security features. This means that they can’t be relied on as they use heuristics to &lt;em&gt;sometimes&lt;/em&gt; allow cross-origin implicit credentials. CHIPS is actually better in this respect as it is reliable in supporting browsers, but it only supports cookies.&lt;/p&gt;


&lt;p&gt;So it does seem like browsers are moving away from cookies that span top-level contexts, but it is a uncoodinated bumbling. It also isn’t clear if this will be by blocking third-party cookies (Safari), partitioning (Firefox, CHIPS).&lt;/p&gt;

</description>
      <category>web</category>
      <category>cors</category>
    </item>
    <item>
      <title>Sementic Versioning Doesn't Support Rolling Deprecation</title>
      <dc:creator>Kevin Cox</dc:creator>
      <pubDate>Sat, 17 Aug 2024 17:40:00 +0000</pubDate>
      <link>https://forem.com/kevincox/sementic-versioning-doesnt-support-rolling-deprecation-4h1l</link>
      <guid>https://forem.com/kevincox/sementic-versioning-doesnt-support-rolling-deprecation-4h1l</guid>
      <description>&lt;p&gt;I like &lt;a href="https://semver.org/" rel="noopener noreferrer"&gt;SemVer&lt;/a&gt;. However, there is one important use case that I wish it supported better. This is what I’ll call “rolling deprecation”.&lt;/p&gt;

&lt;p&gt;The idea is simply that instead of removing APIs in a single compatibility-breaking version, you first deprecate an API in one version, then remove it in a later version. This gives time to migrate off of the deprecated API without being cut off from new features. Let’s look at a quick example:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You start a new project and release a public API that contains &lt;code&gt;&lt;span&gt;awesome_function&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/code&gt;. All excited you cut a release.&lt;/li&gt;
&lt;li&gt;You realize that &lt;code&gt;&lt;span&gt;awesome_function&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/code&gt; has a few flaws. Maybe it silently ignores an important error, or it would work much better with an extra argument. You don’t want to break backwards compatibility, so you release the improved version as &lt;code&gt;&lt;span&gt;awesome_function_2&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/code&gt; but leave &lt;code&gt;&lt;span&gt;awesome_function&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/code&gt; around. (Maybe it is reimplemented using &lt;code&gt;&lt;span&gt;awesome_function_2&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/code&gt;, but your users don’t need to care.) You mark &lt;code&gt;&lt;span&gt;awesome_function&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/code&gt; as deprecated and release.&lt;/li&gt;
&lt;li&gt;We add some unrelated feature and make a new release.&lt;/li&gt;
&lt;li&gt;A some point in the future you decide that keeping &lt;code&gt;&lt;span&gt;awesome_function&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/code&gt; around isn’t a good idea anymore. Maybe it adds an internal requirement that is expensive to maintain, or you just want that wart gone. You’ve given people enough time to move off. You delete it and cut a new release.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So what does SemVer say about this?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;v1.0.0&lt;/code&gt; - First release.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;v1.1.0&lt;/code&gt; - Added functionality, backwards compatible.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;v1.2.0&lt;/code&gt; - Extra feature, backwards compatible.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;v2.0.0&lt;/code&gt; - Breaking change.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Pretty straightforward. Users have between release 2 and release 4 to move off of the deprecated functionality. But it isn’t optimal.&lt;/p&gt;

&lt;p&gt;What if a user created their project with release 2 (&lt;code&gt;v1.1.0&lt;/code&gt; according to SemVer) and didn’t use the deprecated functionality? What they upgraded from &lt;code&gt;v1.0.0&lt;/code&gt; and moved away from all deprecated APIs? In both of these cases their code is compatible with the latest release, but SemVer can’t express this.&lt;/p&gt;

&lt;p&gt;Wouldn’t it be great if you could easily maintain compatibility with multiple “major” versions? This means that an ecosystem could gradually migration from one major version to the next without any incompatibilities during the transition period.&lt;/p&gt;

&lt;p&gt;This is a real problem with SemVer. Every major release is a “flag day”. Sure, maybe your ecosystem of choice will let your dependency tree use both &lt;code&gt;v1&lt;/code&gt; and &lt;code&gt;v2&lt;/code&gt; at the same time (for example &lt;a href="https://doc.rust-lang.org/cargo/" rel="noopener noreferrer"&gt;Cargo&lt;/a&gt; does this) but that doesn’t mean that the experience is seamless. It is common to have one dependency using foo &lt;code&gt;v1&lt;/code&gt; and one dependency using foo &lt;code&gt;v2&lt;/code&gt;. Interacting with both of these dependencies at the same time can be difficult because your top-level code can only use one version of foo (without getting creative). It gets especially painful if you need to move data of type &lt;code&gt;&lt;span&gt;foo&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Data&lt;/span&gt;&lt;/code&gt; between the two (not possible unless you can convert it).&lt;/p&gt;

&lt;p&gt;Some ecosystems support expressing compatibility as a range. For example &lt;a href="https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#comparison-requirements" rel="noopener noreferrer"&gt;Cargo lets you specify &lt;code&gt;&amp;gt;=1.4.0, &amp;lt;3.0.0&lt;/code&gt;&lt;/a&gt;, but this moves the complexity back to the users of the API. One of the great things about SemVer is that it moves most of the complexity onto the package author. The author has a responsibility to indicate their compatibility via the version number and clients (mostly package managers) can calculate if they are compatible. If Cargo sees &lt;code&gt;foo = "1.2.3"&lt;/code&gt; it assumes foo &lt;code&gt;v1.6.2&lt;/code&gt; will be compatible. Specifying dependency ranges pushes that back onto the user. Every user will need to specify the range which requires understanding the versioning scheme that every dependency uses. Most importantly if any library you depend on gets it wrong you will have a version conflict, one mistake ruins the whole system.&lt;/p&gt;

&lt;h2 id="specification"&gt;&lt;a href="https://kevincox.ca/2024/08/17/semver-rolling-deprecation/#specification" rel="noopener noreferrer"&gt;Specification&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;I have an example versioning scheme here to illustrate how the problem could be solved. It isn’t intended as a real proposal. I honestly think SemVer is pretty good and close enough for most things. But it is interesting to see what the simplest form of this versioning scheme would look like.&lt;/p&gt;

&lt;p&gt;Let’s stick with dotted numbers for familiarity. We also won’t worry about patch versions for now. We’ll just have two components, the &lt;em&gt;base&lt;/em&gt; version and the &lt;em&gt;best&lt;/em&gt; version.&lt;/p&gt;

&lt;p&gt;You bump the &lt;em&gt;best&lt;/em&gt; version every time you make a release. Any time you make a backwards incompatible API change you set the &lt;em&gt;base&lt;/em&gt; version to the &lt;em&gt;best&lt;/em&gt; version of the first release in which that API was marked as deprecated.&lt;/p&gt;

&lt;p&gt;How this looks with our example:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;v1.1&lt;/code&gt; - First release.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;v1.2&lt;/code&gt; - Fully compatible, marks &lt;code&gt;&lt;span&gt;awesome_function&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/code&gt; as deprecated.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;v1.3&lt;/code&gt; - Fully compatible.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;v2.4&lt;/code&gt; - Breaks API that was marked deprecated in &lt;code&gt;v1.2&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One interesting is that both components of the version only go up. There is no “reset” for the &lt;em&gt;best&lt;/em&gt; version when the &lt;em&gt;base&lt;/em&gt; is incremented. (Although there probably would be if we added patch versions.) If we wanted to release a completely incompatible version it would be &lt;code&gt;v5.5&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let’s clarify how to check compatibility. Software that doesn’t use any deprecated APIs on &lt;code&gt;vA.B&lt;/code&gt; is compatible with version &lt;code&gt;vX.Y&lt;/code&gt; if &lt;span&gt;X≤B≤YX \le B \le Y&lt;/span&gt;. (&lt;code&gt;A&lt;/code&gt; is irrelevant)&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;v1.1&lt;/code&gt; would be compatible with &lt;code&gt;v1.3&lt;/code&gt; because &lt;span&gt;1≤1≤31 \le 1 \le 3&lt;/span&gt;but not &lt;code&gt;v2.4&lt;/code&gt; because &lt;span&gt;2≰1≤42 \nleq 1 \le 4&lt;/span&gt;. &lt;code&gt;v1.2&lt;/code&gt; is compatible with &lt;code&gt;v1.3&lt;/code&gt; because &lt;span&gt;1≤2≤31 \le 2 \le 3&lt;/span&gt;and &lt;code&gt;v2.4&lt;/code&gt; because &lt;span&gt;2≤2≤42 \le 2 \le 4&lt;/span&gt;.&lt;/p&gt;

&lt;h3 id="semver-compatibility"&gt;&lt;a href="https://kevincox.ca/2024/08/17/semver-rolling-deprecation/#semver-compatibility" rel="noopener noreferrer"&gt;SemVer Compatibility&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;Interestingly this scheme is backwards compatible with SemVer! This means that you could start publishing using this scheme and software that expects to receive SemVer would do something reasonable. Most notably it would never consider two versions compatible that aren’t. (However it will tell you that some versions are not compatible when they are.) This is obviously true because the “major” version is bumped on any change that could break anyone and the “minor” version still requires ordering. The main difference is that a “major” version bump doesn’t mean breaking everyone, it specifies which versions were broken. The only downside for using this scheme for SemVer clients is that this scheme would encourage more frequent “major” releases, which would be annoying for SemVer users.&lt;/p&gt;

&lt;h3 id="patterns"&gt;&lt;a href="https://kevincox.ca/2024/08/17/semver-rolling-deprecation/#patterns" rel="noopener noreferrer"&gt;Patterns&lt;/a&gt;&lt;/h3&gt;

&lt;h4 id="last-n-compatibility"&gt;&lt;a href="https://kevincox.ca/2024/08/17/semver-rolling-deprecation/#last-n-compatibility" rel="noopener noreferrer"&gt;Last N Compatibility&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;Some projects have “deprecation compatibility” for a fixed number of releases. In this scheme it would just look like the &lt;em&gt;base&lt;/em&gt; version trailing the &lt;em&gt;best&lt;/em&gt; version by that fixed amount.&lt;/p&gt;

&lt;p&gt;For example for “last 3 versions” compatibility.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;v1.1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;v1.2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;v1.3&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;v2.4&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;v3.5&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;v4.6&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;…and so on.&lt;/p&gt;

&lt;h4 id="no-compatibility"&gt;&lt;a href="https://kevincox.ca/2024/08/17/semver-rolling-deprecation/#no-compatibility" rel="noopener noreferrer"&gt;No Compatibility&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;For projects wishing to express no backwards compatibility the &lt;em&gt;base&lt;/em&gt; and &lt;em&gt;best&lt;/em&gt; versions would always be the same.&lt;/p&gt;



&lt;ol&gt;


&lt;li&gt;&lt;code&gt;v1.1&lt;/code&gt;&lt;/li&gt;


&lt;li&gt;&lt;code&gt;v2.2&lt;/code&gt;&lt;/li&gt;


&lt;li&gt;&lt;code&gt;v3.3&lt;/code&gt;&lt;/li&gt;


&lt;/ol&gt;

</description>
      <category>versioning</category>
      <category>programming</category>
    </item>
    <item>
      <title>Private Internet</title>
      <dc:creator>Kevin Cox</dc:creator>
      <pubDate>Fri, 16 Aug 2024 23:55:00 +0000</pubDate>
      <link>https://forem.com/kevincox/private-internet-5239</link>
      <guid>https://forem.com/kevincox/private-internet-5239</guid>
      <description>&lt;p&gt;Many widespread internet protocols were written at a time when internet security wasn’t much of a consideration. From things like lack of &lt;code&gt;From&lt;/code&gt; address verification in email to NTP reflection there are lots of protocols that are now considered badly designed. When they were authored there was a lot of implicit trust (For example because there were just a few universities connected to your network and you could just kick off any bad actors.) but now the internet is full of bad actors and these designs are harming us.&lt;/p&gt;

&lt;p&gt;I think &lt;a href="https://datatracker.ietf.org/doc/html/rfc791" rel="noopener noreferrer"&gt;IP&lt;/a&gt; is generally considered well-designed. IPv4 refuses to die and IPv6 is working great. But I think that these protocols have some fundamental flaws. Or at the very least they need more layers on top to work well on today’s internet.&lt;/p&gt;

&lt;p&gt;The most obvious sign of this is the many non-technical people know what an IP address is. I don’t think the average user should need to care how their networking works, but due to issues with the protocol many people are aware that their IP is sensitive information and that they need to protect it.&lt;/p&gt;

&lt;p&gt;Two of the biggest flaws of IP are privacy concerns and DoS susceptibility. These were likely not even considered when designing the protocol. But much like &lt;code&gt;From&lt;/code&gt; address forgery was not a concern when designing SMTP, I think these privacy issues should be considered a deficiency that needs addressing.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Metadata absolutely tells you everything about somebody’s life. If you have enough metadata you don’t really need content.&lt;/p&gt;
&lt;p&gt;Stewart Baker, NSA General Counsel&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id="valuable-properties"&gt;&lt;a href="https://kevincox.ca/2024/08/16/private-internet/#valuable-properties" rel="noopener noreferrer"&gt;Valuable Properties&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;Here I tried to brainstorm a few properties that I would like to see in a private and DoS resistant network protocol.&lt;/p&gt;

&lt;h3 id="non-sensitive-client-addresses"&gt;&lt;a href="https://kevincox.ca/2024/08/16/private-internet/#non-sensitive-client-addresses" rel="noopener noreferrer"&gt;Non-sensitive Client Addresses&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;If I make two connections, the remote end should not be able to tell if I am the same person. This property should hold whether I connect to the same host twice, or two hosts that coordinate.&lt;/p&gt;

&lt;h3 id="dos-resistant"&gt;&lt;a href="https://kevincox.ca/2024/08/16/private-internet/#dos-resistant" rel="noopener noreferrer"&gt;DoS Resistant&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;All methods of connecting to me must be revocable. That is at some point they stop functioning and you need new information to connect. Ideally this could be done at any time, immediately invalidating an address, but could be time based (address expire after a period of time).&lt;/p&gt;

&lt;p&gt;This revocation must not just cause the endpoints to ignore traffic. The traffic should be dropped as close to the source as possible. (For example the first “well behaved” network. On the internet this would at least be a tier-1 ISP, if not the attacker’s own ISP)&lt;/p&gt;

&lt;h3 id="no-leak-of-outgoing-connection-server-address"&gt;&lt;a href="https://kevincox.ca/2024/08/16/private-internet/#no-leak-of-outgoing-connection-server-address" rel="noopener noreferrer"&gt;No Leak of Outgoing Connection Server Address&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;If I open a connection, a local observer must not be able to identify which server I am connecting to. This property should continue for as many hops as possible. (At some point the traffic will have to reach the server and someone who can observe every hop on the path will be able to correlate it.)&lt;/p&gt;

&lt;h3 id="independent-services"&gt;&lt;a href="https://kevincox.ca/2024/08/16/private-internet/#independent-services" rel="noopener noreferrer"&gt;Independent Services&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;A server should be able to host any number of public services and no client should be able to tell if these services are co-located.&lt;/p&gt;

&lt;h3 id="no-leak-of-incoming-connection-server-address"&gt;&lt;a href="https://kevincox.ca/2024/08/16/private-internet/#no-leak-of-incoming-connection-server-address" rel="noopener noreferrer"&gt;No Leak of Incoming Connection Server Address&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;If I receive a connection a local observer must not be able to identify which address was used to create it. (If the address is publicly known they can likely probe this by making connections and comparing traffic, but I’ll consider that out of scope of this feature.)&lt;/p&gt;

&lt;h2 id="stretch-goals"&gt;&lt;a href="https://kevincox.ca/2024/08/16/private-internet/#stretch-goals" rel="noopener noreferrer"&gt;Stretch Goals&lt;/a&gt;&lt;/h2&gt;

&lt;h3 id="encryption"&gt;&lt;a href="https://kevincox.ca/2024/08/16/private-internet/#encryption" rel="noopener noreferrer"&gt;Encryption&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;I didn’t require encryption as while it would be nice (my default stance is to encrypt everything, more encryption never hurts) it may make the protocol significantly more expensive. Furthermore, fundamental protocols like IP tend to evolve very slowly, so when the encryption included in the protocol is no longer state-of-the-art it will be very hard to update. For these reasons encryption is likely best managed by a higher layer.&lt;/p&gt;

&lt;p&gt;That being said it would still be a great stretch goal. Especially if the traffic was differently encrypted on each hop. This would prevent attacks where traffic is sniffed at multiple locations on the network to try to identify different ends of a connection. This is a feature that could be hard to implement just on the endpoints.&lt;/p&gt;

&lt;h2 id="possible-implementations"&gt;&lt;a href="https://kevincox.ca/2024/08/16/private-internet/#possible-implementations" rel="noopener noreferrer"&gt;Possible Implementations&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;I haven’t actually designed a protocol. These requirements are more of a discussion point. I’m sure other properties will be desirable as well. But I did think of ways to accomplish some of them.&lt;/p&gt;

&lt;h3 id="onion-routing"&gt;&lt;a href="https://kevincox.ca/2024/08/16/private-internet/#onion-routing" rel="noopener noreferrer"&gt;Onion Routing&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;My first thought would be some form of &lt;a href="https://en.wikipedia.org/wiki/Onion_routing" rel="noopener noreferrer"&gt;onion routing&lt;/a&gt;. The client builds a route and encrypts the packet for each layer in turn. Each router then decrypts a layer and forwards it. This has worked well for &lt;a href="https://www.torproject.org/" rel="noopener noreferrer"&gt;Tor&lt;/a&gt; but seems to have some fundamental downsides.&lt;/p&gt;

&lt;p&gt;Some concerns with this approach:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The client needs to know about various routers along the path. On the public internet this is a lot of data that is frequently changing.&lt;/li&gt;
&lt;li&gt;Every layer needs metadata on where to forward the packet next, eating into the &lt;a href="https://en.wikipedia.org/wiki/Maximum_transmission_unit" rel="noopener noreferrer"&gt;MTU&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Encrypting data many times is expensive.&lt;/li&gt;
&lt;li&gt;How is the connection initially routed while keeping the server private?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first 3 could be mitigated by reducing the number of hops.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Individual ISPs could act as one large hop, rather than multiple routers. This could reduce the number of hops for each connection and total number of logical routers that clients need to know about.&lt;/li&gt;
&lt;li&gt;Instead of having a layer for every hop the packet can be wrapped only until large networks. For example major ISPs. At this point they can absorb any attacks and your traffic is well mixed with other sources.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I don’t know how to solve 4. &lt;a href="https://community.torproject.org/onion-services/overview/" rel="noopener noreferrer"&gt;Tor Onion Services&lt;/a&gt; solve this by having the server maintain a few fixed routes leading to it. The client then adds one of these (encrypted) routes to the end of its route. However, this results in a non-optimal path which may not be a good tradeoff for general internet use. Maybe the client can evaluate them all use use whichever one is fastest?&lt;/p&gt;

&lt;h3 id="fine-grained-routing"&gt;&lt;a href="https://kevincox.ca/2024/08/16/private-internet/#fine-grained-routing" rel="noopener noreferrer"&gt;Fine-grained Routing&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;Another option may be implementing something like BGP but on a mine narrower scale. Servers generate random values as addresses and announce them. These are then broadcast across routers like BGP routes are today. When the client connects it generates a random “return” address and sends its traffic. Routers along the route also remember the return address to establish a connection.&lt;/p&gt;

&lt;p&gt;There are also concerns with this approach.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How can we avoid leaking the server address?&lt;/li&gt;
&lt;li&gt;Internet routing tables are already huge. To help combat this there are minimum network sizes that are publicly routable. Having many unique routes to each host would explode routing table size.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We can avoid leaking the client address by having each router along the path transform it. (Example: generate a new random address and remember the mapping) However, this trick doesn’t work for the server address because every client needs to be able to connect to the same server address.&lt;/p&gt;


&lt;p&gt;I have no idea how to solve 2. If addresses are random it is very hard to be able to route to any random address from anywhere else.&lt;/p&gt;

</description>
      <category>privacy</category>
    </item>
    <item>
      <title>Time Sharing in Instant Messaging</title>
      <dc:creator>Kevin Cox</dc:creator>
      <pubDate>Fri, 05 Jul 2024 19:55:00 +0000</pubDate>
      <link>https://forem.com/kevincox/time-sharing-in-instant-messaging-349</link>
      <guid>https://forem.com/kevincox/time-sharing-in-instant-messaging-349</guid>
      <description>&lt;p&gt;A seemingly-obvious feature that I haven’t seen in any instant messenger yet. I want to be able to share a timestamp in a way that can be computer-understood and presented to the user with a good UX.&lt;/p&gt;

&lt;p&gt;For example, I want to say:&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;Let’s have a call about this at &lt;strong&gt;11:30&lt;/strong&gt;.&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;But that “11:30” is actually a rich object that encodes an exact timestamp. If your IM application uses HTML formatting for messages it may look something like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span&gt;&amp;lt;time&lt;/span&gt; &lt;span&gt;datetime=&lt;/span&gt;&lt;span&gt;2024-07-05T11:30:00-04:00&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;2024-07-05 at 11:30&lt;span&gt;&amp;lt;/time&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The time is machine-readable (with some fallback text). Clients would show the users the time converted to their local time zone! Or maybe a relative time like “in 32 minutes” if they prefer. Maybe they can click it and see that time in their calendar, or see what time that is in different time zones. The possibilities are endless, but the most important thing is that the time is correct, because computers are much better at time math than humans are.&lt;/p&gt;


&lt;p&gt;Right now I frequently see people just mention a local time and you need to have their time zone memorized (and know if and where they are travelling). If you are lucky they say something like “11:30 IST”, then you just need to guess if they mean &lt;a href="https://en.wikipedia.org/wiki/Indian_Standard_Time" rel="noopener noreferrer"&gt;Indian Standard Time&lt;/a&gt; or &lt;a href="https://en.wikipedia.org/wiki/Time_in_the_Republic_of_Ireland" rel="noopener noreferrer"&gt;Irish Standard Time&lt;/a&gt;. Having a rich “timestamp” object in instant messages would be easier for both the sender and receiver.&lt;/p&gt;

</description>
      <category>ux</category>
      <category>instantmessaging</category>
    </item>
    <item>
      <title>Announcing git bisect-find</title>
      <dc:creator>Kevin Cox</dc:creator>
      <pubDate>Sun, 19 May 2024 17:50:00 +0000</pubDate>
      <link>https://forem.com/kevincox/announcing-git-bisect-find-4fcf</link>
      <guid>https://forem.com/kevincox/announcing-git-bisect-find-4fcf</guid>
      <description>&lt;p&gt;This is a small utility that I wrote to compliment &lt;a href="https://git-scm.com/docs/git-bisect"&gt;&lt;code&gt;git bisect&lt;/code&gt;&lt;/a&gt;. &lt;code&gt;git bisect&lt;/code&gt; is a fantastic tool. In the most basic usage you give it one “good” commit (a commit that doesn’t yet include some property) and at least one “bad” commit (one that does have it). &lt;code&gt;git bisect&lt;/code&gt; will then guide you through the search, picking commits to test that cut the search space in half. This allows you to efficiently identify the first commit with a particular feature.&lt;/p&gt;

&lt;p&gt;However one of the premises of &lt;code&gt;git bisect&lt;/code&gt; is that you know a good commit. Often times you notice a bug but don’t yet know a good version. Was that broken in the last release? Or is it new? Maybe it was broken unnoticed in the past couple of releases? Sure, you could start checking out various revisions to see if the bug is there, but why do this manually?&lt;/p&gt;

&lt;p&gt;This is where &lt;a href="https://gitlab.com/kevincox/git-bisect-find"&gt;&lt;code&gt;git bitsect-find&lt;/code&gt;&lt;/a&gt; comes in. It will take just a bad commit, and step back looking for the first good commit. Basic usage looks like this:&lt;/p&gt;

&lt;h2 id="basic-usage"&gt;&lt;a href="https://kevincox.ca/2024/04/19/git-bisect-find/#basic-usage"&gt;Basic Usage&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;First check out your bad commit:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span&gt;$ &lt;/span&gt;git checkout main
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then start bisecting with &lt;code&gt;git bisect-find bad&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You will be asked to start a regular &lt;code&gt;git bisect&lt;/code&gt; run, type y.&lt;/p&gt;

&lt;p&gt;Then a new candidate commit will be checked out. If the candidate commit is bad run &lt;code&gt;git bisect-find bad&lt;/code&gt; and a new candidate will be checked out. Repeat the test and mark process until a good commit is found.&lt;/p&gt;

&lt;p&gt;Once you have a good commit found you are done with &lt;code&gt;git bisect-find&lt;/code&gt;. Just run &lt;code&gt;git bisect good&lt;/code&gt; and continue with the regular bisection process.&lt;/p&gt;

&lt;h2 id="more"&gt;&lt;a href="https://kevincox.ca/2024/04/19/git-bisect-find/#more"&gt;More&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;There are more complete docs &lt;a href="https://gitlab.com/kevincox/git-bisect-find/-/blob/master/README.md"&gt;in the README&lt;/a&gt;. There are also more features to add (like automatic bisection). However the basics have already been quite useful to me so I figured I would share.&lt;/p&gt;

&lt;h2 id="mechanism"&gt;&lt;a href="https://kevincox.ca/2024/04/19/git-bisect-find/#mechanism"&gt;Mechanism&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;There really isn’t anything too special about the way it works. It just jumps back twice as far each time (following first parents). All state is logged via regular &lt;code&gt;git bisect&lt;/code&gt; so once you find a good commit all of your &lt;code&gt;git bisect-find bad&lt;/code&gt; (and &lt;code&gt;git bisect-find skip&lt;/code&gt;) commands are remembered as regular &lt;code&gt;git bisect bad&lt;/code&gt; and &lt;code&gt;git bisect skip&lt;/code&gt; would be.&lt;/p&gt;


&lt;p&gt;I do wonder if jumping back by twice as much is optimal. Obviously jumping right to the first commit would reduce the number of &lt;code&gt;git bisect-find&lt;/code&gt; steps. But would result in more &lt;code&gt;git bisect&lt;/code&gt;. But &lt;code&gt;git bisect&lt;/code&gt; is quite efficient. (Not to mention that the first commit is likely not particularly interesting.) At some point I should probably work out which growth pattern results in the shortest number of total steps. However I think the optimal number also depends on the distribution of the age of the target commit. But either way doubling works well enough for this tool to be very helpful for me.&lt;/p&gt;

</description>
      <category>git</category>
      <category>tools</category>
    </item>
    <item>
      <title>My Favourite Feeds</title>
      <dc:creator>Kevin Cox</dc:creator>
      <pubDate>Thu, 29 Feb 2024 23:10:00 +0000</pubDate>
      <link>https://forem.com/kevincox/my-favourite-feeds-301i</link>
      <guid>https://forem.com/kevincox/my-favourite-feeds-301i</guid>
      <description>&lt;p&gt;I am an avid user of RSS feeds. Checking my list reveals that I am subscribed to 530 feeds! In the spirit of sharing I thought it would be interesting to share a snapshot of my current reading list. This may prove an interesting resource for someone looking to get started with feeds or just to grow their existing subscription list.&lt;/p&gt;

&lt;p&gt;I’ve tried to focus on feeds that are generally interesting and have high value per-post. So these should get you some interesting content without exploding your reading queue. I’ve also preferred feeds that contain the full article content so that you don’t need to leave your reader (although articles that require interactivity or are worth the click are still included). I have cut out feeds about specific software that I use or games I play as I don’t think most of these changelogs are very interesting unless you use that software. I’ve also cut out most of the higher-volume feeds and feeds that were specific to me (like a feed of outdated packages I maintain).&lt;/p&gt;

&lt;p&gt;I’ve managed to cut down the list to 50 items. Still a good chunk, but I encourage you to read a post or two from each before subscribing and take the ones that interest you. Or just subscribe to them all then unsubscribe if you don’t find the posts interesting. That is one of the great things about RSS, subscribing costs nothing and unsubscribing is reliable. No one will save your email to spam you later.&lt;/p&gt;

&lt;p&gt;If you just want to grab them all you can download &lt;a href="https://kevincox.ca/a/favourite-feeds-2024-zCyc-_.opml"&gt;this OPML file&lt;/a&gt; which can be imported into your feed reader. Or you can just follow along and subscribe to your favourites.&lt;/p&gt;

&lt;p&gt;Below I’ve provided links to each site, as well as a direct link to the feed. I’ve also added a small summary which is typically copied from the site or feed itself, but may be added or edited by me if it didn’t seem very helpful. Items are just in alphabetical order.&lt;/p&gt;

&lt;h2 id="news--blogs"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#news--blogs"&gt;News &amp;amp; Blogs&lt;/a&gt;&lt;/h2&gt;

&lt;h3 id="a-few-thoughts-on-cryptographic-engineering"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#a-few-thoughts-on-cryptographic-engineering"&gt;A Few Thoughts on Cryptographic Engineering&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://blog.cryptographyengineering.com"&gt;Homepage&lt;/a&gt; &lt;a href="https://blog.cryptographyengineering.com/feed/"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;Some random thoughts about crypto. Notes from a course I teach. Pictures of my dachshunds.&lt;/p&gt;&lt;/blockquote&gt;

&lt;h3 id="almost-secure"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#almost-secure"&gt;Almost Secure&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://palant.info"&gt;Homepage&lt;/a&gt; &lt;a href="https://palant.info/pmo.xml"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Focus on security.&lt;/p&gt;

&lt;h3 id="andrew-ayer"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#andrew-ayer"&gt;Andrew Ayer&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.agwa.name"&gt;Homepage&lt;/a&gt; &lt;a href="https://www.agwa.name/blog/feed"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Blog typically focused on TLS and the Web PKI.&lt;/p&gt;

&lt;h3 id="aphyr-posts"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#aphyr-posts"&gt;Aphyr: Posts&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://aphyr.com"&gt;Homepage&lt;/a&gt; &lt;a href="https://aphyr.com/posts.atom"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Blog focused on distributed systems especially consistency as well as software and Clojure in general.&lt;/p&gt;

&lt;h3 id="bartosz-ciechanowski"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#bartosz-ciechanowski"&gt;Bartosz Ciechanowski&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://ciechanow.ski"&gt;Homepage&lt;/a&gt; &lt;a href="https://ciechanow.ski/atom.xml"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;Interactive articles&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;Great interactive introductions to mechanisms and physics.&lt;/p&gt;

&lt;h3 id="bartosz-sypytkowski"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#bartosz-sypytkowski"&gt;Bartosz Sypytkowski&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.bartoszsypytkowski.com"&gt;Homepage&lt;/a&gt; &lt;a href="https://www.bartoszsypytkowski.com/rss/"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;Software dev blog&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;Especially CRDTs.&lt;/p&gt;

&lt;h3 id="bits-about-money"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#bits-about-money"&gt;Bits about Money&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.bitsaboutmoney.com"&gt;Homepage&lt;/a&gt; &lt;a href="https://www.bitsaboutmoney.com/archive/rss/"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;About the modern financial infrastructure that the world sits atop of.&lt;/p&gt;&lt;/blockquote&gt;

&lt;h3 id="filippo-valsorda"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#filippo-valsorda"&gt;Filippo Valsorda&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://words.filippo.io"&gt;Homepage&lt;/a&gt; &lt;a href="https://words.filippo.io/rss/"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Posts on cryptography.&lt;/p&gt;

&lt;h3 id="gnome-shell--mutter"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#gnome-shell--mutter"&gt;GNOME Shell &amp;amp; Mutter&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://blogs.gnome.org/shell-dev/"&gt;Homepage&lt;/a&gt; &lt;a href="https://blogs.gnome.org/shell-dev/feed/"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;Development blog for GNOME Shell and Mutter&lt;/p&gt;&lt;/blockquote&gt;

&lt;h3 id="gtk-development-blog"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#gtk-development-blog"&gt;GTK Development Blog&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://blog.gtk.org"&gt;Homepage&lt;/a&gt; &lt;a href="https://blog.gtk.org/feed/"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;All things GTK&lt;/p&gt;&lt;/blockquote&gt;

&lt;h3 id="imperialviolet"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#imperialviolet"&gt;ImperialViolet&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.imperialviolet.org"&gt;Homepage&lt;/a&gt; &lt;a href="https://www.imperialviolet.org/iv-rss.xml"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;Adam Langley’s Weblog&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;Focus on cryptography, especially passkeys.&lt;/p&gt;

&lt;h3 id="jazs-blog"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#jazs-blog"&gt;Jaz’s Blog&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://jazco.dev"&gt;Homepage&lt;/a&gt; &lt;a href="https://jazco.dev/atom.xml"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;A space where I rant about computers&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;Focus on software performance.&lt;/p&gt;

&lt;h3 id="kevin-coxs-blog"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#kevin-coxs-blog"&gt;Kevin Cox’s Blog&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://kevincox.ca/"&gt;Homepage&lt;/a&gt; &lt;a href="https://kevincox.ca/feed.atom"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You’re reading it.&lt;/p&gt;

&lt;h3 id="kobzols-blog"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#kobzols-blog"&gt;Kobzol’s blog&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://kobzol.github.io"&gt;Homepage&lt;/a&gt; &lt;a href="https://kobzol.github.io/feed.xml"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;Blog about programming stuff.&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;Focus on Rust.&lt;/p&gt;

&lt;h3 id="mozilla-hacks"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#mozilla-hacks"&gt;Mozilla Hacks&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://hacks.mozilla.org"&gt;Homepage&lt;/a&gt; &lt;a href="https://hacks.mozilla.org/feed/"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;The Web developer blog.&lt;/p&gt;&lt;/blockquote&gt;

&lt;h3 id="on-life-and-lisp"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#on-life-and-lisp"&gt;On Life and Lisp&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://rosenzweig.io"&gt;Homepage&lt;/a&gt; &lt;a href="https://rosenzweig.io/feed.xml"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;Software freedom, graphics, and gay.&lt;/p&gt;&lt;/blockquote&gt;

&lt;h3 id="pcg"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#pcg"&gt;PCG&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.pcg-random.org"&gt;Homepage&lt;/a&gt; &lt;a href="https://www.pcg-random.org/rss.xml"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;A Better Random Number Generator.&lt;/p&gt;&lt;/blockquote&gt;

&lt;h3 id="performance-matters"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#performance-matters"&gt;Performance Matters&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://travisdowns.github.io"&gt;Homepage&lt;/a&gt; &lt;a href="https://travisdowns.github.io/feed.xml"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;A blog about low-level software and hardware performance.&lt;/p&gt;&lt;/blockquote&gt;

&lt;h3 id="postgresql-and-databases-in-general"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#postgresql-and-databases-in-general"&gt;PostgreSQL and Databases in general&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://amitkapila16.blogspot.com"&gt;Homepage&lt;/a&gt; &lt;a href="https://amitkapila16.blogspot.com/feeds/posts/default"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;Database internals.&lt;/p&gt;&lt;/blockquote&gt;

&lt;h3 id="random-ascii--tech-blog-of-bruce-dawson"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#random-ascii--tech-blog-of-bruce-dawson"&gt;Random ASCII – tech blog of Bruce Dawson&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://randomascii.wordpress.com"&gt;Homepage&lt;/a&gt; &lt;a href="https://randomascii.wordpress.com/feed/"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;Programming, tech topics, with a chance of unicycling&lt;/p&gt;&lt;/blockquote&gt;

&lt;h3 id="stephen-colebournes-blog"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#stephen-colebournes-blog"&gt;Stephen Colebourne’s blog&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://blog.joda.org"&gt;Homepage&lt;/a&gt; &lt;a href="https://blog.joda.org/feeds/posts/default"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;Thoughts and Musings on the world of Java and beyond.&lt;/p&gt;&lt;/blockquote&gt;

&lt;h3 id="tyler-mandry"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#tyler-mandry"&gt;Tyler Mandry&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://tmandry.gitlab.io/blog/"&gt;Homepage&lt;/a&gt; &lt;a href="https://tmandry.gitlab.io/blog/index.xml"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Focus on Rust.&lt;/p&gt;

&lt;h3 id="v8"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#v8"&gt;V8&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://v8.dev/blog"&gt;Homepage&lt;/a&gt; &lt;a href="https://v8.dev/blog.atom"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="without-boats-dreams-dry-up"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#without-boats-dreams-dry-up"&gt;Without boats, dreams dry up&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://without.boats"&gt;Homepage&lt;/a&gt; &lt;a href="https://without.boats/index.xml"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="xavers-blog"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#xavers-blog"&gt;Xaver’s blog&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://zamundaaa.github.io"&gt;Homepage&lt;/a&gt; &lt;a href="https://zamundaaa.github.io/feed.xml"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;More or less random posts about stuff in KDE and computer graphics.&lt;/p&gt;&lt;/blockquote&gt;

&lt;h3 id="baby-steps"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#baby-steps"&gt;baby steps&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://smallcultfollowing.com/babysteps/"&gt;Homepage&lt;/a&gt; &lt;a href="https://smallcultfollowing.com/babysteps/atom.xml"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;This blog is where I post up various half-baked ideas that I have.&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;Focus on Rust.&lt;/p&gt;

&lt;h3 id="benjojo-blog"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#benjojo-blog"&gt;benjojo blog&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://blog.benjojo.co.uk"&gt;Homepage&lt;/a&gt; &lt;a href="https://blog.benjojo.co.uk/rss.xml"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;Programming, Networking and some things I found hard to fix at some point&lt;/p&gt;&lt;/blockquote&gt;

&lt;h3 id="brr"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#brr"&gt;brr&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://brr.fyi"&gt;Homepage&lt;/a&gt; &lt;a href="https://brr.fyi/feed.xml"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;Observations on Antarctic infrastructure. Anecdotes from daily life for support staff. Focused on McMurdo Station and Amundsen-Scott South Pole Station.&lt;/p&gt;&lt;/blockquote&gt;

&lt;h3 id="computers-are-bad"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#computers-are-bad"&gt;computers are bad&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://computer.rip"&gt;Homepage&lt;/a&gt; &lt;a href="https://computer.rip/rss.xml"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;A newsletter on technology and its problems and also whatever else&lt;/p&gt;&lt;/blockquote&gt;

&lt;h3 id="crypto-blog"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#crypto-blog"&gt;cr.yp.to blog&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://blog.cr.yp.to"&gt;Homepage&lt;/a&gt; &lt;a href="https://blog.cr.yp.to/feed.application=xml"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;D. J. Bernstein’s personal weblog.&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;Focus on cryptography.&lt;/p&gt;

&lt;h3 id="dolphin-emulator"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#dolphin-emulator"&gt;Dolphin Emulator&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://dolphin-emu.org/blog/"&gt;Homepage&lt;/a&gt; &lt;a href="https://dolphin-emu.org/blog/feeds/"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Posts about the Dolphin GameCube and Wii emulator with in-depth technical information.&lt;/p&gt;

&lt;h3 id="what-if"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#what-if"&gt;what if?&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://what-if.xkcd.com"&gt;Homepage&lt;/a&gt; &lt;a href="https://what-if.xkcd.com/feed.atom"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;Answering your hypothetical questions with physics, every Tuesday.&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;Spoiler: Not every Tuesday.&lt;/p&gt;

&lt;h3 id="hermans-blog"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#hermans-blog"&gt;ᕕ( ᐛ )ᕗ Herman’s blog&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://herman.bearblog.dev"&gt;Homepage&lt;/a&gt; &lt;a href="https://herman.bearblog.dev/feed/"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;I’m currently working on a no-nonsense blogging platform and a GPT-powered socratic tutor.&lt;/p&gt;&lt;/blockquote&gt;

&lt;h2 id="comics"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#comics"&gt;Comics&lt;/a&gt;&lt;/h2&gt;

&lt;h3 id="loading-artist"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#loading-artist"&gt;Loading Artist&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://loadingartist.com"&gt;Homepage&lt;/a&gt; &lt;a href="https://loadingartist.com/index.xml"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="poorly-drawn-lines"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#poorly-drawn-lines"&gt;Poorly Drawn Lines&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://poorlydrawnlines.com"&gt;Homepage&lt;/a&gt; &lt;a href="https://poorlydrawnlines.com/feed/"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="sarahs-scribbles"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#sarahs-scribbles"&gt;Sarah’s Scribbles&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://sarahcandersen.com"&gt;Homepage&lt;/a&gt; &lt;a href="https://sarahcandersen.com/rss"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="sticky-comics"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#sticky-comics"&gt;sticky comics&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.stickycomics.com"&gt;Homepage&lt;/a&gt; &lt;a href="https://www.stickycomics.com/feed/"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="webcomic-name"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#webcomic-name"&gt;webcomic name&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://webcomicname.com"&gt;Homepage&lt;/a&gt; &lt;a href="https://webcomicname.com/rss"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;the same joke every time&lt;/p&gt;&lt;/blockquote&gt;

&lt;h2 id="videos"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#videos"&gt;Videos&lt;/a&gt;&lt;/h2&gt;

&lt;h3 id="breaking-taps"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#breaking-taps"&gt;Breaking Taps&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://nebula.tv/breakingtaps"&gt;Homepage&lt;/a&gt; &lt;a href="https://rss.nebula.app/video/channels/breakingtaps.rss"&gt;Nebula Feed&lt;/a&gt; &lt;a href="https://www.youtube.com/feeds/videos.xml?channel_id=UC06HVrkOL33D5lLnCPjr6NQ"&gt;YouTube Feed&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="cgp-grey"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#cgp-grey"&gt;CGP Grey&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.cgpgrey.com"&gt;Homepage&lt;/a&gt; &lt;a href="https://www.youtube.com/feeds/videos.xml?channel_id=UC2C_jShtL725hvbm1arSV9w"&gt;YouTube Feed&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="captain-disillusion"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#captain-disillusion"&gt;Captain Disillusion&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/@CaptainDisillusion"&gt;Homepage&lt;/a&gt; &lt;a href="https://www.youtube.com/feeds/videos.xml?channel_id=UCEOXxzW2vU0P-0THehuIIeg"&gt;YouTube Feed&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="hbomberguy"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#hbomberguy"&gt;Hbomberguy&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://nebula.tv/hbomberguy"&gt;Homepage&lt;/a&gt; &lt;a href="https://rss.nebula.app/video/channels/hbomberguy.rss"&gt;Nebula Feed&lt;/a&gt; &lt;a href="https://www.youtube.com/feeds/videos.xml?channel_id=UClt01z1wHHT7c5lKcU8pxRQ"&gt;YouTube Feed&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="practical-construction"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#practical-construction"&gt;Practical Construction&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://practical.engineering/"&gt;Homepage&lt;/a&gt; &lt;a href="https://rss.nebula.app/video/channels/practicalconstruction.rss"&gt;Nebula Feed&lt;/a&gt; &lt;a href="https://www.youtube.com/feeds/videos.xml?channel_id=UCMOqf8ab-42UUQIdVoKwjlQ"&gt;YouTube Feed&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="practical-engineering"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#practical-engineering"&gt;Practical Engineering&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://practical.engineering/"&gt;Homepage&lt;/a&gt; &lt;a href="https://rss.nebula.app/video/channels/practical-engineering.rss"&gt;Nebula Feed&lt;/a&gt; &lt;a href="https://www.youtube.com/feeds/videos.xml?channel_id=UCMOqf8ab-42UUQIdVoKwjlQ"&gt;YouTube Feed&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="retro-game-mechanics-explained"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#retro-game-mechanics-explained"&gt;Retro Game Mechanics Explained&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/@RGMechEx"&gt;Homepage&lt;/a&gt; &lt;a href="https://www.youtube.com/feeds/videos.xml?channel_id=UCwRqWnW5ZkVaP_lZF7caZ-g"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="simone-giertz"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#simone-giertz"&gt;Simone Giertz&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/@simonegiertz"&gt;Homepage&lt;/a&gt; &lt;a href="https://www.youtube.com/feeds/videos.xml?channel_id=UC3KEoMzNz8eYnwBC34RaKCQ"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="technology-connections"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#technology-connections"&gt;Technology Connections&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/@TechnologyConnections"&gt;Homepage&lt;/a&gt; &lt;a href="https://www.youtube.com/feeds/videos.xml?channel_id=UCy0tKL1T7wFoYcxCe0xjN6Q"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="veritasium"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#veritasium"&gt;Veritasium&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/@veritasium"&gt;Homepage&lt;/a&gt; &lt;a href="https://www.youtube.com/feeds/videos.xml?channel_id=UCHnyfMqiRRG1u-2MsSQLbXA"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="zogg-from-betelgeuse"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#zogg-from-betelgeuse"&gt;Zogg from Betelgeuse&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/@ZoggFromBetelgeuse"&gt;Homepage&lt;/a&gt; &lt;a href="https://www.youtube.com/feeds/videos.xml?channel_id=UCKY00CSQo1MoC27bdGd-w_g"&gt;Feed&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="videoxiphorg"&gt;&lt;a href="https://kevincox.ca/2024/02/29/favourite-feeds/#videoxiphorg"&gt;video.xiph.org&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://xiph.org/video/"&gt;Homepage&lt;/a&gt; &lt;a href="https://xiph.org/video/atom-feed.xml"&gt;Feed&lt;/a&gt;&lt;/p&gt;



&lt;blockquote&gt;&lt;p&gt;Xiph.Org’s video series about digital media.&lt;/p&gt;&lt;/blockquote&gt;

</description>
      <category>rss</category>
    </item>
    <item>
      <title>Running a Terraria Dedicated Server on NixOS</title>
      <dc:creator>Kevin Cox</dc:creator>
      <pubDate>Mon, 19 Feb 2024 17:05:00 +0000</pubDate>
      <link>https://forem.com/kevincox/running-a-terraria-dedicated-server-on-nixos-5fmi</link>
      <guid>https://forem.com/kevincox/running-a-terraria-dedicated-server-on-nixos-5fmi</guid>
      <description>&lt;p&gt;I recently started playing Terraria and have to say it is a very fun game! To let anyone play at any time I decided to spin up a dedicated server. Unfortunately the Terraria server isn’t what I would consider great server software. Here is how I got a good setup working on NixOS.&lt;/p&gt;

&lt;p&gt;The main flaw with the Terraria server is that it doesn’t gracefully handle SIGTERM. Generally it is expected that upon receiving SIGTERM an application will gracefully shut down. But Terraria just exits immediately &lt;strong&gt;without saving the world!&lt;/strong&gt; With the non-configurable 30min save interval this means that it is easy to lose a lot of data with a naive setup. Even more annoying is that the way to trigger a graceful exit is to run the server in the console and type &lt;code&gt;save&lt;/code&gt; or &lt;code&gt;exit&lt;/code&gt; (which saves). But I don’t want to do this manually every time I update Terraria, change the config or restart my server! So we need to automate this.&lt;/p&gt;


&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: &lt;a href="https://github.com/NixOS/nixpkgs/blob/nixos-unstable/nixos/modules/services/games/terraria.nix"&gt;NixOS does have a Terraria module&lt;/a&gt;, but I wasn’t a fan with how it worked. I’ll discuss the improvements as I get to them. Maybe we can pull some of these improvements upstream.&lt;/p&gt;
&lt;h2 id="config"&gt;&lt;a href="https://kevincox.ca/2024/02/19/terraria-server-nixos/#config"&gt;Config&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;Let’s just start with a big config dump, then I’ll go over the interesting bits later.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;lib&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;pkgs&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;...&lt;/span&gt;&lt;span&gt;}:&lt;/span&gt; &lt;span&gt;let&lt;/span&gt;
    &lt;span&gt;dataDir&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;"/var/lib/terraria"&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
    &lt;span&gt;worldDir&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;"&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;dataDir&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/worlds"&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;

    &lt;span&gt;# Simple config file serializer. Not very robust.&lt;/span&gt;
    &lt;span&gt;mkConfig&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;options&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
        &lt;span&gt;builtins&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;toFile&lt;/span&gt;
            &lt;span&gt;"terraria.cfg"&lt;/span&gt;
            &lt;span&gt;(&lt;/span&gt;&lt;span&gt;lib&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;concatStrings&lt;/span&gt;
                &lt;span&gt;(&lt;/span&gt;&lt;span&gt;lib&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;mapAttrsToList&lt;/span&gt;
                    &lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;value&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;"&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt; &lt;span&gt;value&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;
                    &lt;span&gt;options&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;

    &lt;span&gt;# Config Generator&lt;/span&gt;
    &lt;span&gt;mkWorld&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
        &lt;span&gt;worldSize&lt;/span&gt; &lt;span&gt;?&lt;/span&gt; &lt;span&gt;"large"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
    &lt;span&gt;}:&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
        &lt;span&gt;config&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;mkConfig&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
            &lt;span&gt;world&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;"&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;worldDir&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;.wld"&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
            &lt;span&gt;password&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;"YOUR PASSWORD HERE!!!"&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
            &lt;span&gt;seed&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;"kevincox-&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
            &lt;span&gt;autocreate&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;{&lt;/span&gt; &lt;span&gt;small&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt; &lt;span&gt;medium&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;2&lt;/span&gt;&lt;span&gt;;&lt;/span&gt; &lt;span&gt;large&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;3&lt;/span&gt;&lt;span&gt;;&lt;/span&gt; &lt;span&gt;}&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;worldSize&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;
            &lt;span&gt;upnp&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
        &lt;span&gt;};&lt;/span&gt;
    &lt;span&gt;};&lt;/span&gt;

    &lt;span&gt;# High-level Config&lt;/span&gt;
    &lt;span&gt;worlds&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;lib&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;mapAttrs&lt;/span&gt; &lt;span&gt;mkWorld&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
        &lt;span&gt;my-first-world&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;{};&lt;/span&gt;
        &lt;span&gt;some-other-world&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
            &lt;span&gt;worldSize&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;"medium"&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
        &lt;span&gt;};&lt;/span&gt;
    &lt;span&gt;};&lt;/span&gt;

    &lt;span&gt;world&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;worlds&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;my-first-world&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
&lt;span&gt;in&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
    &lt;span&gt;users&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;users&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;terraria&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
        &lt;span&gt;group&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;"terraria"&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
        &lt;span&gt;home&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;dataDir&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
        &lt;span&gt;uid&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;config&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ids&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;uids&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;terraria&lt;/span&gt;&lt;span&gt;;&lt;/span&gt; &lt;span&gt;# NixOS has a Terraria user, so use those IDs.&lt;/span&gt;
    &lt;span&gt;};&lt;/span&gt;

    &lt;span&gt;users&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;groups&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;terraria&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
        &lt;span&gt;gid&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;config&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ids&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;gids&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;terraria&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
    &lt;span&gt;};&lt;/span&gt;

    &lt;span&gt;systemd&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;sockets&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;terraria&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
        &lt;span&gt;socketConfig&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
            &lt;span&gt;ListenFIFO&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;[&lt;/span&gt;&lt;span&gt;"/run/terraria.sock"&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;
            &lt;span&gt;SocketUser&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;"terraria"&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
            &lt;span&gt;SocketMode&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;"0660"&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
            &lt;span&gt;RemoveOnStop&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;true&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
        &lt;span&gt;};&lt;/span&gt;
    &lt;span&gt;};&lt;/span&gt;

    &lt;span&gt;systemd&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;services&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;terraria&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
        &lt;span&gt;wantedBy&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;[&lt;/span&gt; &lt;span&gt;"multi-user.target"&lt;/span&gt; &lt;span&gt;];&lt;/span&gt;
        &lt;span&gt;after&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;[&lt;/span&gt; &lt;span&gt;"network.target"&lt;/span&gt; &lt;span&gt;];&lt;/span&gt;
        &lt;span&gt;bindsTo&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;[&lt;/span&gt;&lt;span&gt;"terraria.socket"&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;

        &lt;span&gt;preStop&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;''&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt; printf '\nexit\n' &amp;gt;/run/terraria.sock&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt; ''&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;

        &lt;span&gt;serviceConfig&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
            &lt;span&gt;User&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;"terraria"&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
            &lt;span&gt;ExecStart&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;lib&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;escapeShellArgs&lt;/span&gt; &lt;span&gt;[&lt;/span&gt;
                &lt;span&gt;"&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;pkgs&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;terraria-server&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/bin/TerrariaServer"&lt;/span&gt;
                &lt;span&gt;"-config"&lt;/span&gt; &lt;span&gt;world&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;config&lt;/span&gt;
            &lt;span&gt;];&lt;/span&gt;

            &lt;span&gt;StateDirectory&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;"terraria"&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
            &lt;span&gt;StateDirectoryMode&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;"0750"&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;

            &lt;span&gt;StandardInput&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;"socket"&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
            &lt;span&gt;StandardOutput&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;"journal"&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
            &lt;span&gt;StandardError&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;"journal"&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;

            &lt;span&gt;KillSignal&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;"SIGCONT"&lt;/span&gt;&lt;span&gt;;&lt;/span&gt; &lt;span&gt;# Wait for exit after `ExecStop` (https://github.com/systemd/systemd/issues/13284)&lt;/span&gt;
            &lt;span&gt;TimeoutStopSec&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;"1h"&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
        &lt;span&gt;};&lt;/span&gt;
    &lt;span&gt;};&lt;/span&gt;

    &lt;span&gt;kevincox&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;backup&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;terraria&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;paths&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;[&lt;/span&gt;
        &lt;span&gt;dataDir&lt;/span&gt;
    &lt;span&gt;];&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;strong&gt;Warning&lt;/strong&gt;: I currently just hardcode the password in the config file. This means that it is visible both in my Git repo and world-readable in the Nix store. I don’t consider this password very sensitive so I consider that fine. If desired the config file could be moved to a secret deployed via something like &lt;code&gt;nixops&lt;/code&gt;. But for now I am fine with this.&lt;/p&gt;
&lt;h2 id="graceful-exits"&gt;&lt;a href="https://kevincox.ca/2024/02/19/terraria-server-nixos/#graceful-exits"&gt;Graceful Exits&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;As mentioned earlier Terraria has very ungraceful exits by default. Most of the complexity in this configuration is just handling that. The basic strategy is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a socket for stdin.&lt;/li&gt;
&lt;li&gt;Set up &lt;code&gt;ExecStop&lt;/code&gt; (via &lt;code&gt;preStop&lt;/code&gt;) to request a graceful exit.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The idea is simple but the implementation is tedious. It took a lot of doc-reading and trial-and-error to get everything working. I’m not going to go over the details but just try deleting any of the related lines to watch it fail.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a &lt;code&gt;terraria.socket&lt;/code&gt; unit for the stdin pipe.&lt;/li&gt;
&lt;li&gt;Bind the units together. Otherwise stopping the socket leaves you unable to gracefully kill the server.&lt;/li&gt;
&lt;li&gt;Configure &lt;code&gt;StandardInput&lt;/code&gt; which requires configuring &lt;code&gt;StandardOutput&lt;/code&gt; and &lt;code&gt;StandardError&lt;/code&gt; as the defaults would otherwise change.&lt;/li&gt;
&lt;li&gt;Configure &lt;code&gt;preStop&lt;/code&gt; to request an exit.&lt;/li&gt;
&lt;li&gt;Because our &lt;code&gt;preStop&lt;/code&gt; is async we need to disable the &lt;code&gt;KillSignal&lt;/code&gt;. We do this by using a noop signal.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As a side benefit we can just echo whatever we want to the socket to run other admin commands. It is a bit awkward because the output will show up in the journal rather than your console but this seems like the best tradeoff. Just run &lt;code&gt;journalctl -f terraria&lt;/code&gt; in another window to see the output.&lt;/p&gt;


&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: The NixOS module handles this by setting up &lt;code&gt;tmux&lt;/code&gt;. While this does give a better interactive experience it increased complexity and swallowed logs. Also the socket is put in the state directory (&lt;code&gt;/var/lib&lt;/code&gt;) rather than the runtime directory (&lt;code&gt;/run&lt;/code&gt;) which made my backup tool angry. Both the logs and socket location were fixable, but I opted for the system with less moving parts in the end.&lt;/p&gt;
&lt;h2 id="world-management"&gt;&lt;a href="https://kevincox.ca/2024/02/19/terraria-server-nixos/#world-management"&gt;World Management&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;I wanted to be able to declaratively provision new worlds. This is what the &lt;code&gt;worlds&lt;/code&gt; variable is for. I can define a bunch of different worlds and then switch between them just by updating the &lt;code&gt;world&lt;/code&gt; pointer. The first time a world is loaded it will be automatically generated according to the settings. Most settings (like seed and world size) then become inert. So it is not perfectly declarative, but that is largely expected for persistent data and accomplishes my main use cases of automatically provisioning a new world with just a config change.&lt;/p&gt;

&lt;p&gt;For now the only option I support is &lt;code&gt;worldSize&lt;/code&gt;, but I will surely add more knobs as I need them. For example if I start up another world I might want a per-world password.&lt;/p&gt;


&lt;p&gt;&lt;strong&gt;Warning&lt;/strong&gt;: The NixOS module seems to have trouble with this. When tyring to start up with &lt;code&gt;services.terraria.worldPath&lt;/code&gt; set to a location that didn’t exist it would create a new world in &lt;code&gt;/var/lib/terraria/.local/share/Terraria/Worlds&lt;/code&gt; every time the server started. I don’t know why this is as I didn’t have any issues when creating my module, maybe the config file option is handled better than the command-line flag?&lt;/p&gt;
&lt;h2 id="config-file"&gt;&lt;a href="https://kevincox.ca/2024/02/19/terraria-server-nixos/#config-file"&gt;Config File&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;I chose to generate a config file rather than passing command-line flags. The main reason for this is that the config file appears to contain a superset of the options available on the command line. So I figured if I start using those options it will be easier to just keep everything in the config file rather than generating both flags and config.&lt;/p&gt;

&lt;p&gt;For now the config file generator uses no escaping. I don’t even know if Terraria supports any escaping. Ideally I would at least assert that the substituted value doesn’t contain a newline, but I didn’t feel like it.&lt;/p&gt;

&lt;h2 id="backups"&gt;&lt;a href="https://kevincox.ca/2024/02/19/terraria-server-nixos/#backups"&gt;Backups&lt;/a&gt;&lt;/h2&gt;


&lt;p&gt;Another nice feature of a dedicated server is that I can take and publish world backups. Any player can grab a copy if they want and data-loss in the case of a catastrophic event is limited. Of course, you can also take backups on the host’s computer. But it is nice to just roll into the regular process and monitoring that I already have on my servers.&lt;/p&gt;

</description>
      <category>terraria</category>
      <category>nixos</category>
      <category>guide</category>
    </item>
    <item>
      <title>AI Stole Our Jobs</title>
      <dc:creator>Kevin Cox</dc:creator>
      <pubDate>Sat, 17 Feb 2024 19:35:00 +0000</pubDate>
      <link>https://forem.com/kevincox/ai-stole-our-jobs-3c6o</link>
      <guid>https://forem.com/kevincox/ai-stole-our-jobs-3c6o</guid>
      <description>&lt;p&gt;AI is hot right now. Everyone is excited, some people are positive, but many are concerned. One common opposition I hear is “AI will steal our jobs”. This phrase always catches my attention, because while it was always framed negatively it really should be a positive. We can now have computers do things that people had to do. Our society can be more productive with less work!&lt;/p&gt;

&lt;p&gt;So why are people scared? People are scared because in our society you need a job. Without a job you can’t afford a roof over your head or food on the table. But isn’t that the actual problem? Our society no longer needs this task, but instead of being freed from a now unnecessary duty the labourers are being punished. We have now more resources for everyone, but instead we are just taking resources from people who need them the most, and giving them to billionaires who benefit little.&lt;/p&gt;

&lt;p&gt;This isn’t a new problem either. Every time there are talks about closing a coal power plant the coal lobby talks about how many jobs are being created by that power plant and in the mines. But this isn’t a good thing. Jobs aren’t a goal, they are a cost. We have people killing themselves to mine coal so that we can burn that coal to kill more people with pollution. But the coal plant can’t be replaced with a cheaper solar farm because that would require too few jobs. This is a lose-lose-lose scenario. We need to stop paying people to do harmful things. We would be better off paying them to sit at home.&lt;/p&gt;

&lt;p&gt;I don’t know what the best solution is. But capitalism clearly isn’t producing the best world for everyone. On this planet we have enough resources for everyone, but many still suffer. Then we tell ourselves that they would suffer more if we didn’t force them to labour for a pittance. This twisted logic is used to justify harmful decisions that make the original problem worse.&lt;/p&gt;

&lt;p&gt;We need to make it so that improving our society’s efficiency and productivity doesn’t harm us. People relieved from work mustn’t be punished. I think we should still reward those who do spend their time being “productive” (whatever that means), but that should be a choice you make, not a necessity. Our current society effectively forces people to work. The existence of minimum wage is a symptom of this. If people had complete agency when negotiating a salary they would be able to walk away from offers that were too low. But people don’t have a free choice, they &lt;strong&gt;need&lt;/strong&gt; a job. If people truly had a free choice when signing an employment contract we wouldn’t need rules about minimum wage, we wouldn’t need most worker protections. These are only needed because workers don’t have a free choice, the employer has far more power.&lt;/p&gt;


&lt;p&gt;In a good society AI taking people’s jobs would lead to 3-day working weeks, not poverty.&lt;/p&gt;

</description>
      <category>thought</category>
    </item>
    <item>
      <title>How to Highlight Thousands Groups of Large Numers in Vim</title>
      <dc:creator>Kevin Cox</dc:creator>
      <pubDate>Wed, 27 Sep 2023 21:50:00 +0000</pubDate>
      <link>https://forem.com/kevincox/how-to-highlight-thousands-groups-of-large-numers-in-vim-5e66</link>
      <guid>https://forem.com/kevincox/how-to-highlight-thousands-groups-of-large-numers-in-vim-5e66</guid>
      <description>&lt;p&gt;When viewing large numbers it can be helpful to separate thousands or some other grouping. Some languages provide a way to do this natively (such as &lt;code&gt;1_000_000&lt;/code&gt;) but others do not, or sometimes you are editing a generated file that doesn’t do this. I wrote a highlight rule for Vim to highlight the groups to make these numbers easier to read.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span&gt;match&lt;/span&gt; SpellRare &lt;span&gt;/\d\{1,3}\ze\%(\d\{6}\)*\d\{3}\&amp;gt;/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Replace &lt;code&gt;SpellRare&lt;/code&gt; with any highlight group of your choosing. In my editor it looks like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt; 1 &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;1 &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; 2 &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;12 &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; 3 &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;234 &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; 4 &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;123&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;456 &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; 5 &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;234&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;567&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;890 &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; 6 &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;12&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;345&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;678&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;901&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;234 &lt;/span&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I can imagine it being slightly smarter like not highlighting a 4-digit number, but it is simple and doesn’t noticeably slow down my editor. I’ve tried this in Vim and Neovim, and it works well in both.&lt;/p&gt;

&lt;h2 id="how-it-works"&gt;&lt;a href="https://kevincox.ca/2023/09/27/vim-thousands-highlight/#how-it-works"&gt;How it Works&lt;/a&gt;&lt;/h2&gt;


&lt;p&gt;Basically it is matching a set of 1-3 digits that is followed by exactly &lt;span&gt;6n+36n+3&lt;/span&gt;digits then an end-of-word.&lt;/p&gt;

</description>
      <category>vim</category>
    </item>
  </channel>
</rss>
