diff --git a/src/routes/posts/2024.05.27.adding-single-click-dev-environment-links-to-your-open-sourc.md b/src/routes/posts/2024.05.27.adding-single-click-dev-environment-links-to-your-open-sourc.md
index 6b2e768..d5b99a9 100644
--- a/src/routes/posts/2024.05.27.adding-single-click-dev-environment-links-to-your-open-sourc.md
+++ b/src/routes/posts/2024.05.27.adding-single-click-dev-environment-links-to-your-open-sourc.md
@@ -2,7 +2,6 @@
title: 'Adding single click dev environment links to your open source project for easy contributions'
date: 2024-05-27T05:10:06Z
description: 'See how you can streamline contributions and onboarding by leveraging DevContainers and cloud dev environments - a one-click setup that lets new folks dive right in!'
-highlighted: true
---
[I have been using DevContainers](/2023.02.10.why-use-devcontainer/) a lot. If
diff --git a/src/routes/posts/2024.10.06.i-built-an-api-for-random-names.md b/src/routes/posts/2024.10.06.i-built-an-api-for-random-names.md
new file mode 100644
index 0000000..e1ae20a
--- /dev/null
+++ b/src/routes/posts/2024.10.06.i-built-an-api-for-random-names.md
@@ -0,0 +1,178 @@
+---
+title: 'I built an API for random names and numbers because why not?'
+date: 2024-10-06T14:18:00Z
+description: 'So I needed random names for a script, and instead of using a sensible solution, I decided to build an entire API. Join me as I fumble through Go, preact, and Kamal, then end up with a functional free public API.'
+highlighted: true
+---
+
+I was recently writing up a script for... something. I honestly can't quite
+remember anymore, but I needed to generate random names in that script. Think
+something like how Github will assign random names to Codespaces like `upgraded giggle`.
+You can always just call `openssl rand -hex 12` and get a random string,
+but memorable phrases are always better.
+It might not be everyday that you need to read out a container name or remember
+a URL off the top of your head, but when you do it helps out so much to have a
+phrase than a random sequence of letters and numbers.
+
+Importantly for this case, I was writing a basic script that couldn't assume
+much about what is installed or present on the system, and I didn't want to pull
+in any extra files or bloat the script with a large dictionary of words. So I
+thought it would be a smart idea to call a public API to get a random name
+instead!
+
+Simple public APIs like [ipify.org](https://www.ipify.org/) are always a life
+saver when you're writing up a small script and you just need something real
+quick. But I couldn't find one for generating random words or names.
+So I decided to take this up and build one myself.
+
+## Building the API
+
+I've been wanting to learn Go for a while, partly because my new job uses Go in
+some parts. Nothing I work on right now, but I thought it would be useful in
+case something did come up. And besides, I've been looking for something
+in-between NodeJS and Rust in the tradeoff of developer experience and
+performance, and thought Go might strike a good balance.
+
+I ended up building the service with [Fiber](https://gofiber.io/). I primarily
+looked up [a benchmark for requests per second](https://web-frameworks-benchmark.netlify.app/result?asc=0&l=go&metric=totalRequestsPerS&order_by=level64),
+and Fiber stood out as the top option that is actively maintained and well
+documented.
+
+Playing around with that benchmark, you can see there are other frameworks that
+excel in other tasks, but I primarily care about requests per second since that
+is going to ultimately decide how much load the API can handle, and stuff
+like response latency is not particularly important.
+
+### So how was Go?
+
+Building the API took very little time. I am extremely impressed with Go in that
+regard. Sure, the constant `if err != nil` checks do get tedious, but unlike
+when I was using Rust I never had to grapple with incomprehensible compiler
+errors. That's definitely an unfair comparison for Rust, and there are of course
+cases where Go's garbage collector is going to be an unacceptable tradeoff. But
+building APIs is my use case for these languages.
+
+I was pleasantly surprised with the standard library
+[embed](https://pkg.go.dev/embed) functionality, which allows you to bundle
+resources into the program itself. I think this is the way to go for all small
+projects where the backend/API is serving the UI. You can always put a caching
+proxy/CDN in front of the backend, and it simplifies your deployments since you
+now always have a matching version of the UI and backend combined bundled
+together into a single file or container.
+
+The main thing I didn't like was the error handling. I think returning errors
+from functions is a good call, but requiring explicit checks for them makes the
+code noisy. I wish there was an equivalent of Rust's `?` operator that allows
+you to easily bubble up errors, which is what you often want. In an API, it's
+very natural to handle errors at the API level, so you can always bubble the
+error up and then turn it into a 400 or 500 response at a higher level. Or if
+there was support for compiler macros, so someone could build this support into
+Go.
+
+I did find the amazing [Mo](https://github.com/samber/mo), which brings Monads
+to Go. With support for `Option` and `Result`, it does everything I want and
+more. I didn't end up really using it, but I could see that being a better
+option than passing `nil`s around.
+
+And that is one thing I was really disappointed with Go, `nil`. At least make it
+strongly typed so it's obvious what is nullable and what is not!
+
+## The docs
+
+For documenting the API, I decided to build something myself. I knew I wanted an
+interactive doc, and felt that I could have some fun with it. So I built a site
+using [preact](https://preactjs.com/). preact itself is okay, not much to talk
+about. It's basically react. But one thing I immediately fell in love with was
+[signals](https://preactjs.com/guide/v10/signals/). It's a beautiful solution,
+allowing you to handle both global and local state with ease.
+
+Then I put that together with [TailwindCSS](https://tailwindcss.com/) and
+[DaisyUI](https://daisyui.com/). These are my go-to picks nowadays, it makes
+building a decent looking interface really quickly. The whole thing is then
+bundled with [Vite](https://vite.dev/).
+
+## Deploying
+
+Deployment was an interesting question too. In the past I have usually done it
+manually. I also dabbled with ansible a bit, but never used it to deploy stuff.
+But I have gotten really used to automated deployments at work, so I wanted
+something similar for this project. At the same time, I didn't want to just
+throw the project up on a serverless hosting solution, because I'm building a
+free public API here and I don't want to wake up to a million-dollar bill one
+day.
+
+Thanks to some lovely feedback I got on the Fediverse, I found out about Kamal though.
+
+
+
+Setting up Kamal was in some ways easier than I expected. It did take me a few
+attempts of fiddling with CI and Tailscale, but I eventually got it working.
+
+I have Tailscale running on the small VPS hosting the API. I use the Tailscale
+Github Action to temporarily authenticate the CI runner with Tailscale.
+
+```yml
+# In a Github Actions workflow:
+
+# ...
+- name: Tailscale
+ uses: tailscale/github-action@v2
+ with:
+ oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }}
+ oauth-secret: ${{ secrets.TS_OAUTH_SECRET }}
+ tags: tag:ci
+- uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: '3.3'
+- name: Deploy
+ run: |
+ gem install kamal
+ kamal deploy
+```
+
+I enabled Tailscale SSH on the VPS, then configured the access controls so the
+CI runners can authenticate with the VPS without messing around with SSH keys.
+
+```json
+{
+ "tagOwners": {
+ // Temporary CI runners
+ "tag:ci": ["autogroup:admin"],
+ // Devices that can receive deploys from CI
+ "tag:target": ["autogroup:admin"],
+ },
+
+ "acls": [
+ // ...
+
+ // CI has access to CI targets
+ {
+ "action": "accept",
+ "src": ["tag:ci"],
+ "dst": ["tag:target:*"],
+ },
+ ],
+
+ "ssh": [
+ {
+ "action": "accept",
+ "src": ["tag:ci"],
+ "dst": ["tag:target"],
+ "users": ["root"],
+ },
+ ],
+}
+```
+
+And that was about it. After following the instructions, Kamal deployed
+everything and even handled getting an SSL certificate for me. This API is now
+up and running at [rnd.bgenc.dev](https://rnd.bgenc.dev), which is a new domain
+I recently bought for any small projects I build like this.
+
+As a side note, I now own `bgenc.com`, `.net`, and `.dev`! I'm really excited
+because someone was squatting `bgenc.com` for a while, but it finally became
+available a couple months ago and I grabbed it instantly. I wasn't even
+checking, but I got an email for some scam offering to help me get .com and .org
+because they were becoming available soon. I checked the domain and tada, it in
+fact was abandoned. After refreshing the domain registry's page for days, I
+grabbed it as soon as I could.