Static Blog Generator for ptrace.dev
This is a very tailored html generator and search server for my blog.
In a sense where it probably won't fit your needs. But you're welcome to explore
the code and use parts of it.
Since I wanted to avoid writing 30 lines of Javascript, I opted for writing +1600 lines of Jai code to have a search function.
Search Server
The search server is build with sockets and a very, very minimal HTTP implementation. Since it's so low-level, there's no hand holding in regards of security and stability. To guarantee those attributes, you need to make some things happen:
- Test the connection with several types of clients (described below)
- Load test via
wrk&libfuzz - Sandbox with
seccomp,landlock, andnamespaces(more details below)
Clients
The code would be smaller, if all clients would've a good connection. But the reality is different. Clients could be:
- Slow
- Dripping (giving us a few bytes every
ntimes) - Stalling (just refuse to send ACKs back)
- Spammy (flood us with requests aka DoS or dDoS)
And the next issue is, how you design your server. This server is synchronous and single threaded. Which sounds horrible at first, but you'll be surprised how performative it is and it solves the mentioned problems!
Epoll
If you design your server just with the classic accept() and recv(), clients will
block your server thread. Resulting in a unresponsive website for other clients, since
your code will block at recv()!
One might just™ open new threads, but depending on your machine, this concept will fail very quickly.
But we can add epoll() into the mix!
Instead of waiting till the client send its data, the kernel does the waiting and
returns data that is ready to process.
I recommend to look into man epoll for more details.
Timeout
Clients are not stalling our thread anymore, but they still could linger around and waste a file descriptor. For this case, you have to implement a timeout, which disconnects the client after some time.
Malformed Requests
This server expects this http request:
GET /search?q=<search term> HTTP/1.x
You could do the full validation routine, that checks every method kind. You could also
implement some logic that handles the HTTP version.
Or you could ask yourself, what does my server really need?
If you know what requests you expect, you can omit whole chunks of code, which is also easier to test.
That being said, I know that my server only accepts GET requests. And it doesn't support long-living connections. It's just request in, response out, and close connection aka HTTP/1.0.
The validation for that becomes really simple. You can straight up check if the request even starts with GET or not, and reject it early before do any parsing.
Having this tiny validation routine, makes it easier to spot bugs.
Now we can add fuzzing. I'm using LLVMs libfuzz to perform tests on
certain functions. Which reveals, if your program blows up or does not handle specific
szenarios right.
Sandboxing
Since anyone can throw requests towards my server, it is vital to not only ensure your server handles weird requests, it's also important to handle the case where someone took over the process because of some unknown vulnerability in your program.
In this case, we use tools to achieve a sandboxed environment.
The easiest way would be, to use systemd.exec and just start your program in such restrictive environment.
If you don't like the dependency of systemd you could also implement the sandboxing by yourself. Or implement partial sandboxing and close gaps with systemd - which was my approach.
I'm using two concepts for restricting my program:
- seccomp
- landlock
There's also a third concept, called namespaces. But for this tiny project I didn't
implemented it in code, since it required way more care and loc
(Which is the reason why I reached for systemd.exec).
Markdown Metadata
At the top of the markdown file we have to declare some metadata.
This is how an entry looks like:
hidden: false
width: 700
title: test image
published: 2026-01-01-12-00
updated: now
META;
Some text that gets rendered as html later.
Lets walk through each element.
hidden [Optional]
If hidden: true is provided it will ignore this entry entirely.
Providing hidden: false or omitting it entirely will render it.
width [Optional]
Some blog posts are using code blocks where the block will overflow with the standard width. You can set the width in pixel individually per blog post here.
title
The title of the blog post will be presented as big orange text.
published
When the blog post will be published as: YYYY-MM-DD-H-M
You could omit hours and minutes and just supply YYYY-MM-DD
It is also possible to omit it or use now - in both cases it will
use the current date.
After running this program it will add the datetime as milliseconds.
updated
When the article was updated the last time. You could also omit it or
use now like mentioned above.
META;
This is important to close our 'header' with META; so it gets parsed correctly.
If this is missing, the program will complain.
Known Issues
- 2026-04-27: When viewing a search result, that contains an image, it does not
load it, since the path is wrong.
This happens because
documents_html: *[]Entryis not filtered, so it doesn't contain updated image paths.