I have been around the web publishing block a few times. My very first blog engine was cobbled together back in the early days of static-only web pages, when CGI scripts were the only dynamic component you could lean on. Later, like many of us, I chased the dynamic wave. I wrote similar engines in ASP, ASP.NET, and PHP, riding the server-side scripting train for years.
Eventually, that road leads to WordPress. And with WordPress comes the constant toil - update loops, database bloat, plugin conflicts, and the never-ending security advisories. It gets exhausting when all you want to do is write down some thoughts about hardware, photography, or open source.
So, I decided to go back to my roots.
I wanted something dead simple, secure, and entirely under my control. For years, my writing has started its shelf life randomly in plain text files. I write and take notes in Markdown using Typora or Visual Studio Code, which is actually a surprisingly capable markdown environment. When LLMs came along and showed a natural affinity for Markdown, it felt like a fun validation of a workflow I had already been using for a decade.
Why Not a Classic Static Site Generator?
You might wonder why I didn't just pick up an established static site generator like Hugo, Jekyll, or Eleventy. The honest answer is that they feel way too complicated for what I am trying to do.
Most modern static site generators come bloated with extended toolchains, complex dependency trees, and a mountain of command-line magic just to get a basic theme running. By the time I configured their configuration files, customized the layouts, and set up a deployment workflow, I would have ended up writing a similar amount of custom automation code anyway. I didn't want to learn a massive framework's specific way of doing things just to render a folder of text files.
Plus, I just liked the experiment. Python and Ollama were already sitting on my system, and building this custom setup in close collaboration with Gemini was half the fun. It is tailored exactly to how I want to work, without any extra engineering weight.
From Raw Draft to Live Post
Writing a new post here feels nothing like working in a traditional CMS. I don't worry about formatting, picking categories, or fighting frontmatter syntax when I open an editor.
The process starts inside the sources/ directory with a raw, messy text file. I write my notes in Typora or VS Code, often bouncing between German and English depending on whatever is in my head. I don't even add titles or dates. When I am done, I just run the publish script, which orchestrates a pipeline that turns that rough text into a live webpage.
[sources/draft.md]
│
▼
(Ollama / gemma4:e4b) ──► Translates, tags, and cross-links
│
▼
[content/draft.md]
│
▼
(build_blog.py) ──► Sanitizes HTML, fixes links, maps assets
│
▼
[public/index.html] ──► Syncs live via lftp
A bash script picks up the raw draft and prepares a massive prompt for a local Ollama instance running a gemma4:e4b model. The script feeds the AI my strict voice guidelines, a JSON list of all my existing tags, and a link manifest containing the summaries and slugs of every post I have ever published here.
The local model translates any German sections into clean English, formats the text with standard code blocks, selects matching tags from the existing taxonomy, and looks for natural opportunities to drop in one or two context-aware cross-links to older articles. A quick Python snippet validates that the returned YAML frontmatter is well-formed before saving the file to the content/ folder.
From there, the static compiler (build_blog.py) handles the heavy lifting. Instead of relying on a framework, it uses standard Python tools to transform the markdown into static files. This is where a couple of custom architectural choices come in. The script runs an in-memory link pass that compares relative links against valid files in the content directory. If it finds a dead link, it heals it on the fly by stripping the broken markdown link wrapper and keeping the plain text so the site never generates a 404 error. It also scans for local images, copies them to a central assets folder, and automatically renames them using the post slug as a prefix to eliminate filename collisions.
The actual layout generation uses a safe rendering function to swap out variable hooks in a master HTML template. It pops the main article content out of the mapping dictionary entirely, renders the head metadata, layout attributes, and open graph tags first, and only inserts the raw article body at the very last step. This prevents broken layouts if a model-generated summary contains a stray double-quote that tries to break out of an HTML attribute.
Once the script finishes building the post, it regenerates the paginated index pages, updates the tag clouds, and refreshes the RSS feed. The shell script displays a quick confirmation prompt, and typing y triggers an automated mirror command that pushes the flat files to my web space.
Hello World
So, this is it - the official "Hello World" for the new setup. Every word you just read went through the exact pipeline I just described, from a chaotic text file on my laptop to a flat HTML page on a server somewhere. It feels good to have a space that just works without the overhead.
Now that the plumbing is sorted out, I can finally get back to what actually matters. I have a backlog of daily photography notes from around Toronto and a few hardware projects sitting on my desk that need writing up. See you in the next post.