[1] Xe
[2] Blog
[3] Contact
[4] Resume
[5] Talks
[6] VODs
[7] Signalboost

gokrazy is really cool

Published on 09/20/2023, 3515 words, 13 minutes to read

An image of undefined - Nikon D3300, 35mm f/1.8 DX, a red formula one style car
speeds along a racetrack with visible tire skidmarks. Photo by Xe Iaso I worked
for Tailscale at the time of writing this article. I wrote this on my own time
out of my own volition.

When you deal with Linux, you end up hearing about "distributions" as different
"flavors" of Linux combined with a bunch of other tools. This is mostly true,
but it's slightly missing the forest for the trees.

Consider this famous and often misunderstood quote by Richard Stallman:

    I'd just like to interject for a moment. What you're referring to as Linux
    is in fact, GNU/Linux, or as I've recently taken to calling it, GNU plus
    Linux.Linux is not an operating system unto itself, but rather another free
    component of a fully functioning GNU system made useful by the GNU
    corelibs, shell utilities and vital system components comprising a full OS
    as defined by POSIX.

Many pages of ink have been spilled over analyzing this quote, and a lot of
them fall short of really getting at the heart of the matter. What this
actually means is something like this:

By itself, Linux is useless. It does boot the system, it does interface with
hardware, but without a bunch of other tools, it's not very useful. It's like a
car without a steering wheel, or a boat without a rudder. It does something,
but it's not very useful. The real value of things like the GNU project,
systemd, openrc and other tools in that vein is that they make Linux useful.
They make it into a complete system that you can use to do things. They are the
proverbial steering wheel and rudder in the metaphor.

Mara is hacker
<[8]Mara> Fun fact, if you try to boot a Linux kernel without an init process,
it'll just panic and crash!

Most Linux systems on the face of the planet are built with GNU tools and
utilities. In order to compile the Linux kernel, you need to use [9]GCC. In
order to run ls to list files in the current directory, you need to use [10]GNU
coreutils. Every dynamically linked program uses [11]glibc for performing basic
system interactions like writing to files or opening network sockets.
Everything is built on top of the GNU toolset. This is why Stallman is so
adamant about calling it GNU/Linux. It's not that he's trying to take credit
for Linux, it's that he's trying to give credit to the GNU project for making
Linux useful.

However, there's a lot of room for nuance here. For example, [12]Alpine Linux
is a Linux distribution that uses [13]musl libc instead of [14]glibc and [15]
busybox instead of GNU coreutils. It's still a Linux distribution, but it
doesn't use the GNU toolset. It's still a Linux distribution, but it's not GNU/
Linux.

Mara is hacker
<[16]Mara> Also, for the record you can build the Linux kernel with clang, but
that's a whole other can of worms. For one, GCC supports many more targets than
clang likely ever will, but in general there are some compromises you need to
make until clang implements some GCC-specific compiler extensions a bit better.
Google, Facebook, and a few other companies do run LLVM compiled kernels in
production though, so it's probably closer to viable than you think. Especially
if you use ChromeOS or Android.

So, what is a Linux distribution? It's a collection of tools that make Linux
useful. It's a collection of tools that make Linux into a complete system. It's
not a "flavor" of Linux (though this conceptually can exist with alternative
kernels like the Zen kernel patchset), it's a system that just so happens to
make Linux useful.

As a counter-argument, consider the reason why Linux runs on more devices
worldwide than there are people: [17]Android. Android does use the Linux
kernel, but it doesn't use any GNU tools in the stack at all. You can't take
programs that are compiled against other Linux distributions and run them on
Android. You can't take programs that are compiled against Android and run them
on other Linux distributions.

Aoi is wut
<[18]Aoi> Wait, so does this mean Android's not a Linux distribution? What is
it then?

I'm going to argue that Android is not a Linux distribution unto itself.
Android is a Linux implementation. It uses the Linux kernel, but that's where
the similarities with the rest of the ecosystem end. Android is its own little
world where there's just enough system tools to get the system running, but
once you get into the UI, it's a completely different world. It's a completely
different ecosystem. It's a completely different operating system.

Aoi is wut
<[19]Aoi> So what's the difference between a Linux distribution and a Linux
implementation?
Cadey is enby
<[20]Cadey> It's a bit of a fuzzy line, but I'd say that a Linux distribution
is a collection of discrete tools that make Linux useful, and a Linux
implementation is a cohesive collection of bespoke tools that make Linux into a
complete system. Really, you could argue that if it has /bin/sh, it's a Linux
distribution.

gokrazy

[21]gokrazy is a Linux implementation that I've used off and on for a few years
now. It's a very interesting project because everything on the system is
written in Go save the kernel. The init process is in Go (and even listens over
HTTP to handle updates!), every userland process is written in Go, and even the
core system services are written in Go.

Out of the box a gokrazy install comes with these basic tools:

  • The init process that is mandated to be the parent of all userland
    processes by the Linux kernel.
  • A [22]DHCP client that automatically configures the network interface.
  • A [23]NTP client that automatically sets the system clock.
  • A little tool to save randomness from the kernel to a file so that it can
    be used to seed the random number generator on boot (because the Raspberry
    Pi doesn't have a robust hardware random number generator)

That's it. Everything else from the web UI to A/B update logic is written in
Go. It boots in literal seconds, uses an insanely small amount of RAM out of
the box, and runs with nearly zero overhead. When you configure your gokrazy
install to run additional software, you do so by adding the Go command path to
a configuration file and then updating to trigger a reboot into the new
version.

Here's an example of what my gokrazy virtual machine's file tree looks like:

/ # tree etc gokrazy user
etc
├── breakglass.authorized_keys
├── gokr-pw.txt
├── gokrazy
│   └── sbom.json
├── hostname
├── hosts
├── http-port.txt
├── https-port.txt
├── localtime
├── machine-id
├── resolv.conf -> /tmp/resolv.conf
└── ssl
    └── ca-bundle.pem

gokrazy
├── dhcp
├── heartbeat
├── init
├── ntp
└── randomd

user
├── breakglass
├── fbstatus
├── qemu-guest-kragent
├── serial-busybox
├── tailscale
├── tailscaled
└── waifud-gok-agent

That is the entire system. It's all stripped down to these few programs,
configuration files, and one symlink for DNS resolution. This is a very minimal
system, and it's all you need to run statically linked Go programs. It's very
easy to deploy your own services to it too. It's probably the easiest platform
I know of that lets you just deploy a Go binary and have it run as a service,
automatically restarting when it crashes.

The tooling

When I used gokrazy back in the day, you had to use a command line called
gokr-packer that you passed a bunch of command line flags to with information
about all the Go programs you wanted to run on the machine, configuration for
those programs, and any other meta-information like where the update tool
should push the image to. It was a bit of a pain to use, but it worked.
Recently the [24]gok tool was added to the project, and this has been
revolutionary when it comes to using and administrating gokrazy installs.

Essentially, gok is a wrapper around the existing gokr-packer logic with a JSON
file to store your configuration details. It's a lot easier to use, understand,
and automate. You don't have to remember command line flags or maintain
unwieldy scripts. You just edit a JSON file and push updates with gok update.
It's amazingly simple.

Setting up a gokrazy machine

As an example, I'm going to show you how to install a bunch of tailnet addons
to a gokrazy machine. I'm also going to assume that you don't have a gokrazy
install set up yet, so we'll need to install it. To do this, we'll need to do a
few simple things:

  • Install the gok tool.
  • Create your gok configuration.
  • Install Tailscale on the machine.
  • Create your "seed" image with gok overwrite.
  • Boot it on your Raspberry Pi or VM.
  • Push any updates to the image to the machine with gok update.

First, let's install the gok tool. In order to do this, you need to have the 
[25]Go toolchain installed. Once you have that, you can run go install to
install the gok tool:

go install github.com/gokrazy/tools/cmd/gok@main

Mara is hacker
<[26]Mara> You may want to ensure that ~/go/bin is in your $PATH variable so
that you can run it by the name gok instead of ~/go/bin/gok.

Next, create a new gokrazy configuration with gok new:

gok new -i casa

This will create a configuration named casa (cf: Spanish for "house") in ~/
gokrazy/casa. This is where all of your configuration files will live. You can
edit the configuration file with gok edit:

gok edit -i casa

If you are making a virtual machine

If you are making a virtual machine, you will need to override the kernel and
firmware packages. You can do this by adding the following to your
configuration file:

{
  // ...
  "KernelPackage": "github.com/rtr7/kernel",
  "FirmwarePackage": "github.com/rtr7/kernel",
  // ...
}

You will need to prefix the gok overwrite and gok update commands with GOARCH=
amd64 to ensure that Go builds x86_64 binaries instead of ARM binaries:

GOARCH=amd64 gok update -i casa

If you don't do this, you will get arm64 binaries being built. This may require
manual recovery of your virtual machine.

Let's make our lives easier by installing [27]Tailscale on the machine. By
default, gokrazy will announce its hostname over DHCP, which usually makes most
consumer routers pick it up and then lets you ping it by name. When you have 
[28]MagicDNS enabled, Tailscale can take over this logic and prevent you from
accessing the machine by name.

However, Tailscale is written in Go and doesn't require any of the services
that most Linux distributions provide in order to function. It's a perfect fit
for gokrazy. You can install it with gok add:

gok add tailscale.com/cmd/tailscaled
gok add tailscale.com/cmd/tailscale

And be sure to add the mkfs service to create a persistent partition on /perm:

gok add github.com/gokrazy/mkfs

Next, fetch an [29]auth key from [30]the admin console and make sure you check
that it's reusable. Then, add the following to your configuration file under
the PackageConfig block:

{
    // ...
    "PackageConfig": {
        // ...
        "tailscale.com/cmd/tailscale": {
            "CommandLineFlags": [
                "up",
                 // paste your key here!
                "--authkey=tskey-auth-hunter2-hunter2hunter2hunter2"
            ]
        },
        // ...
    }
    // ...
}

Mara is hacker
<[31]Mara> You can pass any other [32]tailscale up flags you want here, such as
--advertise-exit-node if you want to use your gokrazy machine as an [33]exit
node.

This will make your machine automatically connect to Tailscale on boot.

Next, we need to create our "seed" image with gok overwrite. First, figure out
what the device node for your SD card is. On Linux, you can do this with lsblk:

lsblk

And then look for the one that has the same size as your SD card. In my case,
it's /dev/sdd. Once you have that, you can run gok overwrite:

gok overwrite --full /dev/sdd

However if you want to write the image to a file (such as if you are doing mass
distribution or making a VM image), you need to use gok overwrite with a file
instead of a device node. This will create a 16 GB image:

gok overwrite -i casa --full gokrazy.img --target_storage_bytes 17179869184

Once you have your image, you can write it to your SD card with dd (or [34]
balenaEtcher) or import it into your virtual machine hypervisor of choice.

Once you have your image written to your SD card, you can boot it on your
Raspberry Pi or VM.

Aoi is wut
<[35]Aoi> Wait, so how do I log in with a shell?
Cadey is enby
<[36]Cadey> You don't. gokrazy doesn't have a login prompt. It's a single-user
system. There is [37]breakglass as a tool of last resort to modify things, but
you only have a very minimal subset of busybox to work with, so it should be
avoided if at all possible.

Once you have your machine booted and it responds to pings over Tailscale, you
can open its HTTP interface in your browser. If you called your machine casa,
you can open it at [38]http://casa. It will prompt you for a username and
password. Your username is gokrazy, and the password is near the top of your
config.json file. When you log in, you'll see a screen like this:

[39][gokrazy-ui]

This is the gokrazy web UI. It lets you see the status of your machine and any
logs that are being generated by your applications. You can also start, stop,
and restart any of your applications from here. It's a very simple UI, but it's
fantastic for debugging and monitoring.

Tailnet addons

Now that we have a Gokrazy system up and running, let's add some programs to
it! I'm going to list a couple tailnet addons that give your tailnet
superpowers. These are all written in Go, so they're a perfect fit for gokrazy.

Today I'm going to show you how to install these tools into your tailnet:

  • [40]golink - a URL shortener at http://go
  • [41]tmemes - an internal meme generator you can host at http://memegen
  • [42]tclip - a pastebin you can host at http://paste

These tools help you augment your tailnet by giving you tools that will make
you and your team's life a lot easier. A URL shortener helps you link to
complicated Google Docs URLs. A meme generator gives you a new innovative way
to let off steam. A pastebin lets you share text with your team without having
to worry about the service you're using going offline due to no fault of your
own.

golink

To install golink, we need to add the golink binary to the configuration. You
can do this with gok add:

gok add github.com/tailscale/golink/cmd/golink

Then configure it with gok edit:

{
    // ...
    "PackageConfig": {
        // ...
        "github.com/tailscale/golink/cmd/golink": {
            "CommandLineFlags": [
                "--sqlitedb=/perm/home/golink/data.db"
            ],
            "Environment": [
                // the same one from before
                "TS_AUTHKEY=tskey-auth-hunter2-hunter2hunter2hunter2"
            ],
            // don't start the service until NTP catches up
            "WaitForClock": true
        },
        // ...
    }
    // ...
}

And finally push it with gok update:

gok update -i casa

It'll build the image, push it out over Tailscale, trigger a reboot, and be
back up in the span of a minute. Once it's back up, you can open the web UI
again and see the status of your golink instance at [43]http://casa/status?path
=%2fuser%2fgolink:

[44][golink]

And then you can start using short URLs at [45]http://go:

[46][golink-ui]

And that's it! You now have a super minimal VM running small programs that let
you do useful things to you. You can add more programs to your configuration
file and push them with gok update to add more functionality to your machine.
You can even add your own programs to the configuration file and push them to
your machine. It's a very simple system, but it's very powerful.

tmemes

Google is infamous for having an internal service named [47]memegen. This
allows Googlers to make internal-facing memes about the slings and arrows that
impact them as highly paid programmers. This is an internal service inside
Google that has a lot of serious investment of time and energy to make it the
best possible experience it can be. It's to the point that reportedly people
can keep up with how an all-hands meeting is going by the tone of the sarcastic
memes that are being posted to memegen.

The main reason this is run inside Google is to avoid information leaking via
memes. Yes, this is an actual threat model.

Thanks to the magic of Tailscale, you can make your own private memegen using 
[48]tmemes. tmemes is a tailnet addon that lets you post image macro templates
and layer wisdom over it in the form of text.

Here's an example meme:

[49][society-if]

To add tmemes to your gokrazy machine, you can use gok add:

gok add github.com/tailscale/tmemes/tmemes

Then open your config with gok edit and add the following to your PackageConfig
block:

{
    // ...
    "PackageConfig": {
        // ...
        "github.com/tailscale/tmemes/tmemes": {
            "Environment": [
                "TS_AUTHKEY=tskey-auth-hunter2-hunter2hunter2hunter2"
            ],
            "CommandLineFlags": [
                // change this to your desired hostname
                "--hostname=memegen",
                // change this to your username on Tailscale
                "--admin=Xe@github",
                "--store=/perm/home/tmemes"
            ],
            "WaitForClock": true
        },
        // ...
    },
    // ...
}

And then push it with gok update:

gok update -i casa

Then you can head to [50]http://memegen and upload a template to make your own
dank memes.

If you want to integrate your own tools with tmemes, you can check out the [51]
API documentation. This should help you do whatever it is you want with a meme
generator as a service.

tclip

Sometimes you just need a place to paste text and get a URL pointing to it. 
[52]tclip is a tool that you can add to your tailnet and get exactly that. It's
a very simple tool, but it's very useful. It's also written in Go, so it's a
perfect fit for gokrazy. [53]Their recent update to remove Cgo dependencies
makes it possible to run your tclip node on a gokrazy machine.

To add tclip to your gokrazy machine, you can use gok add:

gok add github.com/tailscale-dev/tclip/cmd/tclipd

Then open your config with gok edit and add the following to your PackageConfig
block:

{
    // ...
    "PackageConfig": {
        // ...
        "github.com/tailscale-dev/tclip/cmd/tclipd": {
            "CommandLineFlags": [
                "--data-location=/perm/home/tclip/"
            ],
            "WaitForClock": true,
            "Environment": [
                "TS_AUTHKEY=tskey-auth-hunter2-hunter2hunter2hunter2",
                "USE_FUNNEL=true" // Remove this if you don't want to use Funnel
            ]
        },
        // ...
    }
}

And then push it with gok update:

gok update -i casa

And then you can start using it by heading to [54]http://paste. Install the
command-line tool on your development workstation with go install:

go install github.com/tailscale-dev/tclip/cmd/tclip@latest

Here's an example tclip link if you want to see what it looks like in practice:
[55]interjection.c. It's a very simple tool, but it's very useful.

Conclusion

gokrazy is insanely cool. It's the easiest way to deploy Go services to your
homelab. It integrates seamlessly with Tailscale, and is something that I'm
very excited to see grow and mature. I'm very excited to see what the future
holds for gokrazy, and I'm very excited to see what people do with it.

I've seen signs that they're going to be adding an automatic update process,
and that has me very excited. I'm also excited to see what other services
people add to the gokrazy ecosystem. I'm hoping to add a few of my own in the
future, and I'm hoping to see what other people do with it.

Mara is hacker
<[56]Mara> Spoiler alert: [57]waifud support is coming soon to a homelab near
you.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Share

Facts and circumstances may have changed since publication. Please contact me
before jumping to conclusions if something seems wrong or unclear.

Tags: go, gokrazy, linux

Copyright 2012-2024 Xe Iaso (Christine Dodrill). Any and all opinions listed
here are my own and not representative of any of my employers, past, future,
and/or present.

Like what you see? Donate on [59]Patreon like [60]these awesome people!

Served by xesite v4 (/nix/store/
5pbrsn0khhga62ipyl043c9z22dlah4w-xesite_v4-20231122/bin/xesite) with site
version [61]71d942d2 , source code available [62]here.


References:

[1] https://xeiaso.net/
[2] https://xeiaso.net/blog
[3] https://xeiaso.net/contact
[4] https://xeiaso.net/resume
[5] https://xeiaso.net/talks
[6] https://xeiaso.net/vods
[7] https://xeiaso.net/signalboost
[8] https://xeiaso.net/characters#mara
[9] https://gcc.gnu.org/
[10] https://www.gnu.org/software/coreutils/coreutils.html
[11] https://www.gnu.org/software/libc/
[12] https://alpinelinux.org/
[13] https://musl.libc.org/
[14] https://www.gnu.org/software/libc/
[15] https://en.wikipedia.org/wiki/BusyBox
[16] https://xeiaso.net/characters#mara
[17] https://en.wikipedia.org/wiki/Android_(operating_system)
[18] https://xeiaso.net/characters#aoi
[19] https://xeiaso.net/characters#aoi
[20] https://xeiaso.net/characters#cadey
[21] https://gokrazy.org/
[22] https://en.wikipedia.org/wiki/Dynamic_Host_Configuration_Protocol
[23] https://en.wikipedia.org/wiki/Network_Time_Protocol
[24] https://gokrazy.org/quickstart/
[25] https://golang.org/doc/install
[26] https://xeiaso.net/characters#mara
[27] https://tailscale.com/
[28] https://tailscale.com/kb/1081/magicdns/
[29] https://tailscale.com/kb/1085/auth-keys/
[30] https://login.tailscale.com/admin/settings/keys
[31] https://xeiaso.net/characters#mara
[32] https://tailscale.com/kb/1080/cli/#up
[33] https://tailscale.com/kb/1103/exit-nodes/?q=exit%20node
[34] https://etcher.balena.io/
[35] https://xeiaso.net/characters#aoi
[36] https://xeiaso.net/characters#cadey
[37] https://github.com/gokrazy/breakglass
[38] http://casa/
[39] https://cdn.xeiaso.net/file/christine-static/blog/2023/gokrazy/gokrazy-ui.jpg
[40] https://github.com/tailscale/golink
[41] https://github.com/tailscale/tmemes
[42] https://github.com/tailscale-dev/tclip
[43] http://casa/status?path=%2fuser%2fgolink
[44] https://cdn.xeiaso.net/file/christine-static/blog/2023/gokrazy/golink.jpg
[45] http://go/
[46] https://cdn.xeiaso.net/file/christine-static/blog/2023/gokrazy/golink-ui.jpg
[47] https://www.buzzfeednews.com/article/reyhan/inside-googles-internal-meme-generator
[48] https://github.com/tailscale/tmemes
[49] https://cdn.xeiaso.net/file/christine-static/blog/2023/gokrazy/society-if-gokrazy.jpg
[50] http://memegen/
[51] https://github.com/tailscale/tmemes/blob/main/docs/api.md
[52] https://tailscale.dev/blog/tclip
[53] https://tailscale.dev/blog/tclip-updates-092023
[54] http://paste/
[55] https://paste.shark-harmonic.ts.net/paste/696b9b02-90ac-4adc-a33d-d749bb6f460f
[56] https://xeiaso.net/characters#mara
[57] https://github.com/Xe/waifud-gok-agent
[59] https://patreon.com/cadey
[60] https://xeiaso.net/patrons
[61] https://github.com/Xe/site/commit/71d942d2664615d081d14037ad9930848f6a72b4
[62] https://github.com/Xe/site
