summaryrefslogtreecommitdiff
path: root/mod/web/blog
diff options
context:
space:
mode:
authorKleidi Bujari <mail@4kb.net>2024-12-17 23:03:27 -0500
committerKleidi Bujari <mail@4kb.net>2024-12-17 23:03:27 -0500
commiteb13a9a552f9e50a66faba985521ba8e3483c447 (patch)
tree209193a91b0db20fb1bb30007e72e321f0293696 /mod/web/blog
parentf5ed731abbc38d765333b005b9815a5f6d3e59c2 (diff)
downloaddepot-eb13a9a552f9e50a66faba985521ba8e3483c447.tar.gz
depot-eb13a9a552f9e50a66faba985521ba8e3483c447.tar.bz2
depot-eb13a9a552f9e50a66faba985521ba8e3483c447.zip
Push configuration out of flake
Projects are not necessarily all exported by the system flake, and should freely support other derivations. The new "mod" directory will be used for most projects, and it can be imported by machine configurations. That said, the tooling around flakes is pretty good and will still be used until the dependency is lessened.
Diffstat (limited to 'mod/web/blog')
-rw-r--r--mod/web/blog/.gitignore2
-rw-r--r--mod/web/blog/config.toml8
-rw-r--r--mod/web/blog/content/log/_index.md7
-rw-r--r--mod/web/blog/content/log/deterministic-hostnames.md80
-rw-r--r--mod/web/blog/content/log/nohup.md26
-rw-r--r--mod/web/blog/content/posts/_index.md8
-rw-r--r--mod/web/blog/content/posts/stateless-compute-networks.md41
-rw-r--r--mod/web/blog/default.nix15
-rw-r--r--mod/web/blog/static/avatar.pngbin0 -> 1800 bytes
-rw-r--r--mod/web/blog/static/style.css85
-rw-r--r--mod/web/blog/templates/404.html9
-rw-r--r--mod/web/blog/templates/base.html26
-rw-r--r--mod/web/blog/templates/blog.html22
-rw-r--r--mod/web/blog/templates/index.html44
-rw-r--r--mod/web/blog/templates/post.html12
15 files changed, 385 insertions, 0 deletions
diff --git a/mod/web/blog/.gitignore b/mod/web/blog/.gitignore
new file mode 100644
index 0000000..decc3f8
--- /dev/null
+++ b/mod/web/blog/.gitignore
@@ -0,0 +1,2 @@
+public/
+nohup.out
diff --git a/mod/web/blog/config.toml b/mod/web/blog/config.toml
new file mode 100644
index 0000000..69c389b
--- /dev/null
+++ b/mod/web/blog/config.toml
@@ -0,0 +1,8 @@
+base_url = "https://4kb.net"
+build_search_index = false
+compile_sass = false
+generate_feeds = true
+minify_html = true
+
+[markdown]
+highlight_code = false
diff --git a/mod/web/blog/content/log/_index.md b/mod/web/blog/content/log/_index.md
new file mode 100644
index 0000000..10f2311
--- /dev/null
+++ b/mod/web/blog/content/log/_index.md
@@ -0,0 +1,7 @@
++++
+title = "Log"
+sort_by = "date"
+template = "blog.html"
+page_template = "post.html"
+description = "notes too short to post"
++++
diff --git a/mod/web/blog/content/log/deterministic-hostnames.md b/mod/web/blog/content/log/deterministic-hostnames.md
new file mode 100644
index 0000000..43dc7d8
--- /dev/null
+++ b/mod/web/blog/content/log/deterministic-hostnames.md
@@ -0,0 +1,80 @@
+---
+title: "Deterministic and unique network hostnames"
+date: "2024-11-10"
+---
+
+As part of building out a Kubernetes cluster, I wanted to build and distribute a
+single OS image to create stateless worker nodes. Using network booting, and
+some clever tricks to differentiate nodes, we can create a scaleable and
+efficient farm of workers for a cluster that don't even need disks.
+
+The idea came from a plan to build a cluster using the
+[compute blade](https://computeblade.com/), and a few Raspberry Pi SBCs I
+already own. Running the cluster from an SD card is not recommended due to the
+not-so-great reliability of the flash used by most manufacturers, so I wanted to
+try PXE booting each Pi to save money rather than purchasing an SSD for each
+one. The compute blades do support an NVMe disk, but I plan to use those for a
+storage cluster later, so they need to remain empty.
+
+## Base image
+
+Alpine Linux has been my preferred server OS for a long time. It provides a very
+lightweight base system, and bundles an excellent bootstrapping system,
+[apkovl](https://wiki.alpinelinux.org/wiki/Alpine_local_backup), that allows the
+user to save a set of customisations to an system as an overlay to a stock
+Alpine live image. In other words, we can create our image once, save the
+changes as an `apkovl.tar.gz` file, and apply the same changes to a base system
+on boot. This file can even be provided as a
+[kernel parameter](https://wiki.alpinelinux.org/wiki/PXE_boot#Guide_to_options)
+and will be fetched from a remote webserver automatically!
+
+Since the image and configuration will be shipped to the node via the network,
+an added benefit of using Alpine is its tiny space consumption. I'm not using
+enough nodes for this to really matter, but it's a cool optimization regardless.
+
+## Differentiating the nodes
+
+One of the main goals of this project is that there should be no persistent
+storage required outside the boot image itself. Since every node will download
+and generate the same root file-system on startup, the first problem that arises
+is how the nodes will identify themselves both on the network and the cluster,
+given that it's not possible to name them ahead of time. In other words, any
+given node has to generate a unique hostname that won't collide with other
+workers, and that will be the same each time that node boots.
+
+Since these nodes will not have a predefined name, we have to rely on
+characteristics of the hardware to differentiate each one. The hardware MAC
+address is perfect for this, since it's unique to to each node and will not be
+wiped away after the node reboots. On a system like Linux that exposes its
+hardware through a _sysfs_, we can find a file containing the address at
+`/sys/class/net/eth0/address`. I don't really like the idea of attaching the
+literal MAC address of the node to its network hostname, since it's a security
+risk, and a bit too verbose. Instead, we can transform it into something safer
+using a `sha1sum`, which is already present on our Alpine base system:
+
+```console
+sha1sum /sys/class/net/eth0/address | head -c 6 | awk '{print "worker-" $0}'
+```
+
+### Applying the new name
+
+Ideally, the node should apply its generated hostname before reaching out for an
+address over DHCP or joining the cluster. We can make sure it happens before any
+traffic is sent out by adding a `pre-up` command to the right interface in
+`/etc/network/interfaces`:
+
+```
+
+...
+
+auto eth0
+iface eth0 inet dhcp
+ pre-up sha1sum /sys/class/net/eth0/address | head -c 6 | awk '{print "worker-" $0}' > /etc/hostname
+
+...
+```
+
+The VM I tested with looks outputs `worker-e2fae8`. Pretty clean result, and if
+you want to know the physical node that maps to each hostname, you can take note
+of the MAC address beforehand and generate the same hash on another computer to
+match them up.
diff --git a/mod/web/blog/content/log/nohup.md b/mod/web/blog/content/log/nohup.md
new file mode 100644
index 0000000..64f7983
--- /dev/null
+++ b/mod/web/blog/content/log/nohup.md
@@ -0,0 +1,26 @@
+---
+title: "Spawning background processes"
+date: "2024-11-17"
+---
+
+Working in a terminal,
+I often pair my editor with a background process watching files.
+Before reaching for terminal multiplexers,
+see if you can get away with simple tty job control.
+Spawn the background process,
+still attached to the terminal instance:
+
+```
+program args &
+```
+
+Also redirect its output to a file,
+for when the process writes to the tty from the background:
+
+```
+nohup program args &
+```
+
+Extra reading:
+
+- <https://jvns.ca/blog/2024/07/03/reasons-to-use-job-control/>
diff --git a/mod/web/blog/content/posts/_index.md b/mod/web/blog/content/posts/_index.md
new file mode 100644
index 0000000..1f4024a
--- /dev/null
+++ b/mod/web/blog/content/posts/_index.md
@@ -0,0 +1,8 @@
++++
+title = "Posts"
+sort_by = "date"
+template = "blog.html"
+page_template = "post.html"
++++
+
+Longer form writing.
diff --git a/mod/web/blog/content/posts/stateless-compute-networks.md b/mod/web/blog/content/posts/stateless-compute-networks.md
new file mode 100644
index 0000000..bac9e5d
--- /dev/null
+++ b/mod/web/blog/content/posts/stateless-compute-networks.md
@@ -0,0 +1,41 @@
+---
+title: "On stateless compute networks"
+date: "2024-11-10"
+draft: true
+---
+
+On the topic of distributed systems and clustering,
+I am quite invested in the idea of compute nodes that rely entirely on the network for configuration.
+Arbitrary nodes can join a pre-existing cluster,
+offering their CPU time and memory for computation without relying on any pre-existing configuration on the node itself.
+In other words, any computer could pick up work,
+only needing power and a network connection to the cluster.
+
+Perhaps this eventually leads into a "self-healing" cluster where only one node is manually bootstrapped,
+which then serves a _configuration endpoint_ for other stateless nodes to reach out to for their instructions,
+which they will then also serve once they are themselves ready.
+
+Early revisions of these notes mention Kubernetes,
+but I am also trying to achieve similar results with NixOS on a custom project.
+In any case, these are my ever-updating notes towards a general implementation of a stateless distributed systems architecture.
+
+## Self healing cluster
+
+Assuming control of an external DHCP server,
+a self healing Kubernetes cluster would be feasible,
+with the PXE boot artifacts supplied by the cluster itself.
+That is, as long as one node is running the pod hosting the artifacts on a given endpoint,
+other nodes can boot those artifacts and join the cluster,
+thereby being able to host the artifacts as well.
+
+## Configuration endpoint
+
+The nodes shouldn't require a disk installed to be able to join the network.
+Rather, the lofty goal of zero-configuration compute nodes passes the job of node initialization to the supporting network.
+This is accomplished with PXE boot instructions supplied over DHCP.
+
+I delegate the following tasks to a single node in the subnet:
+
+- Gateway: Optional outbound connections if required
+- DHCP server: Cluster IPAM
+- TFTP and HTTP server: Serves iPXE firmware and kernel/initrd artifacts
diff --git a/mod/web/blog/default.nix b/mod/web/blog/default.nix
new file mode 100644
index 0000000..5d76e7a
--- /dev/null
+++ b/mod/web/blog/default.nix
@@ -0,0 +1,15 @@
+{ pkgs, ... }: pkgs.stdenvNoCC.mkDerivation {
+ pname = "4kb.net";
+ version = "1.0";
+ src = ./.;
+
+ nativeBuildInputs = with pkgs; [
+ zola
+ ];
+
+ buildPhase = "zola build";
+ installPhase = ''
+ mkdir -p $out
+ cp -r public/* $out/
+ '';
+}
diff --git a/mod/web/blog/static/avatar.png b/mod/web/blog/static/avatar.png
new file mode 100644
index 0000000..91357dc
--- /dev/null
+++ b/mod/web/blog/static/avatar.png
Binary files differ
diff --git a/mod/web/blog/static/style.css b/mod/web/blog/static/style.css
new file mode 100644
index 0000000..fdfd692
--- /dev/null
+++ b/mod/web/blog/static/style.css
@@ -0,0 +1,85 @@
+:root {
+ --bg: #181616;
+ --fg: #c5c9c5;
+ --link: #76946a;
+ --linkhover: #98bb6c;
+}
+
+html {
+ scroll-behavior: smooth;
+ color-scheme: dark;
+}
+
+body {
+ font-family: serif;
+ padding: 0 0.75em;
+ max-width: 42em;
+ margin: auto;
+ background: var(--bg);
+ color: var(--fg);
+}
+
+p {
+ line-height: 1.3em;
+}
+
+th,
+td {
+ padding: 0.2em 0.4em;
+ border: thin solid;
+}
+
+table {
+ border: thin solid;
+ border-collapse: collapse;
+}
+
+article {
+ padding-top: 1em;
+}
+
+footer {
+ margin: 3em auto;
+ text-align: center;
+}
+
+a,
+a:link {
+ color: var(--link);
+}
+
+a:hover {
+ color: var(--linkhover);
+}
+
+a[href^="http"]:where(:not([href*="4kb.net/"]))::after {
+ content: " \21e2";
+}
+
+article img {
+ display: block;
+ margin: 0 auto;
+ max-width: 80%;
+ height: auto;
+ object-fit: contain;
+}
+
+nav {
+ margin-top: 1em;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ font-family: monospace;
+ font-weight: bold;
+ gap: 8px;
+}
+
+nav div {
+ display: flex;
+ gap: 10px;
+}
+
+pre {
+ padding: 1em;
+ overflow-x: scroll;
+}
diff --git a/mod/web/blog/templates/404.html b/mod/web/blog/templates/404.html
new file mode 100644
index 0000000..a4669df
--- /dev/null
+++ b/mod/web/blog/templates/404.html
@@ -0,0 +1,9 @@
+{% extends "base.html" %}
+
+{% block content %}
+<head>
+ <title>Error 404!</title>
+</head>
+
+404!
+{% endblock content %}
diff --git a/mod/web/blog/templates/base.html b/mod/web/blog/templates/base.html
new file mode 100644
index 0000000..6757d02
--- /dev/null
+++ b/mod/web/blog/templates/base.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <link href="/style.css" rel="stylesheet" />
+ <link rel="preload" href="style.css" as="style" />
+ <title>{% block title %}4kb.net{% endblock title %}</title>
+ </head>
+ <body>
+ <nav>
+ <span>4kb.net</span>
+ <div>
+ <a href="/">Home</a>
+ <a href="/posts">Posts</a>
+ <a href="/log">Log</a>
+ </div>
+ </nav>
+ <hr />
+ <main>{% block content %} {% endblock %}</main>
+ <footer>
+ <hr />
+ <span>&#x00a9; 2024 Kleidi Bujari</span>
+ </footer>
+ </body>
+</html>
diff --git a/mod/web/blog/templates/blog.html b/mod/web/blog/templates/blog.html
new file mode 100644
index 0000000..6c92a07
--- /dev/null
+++ b/mod/web/blog/templates/blog.html
@@ -0,0 +1,22 @@
+{% extends "base.html" %}
+
+{% block content %}
+
+<head>
+ <title>4kb.net</title>
+</head>
+
+<h2>{{ section.title }}</h2>
+
+{{ section.content | safe }}
+
+<ul>
+ {% for page in section.pages %}
+ <li>
+ <span>[{{ page.date }}]</span>
+ <a href="{{ page.permalink | safe }}">{{ page.title }}</a>
+ </li>
+ {% endfor %}
+</ul>
+
+{% endblock content %}
diff --git a/mod/web/blog/templates/index.html b/mod/web/blog/templates/index.html
new file mode 100644
index 0000000..74e9a0f
--- /dev/null
+++ b/mod/web/blog/templates/index.html
@@ -0,0 +1,44 @@
+{% extends "base.html" %} {% block content %}
+
+<h2>welcome!</h2>
+
+<p>
+ I'm a computer engineering undergrad studying in Toronto. This is my personal
+ site where I sometimes publish long form posts on topics I'm interested in, or
+ short "logs" for information I want to remember.
+</p>
+
+<span>Find me on:</span>
+<ul>
+ <li>email: <a href="mailto:mail@4kb.net">mail@4kb.net</a></li>
+ <li>github: <a href="https://github.com/kbujari">kbujari</a></li>
+</ul>
+
+<!--<h3>Recent Posts</h3>-->
+<!---->
+<!--{% set section = get_section(path="posts/_index.md") %}-->
+<!---->
+<!--<ul>-->
+<!-- {% for page in section.pages | slice(end=4) %}-->
+<!-- <li>-->
+<!-- <span>[{{ page.date }}]</span>-->
+<!-- <a href="{{ page.permalink | safe }}">{{ page.title }}</a>-->
+<!-- </li>-->
+<!-- {% endfor %}-->
+<!--</ul>-->
+
+<h3>Recent Logs</h3>
+
+{% set section = get_section(path="log/_index.md") %}
+
+<ul>
+ {% for page in section.pages | slice(end=4) %}
+ <li>
+ <span>[{{ page.date }}]</span>
+ <a href="{{ page.permalink | safe }}">{{ page.title }}</a>
+ </li>
+ {% endfor %}
+</ul>
+
+
+{% endblock content %}
diff --git a/mod/web/blog/templates/post.html b/mod/web/blog/templates/post.html
new file mode 100644
index 0000000..b1981e4
--- /dev/null
+++ b/mod/web/blog/templates/post.html
@@ -0,0 +1,12 @@
+{% extends "base.html" %}
+{% block title%}
+ {{ page.title }} - {{ super() }}
+{% endblock title %}
+
+{% block content %}
+
+<h1 style="text-align: center; margin-top: 1em;">{{ page.title }}</h1>
+<article>
+ {{ page.content | safe }}
+</article>
+{% endblock content %}