Personal blog at n0tls.com. Plain static site — no frameworks, just Markdown, Jinja2 templates, and a Python build script.
.
├── .github/
│ └── workflows/
│ └── build.yml # CI: builds site and pushes to gh-pages
├── posts/ # Source blog posts (Markdown)
│ └── YYYY-MM-DD-title.md
├── templates/ # Jinja2 HTML templates
│ ├── index.html
│ ├── post.html
│ ├── tag.html
│ └── transcripts.html
├── transcripts/ # Static HTML transcript pages (copied to site/)
├── css/
│ └── style.css
├── images/
├── build.py # Static site generator
├── autopublish.py # File watcher: auto-commits and pushes new posts
├── requirements.txt # Pinned Python dependencies
├── CNAME # n0tls.com
└── site/ # Generated output (committed, served via gh-pages)
Create posts/YYYY-MM-DD-title.md with YAML front matter:
---
title: Your Post Title
date: 2026-04-09
tags:
- tag1
- tag2
excerpt: One or two sentences shown on the index and in the RSS feed.
---
Your content here in Markdown...Push to main and CI will build and deploy automatically.
pip install -r requirements.txt
python3 build.py
python3 -m http.server 8000 --directory siteThen visit http://localhost:8000.
autopublish.py watches posts/ for new files and auto-commits and pushes them:
python3 autopublish.pyDrop a finished .md file into posts/ and it commits with the message Add post: <filename> and pushes to main. CI takes over from there.
build.py does the full build in one pass:
- Parses YAML front matter and converts Markdown to HTML via
python-markdown - Renders each post through
templates/post.html(Jinja2, autoescape enabled) - Generates
site/index.html, tag pages undersite/tag/, andsite/feed.xml - Copies
css/,images/,transcripts/, andCNAMEintosite/
Post ordering uses the first git commit timestamp for each file, so posts appear in the order they were first committed rather than by filename date.
GitHub Actions (.github/workflows/build.yml) runs on every push to main:
- Installs pinned dependencies from
requirements.txt - Runs
python build.py - Commits the generated
site/back tomain - Deploys
site/to thegh-pagesbranch viapeaceiris/actions-gh-pages
GitHub Pages is configured to serve from gh-pages.
| Layer | Tool |
|---|---|
| Build | Python 3.11, python-markdown, Jinja2, PyYAML |
| Diagrams | Mermaid (loaded from jsDelivr CDN, pinned to exact version) |
| Analytics | Self-hosted (analytics.n0tls.com) |
| CSS | Plain CSS with CSS custom properties, dark mode via localStorage |
| JS | Vanilla JS (theme toggle only) |
| CI/CD | GitHub Actions + GitHub Pages |