What Enough Technology Means


Introduction


It's traditional (at least for me) to spend my first post on a new blogging platform talking about, well, the program I wrote to be my new blogging platform. Right now it's very barebones, soon it's going to be a complete website. The platform itself is trivial, I'm going to include every line of source (as of right now,) in the post itself as well as talk about the infrastructure that makes it all run and it shouldn't take more than a few minutes, but first I'm going to introduce myself.

I'm Danny; I recently quit my job in order to try and make it on my own after nearly 8 years working on an Analytics platform which processed a lot of data. Not Google amounts, but a lot. If you live in an industrialized country (and a lot of developing ones,) your data has passed through software I worked on even though you almost certainly haven't heard of the company. Now I'm on my own and want to spread my skills around more. Is this blog an ad? Yeah, kinda I guess. If you need a hand with wrangling AWS, communicating with Kafka, fighting Flink, feel free to reach out. There's going to be a contact form on here when the business proper is set up. If that was all greek to you and you just need someone to help figure out what your business needs as far as the software, feel free to reach out too. I'll at least try to get you pointed in the right direction.

My core point on this blog and in my practice is that solutions and their scale have to be scoped to match their business. I could have written this blog in a high performance language like Rust. I could've deployed it onto a cloud platform load balanced, automatically scaled, running on top of a serverless database with pay-per-use functions. Quite frankly: I'm one person. We'll get into the details later, but that's a lot of lock-in and a lot of extra work for an amount of traffic that I'm not going to get in my wildest dreams. There are people who need that scale: if your data is measured in petabytes and your events in millions per minute those make sense as a business. I want to work with you to find out where you are, where you're going, and where on the spectrum is going to fit best going forwards without causing problems if you do find yourself bigger than you'd expected. With that out of the way, let's look at how the website is made as a case study, shall we?

Analysis And Code

The website is a professional blog for a professional software developer familiar with development and administration. It receives approximately 0 traffic as it's literally just started, but ideally it could scale up to hundreds or thousands of legitimate hits a day. If it's getting hits in the millions then something has gone horribly right and its owner won't need to worry about analyzing blog software anymore. Because this is 2026 that level of traffic may be possible due to an AI Distributed Denial of Service attack (aka crawling,) so it has to be considered if nothing else, though shedding that traffic is acceptable. Features such as comments frankly just become cesspools of spam and open up GDPR concerns, so there's no need for those. There are, however, people who would prefer to use feed readers rather than go to the website and we should support them. Additionally just as a best practice the website should conform to all reasonable accessibility requirements, allow linking to single posts, and provide a good experience in general to its visitors.

There's plenty of fantastic software out there for this sort of thing. You may have heard of Wordpress. If you're more technical maybe Hugo or Jekyll spring to mind. The latter are what's called static site generators and are (in my opinion) the correct answer here as a category. Rather than generating the page for the website on every visit, these site generators render the page once (because it's the same for every visitor,) then all the server has to do is read the file from the hard drive. Nothing in our needs requires dynamic behavior so we have the option here. It's cheap, it's fast, it's very easy to extend as needed, and there's even off-the-shelf software to do it. In this case I wrote my own, because I needed something to talk about. Accessibility, in a shocking number of cases, pretty much falls out for free if you write your web pages as web pages with meaningful HTML and accessible CSS.

If you want it, here's the complete source to the static site generator as it exists right now. If you're not interested in code, you don't need to understand it. I'm not going to go through line by line or anything.


#!/usr/bin/env rc
# RC supports multi-line strings rather than hacking them in with heredocs.
# I chose %VARIABLE% because it doesn't conflict with existing shell syntax.
# Some of these could be made defined more locally, but if I change them I'd
# like them to be right at the top.

domain=justenough.technology
blogname='Just Enough Technology'

htmlheader='<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8"/>
        <title>%TITLE%</title>
	<link type="text/css" rel="stylesheet" href="/style.css">
        <link rel="alternate" type="application/rss+xml" title=RSS href="feed.rss">
    </head>
    <body>
'

htmltail='</body>
</html>
'

rssheader='<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
	<channel>
		<title>' ^ $"blogname ^ '</title>
		<link>https://' ^ $"domain ^ '/</link>
		<description>' ^ $"blogname ^ '</description>
'

rsstail='	</channel>
</rss>
'

posts = `{ls posts/*.in | sort -r}

fn getid {
	cut -d '/' -f 2 | cut -d '.' -f 1
}

fn genhead {
	sed 's/%TITLE%/' ^ $1 ^ '/'
}

fn genpage {
	page = $1;
	id = `{echo $page | getid}
	title = `{head -n 1 < $page}
	rest = `{tail -n +2 < $page}
	echo '<div class="post"> <article id="' ^ $id ^ '" class="content">'
	echo '<a href="/' ^ $id ^ '.html"><h1>' ^ $"title ^ '</h1></a><hr/>'
	echo $rest
	echo '</article></div>'
}

fn genentry {
	# Duplicated from genpage but there's no nice way of doing this without weirding scope.
	page = $1;
	id = `{echo $page | getid}
	title = `{head -n 1 < $page}
	rest = `{tail -n +2 < $page}
 	echo '	<item>
		<pubDate>' ^ $"id ^ '</pubDate>
		<title><![CDATA[' ^ $"title ^ ']]></title>
		<description><![CDATA[' ^ $"rest ^ ' ]]></description>
	</item>'
}

cp style.css out/

# Generate each post.
for (i in $posts) {
	postid = `{echo $i | getid}
	outfile = 'out/' ^ $postid ^ '.html'
	title=`{head -n 1 < $i}
 	echo $htmlheader | genhead $"blogname ^ ' - ' ^ $"title > $outfile
	genpage $"i >> $outfile
	echo $htmltail >> $outfile
}

# Now make the feeds (index.html and the rss feed)
echo $htmlheader | genhead $"blogname ^ ' - Home' > out/index.html
echo $rssheader > out/feed.rss

for (i in $posts) {
	genpage $"i >> out/index.html
	genentry $"i >> out/feed.rss
}

echo $htmltail >> out/index.html
echo $rsstail >> out/feed.rss

That's it. Fewer than 100 lines. It is written in an obscure shell, but we'll get to that. This checks every box I have for this blog software right now. This is exactly what I need for now and all the growth I project in the forseeable future, and it's (in my opinion,) beautiful for that. The input files are one line at the top of a plain-text title, then the rest is just HTML.

Of course having the code is just a small part of the story. I also have to make this readable. Don't worry, we're getting to the whole stack. I want to address some of the thought process which went into why I came to this exact solution, though. I could've used Jekyll. I didn't have it installed and I'd have less to talk about.

Make is a natural choice, what we're doing is generating output files (html) which is directly derived from input files (my posts.) There were two problems there, in my estimation: first most of the nice features of make break when you're spread across directories. It has a hard time inferring that out/5.html is actually supposed to correspond to posts/5.in so I would need to define a rule for every post. Second is that as defined by POSIX, make is remarkably incapable. One of the biggest blockers to writing portable Makefiles for native code is that linker flags are managed by something called pkg-config, which has to be called as a program. GNU Make and BSD Make, the two main dialects, have mutually incompatible syntaxes for assigning the output of a shell command to a variable. Additionally in order to create the index and RSS feed, I'd need a special case which is semantically awkward and, I think, impossible portably.

I could've used Go or Rust, but most of what this program is doing is opening, closing, and reading verbatim from small files. What would that performance buy me? Maybe I could measure it in seconds when I rack up thousands of posts.

I could've used Perl, since it's very good at text manipulation. In fact, this codebase is inspired by Ube, a set of Perl scripts which I later rewrote into rc (the same language here,) when I migrated my personal server onto plan 9. That's why this software is named Yamaimo (another type of yam.) The manipulation isn't deep enough to justify it. Like I said above, I'm almost exclusively just concatenating canned strings and verbatim files.

So I came to a shell script. I just wrote it in rc because, quite frankly, it's my favorite shell for scripting and I had it installed. The contents themselves are boring and largely invisible, like a tool should be. If you really want me to take you through it step by step, ask for it. By the way, feel free to consider that source (and all of the scripts in this post) under the ISC license.

Generating the posts, like I said, is only half the problem. What else could we lean on to help us? In general I'm somewhat skeptical of the term Software Engineering. In my opinion most of what the industry does is kind of a joke compared to the rigor involved in real engineering with a stamp and personal liability if the bridge you designed collapses and kills people. However one of the things that this industry has gotten painstakingly correct is version control. If you ask anyone who's worked on both sides, what they miss from software is version control. Every version of every document is tracked, can be moved between computers, can be saved away to experiment with changes, can be compared with any other version to find every difference. It's an incredibly powerful tool. We want our code and posts to be in version control anyway, so why not use it to do some of the heavy lifting for us? This is the script which will handle making a new blog post for me:


#!/usr/bin/env rc

id=`{date -u -Iminutes}
infile='posts/' ^ $id ^ '.in'
tmpfile=`{mktemp}
$VISUAL $"tmpfile &&
mv $"tmpfile $"infile &&
git add $"infile &&
git commit -m $"id &&
./build.rc &&
cd out &&
git add $"id ^ '.html' index.html feed.rss style.css &&
git commit -m $"id &&
git push

It creates a new file with the post's ID (the current utc timestamp down to the minute,) lets me write it, adds it to version control, commits it, builds the html files, adds those to version control, commits them, and pushes them to the server. If I have to go back and edit a post I have to perform some of these steps manually, but that's a rare operation which can be done by hand.

We've pushed to our server, now what? Git (the version control system I'm using,) has a feature called hooks which allow yet more scripts to be run when certain events happen. I added a post-update hook to copy the files into the web server when an update is pushed. This one line is left as an exercise (it's actually surprisingly tricky. Look for GIT_DIR in documentation.)

That's absolutely everything. As I said all code here can be used under the ISC license, and you can always see the latest version here.