Source for badcluster.dev — static HTML/CSS, Python build script for the blog.
  • HTML 69.9%
  • Python 16.7%
  • CSS 13.4%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
arelas f13464e810 Add three posts: the IP scanner, The Vortex, and BBS HQ
One post per project that did not have one yet, written from each
project's own material rather than a summary of the card blurb:

- The IP scanner post covers the actual mechanism - the unprivileged
  ICMP interface each OS provides, why alive/filtered/dead are three
  answers and not one bit, and the mDNS/NetBIOS naming measurement.
- The Vortex post is about the Synchronet backup recovered in August,
  and why six years of daily call logs turned out to be the part worth
  having.
- The BBS HQ post is about keeping a directory honest by redialling it,
  and rendering each board's ANSI as text instead of a screenshot.

The IP scanner post originally used a Markdown table, which is dropped
in favour of a list: styles.css has no table rules, so it would have
been the site's first and rendered unstyled.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01W8MK9iamaX3fXBy99oqQsw
2026-09-16 11:19:38 -04:00
assets Add card icons for IP Scanner, Vortex BBS, and BBS HQ 2026-09-14 17:08:34 -04:00
blog Add three posts: the IP scanner, The Vortex, and BBS HQ 2026-09-16 11:19:38 -04:00
contact Restore Contact form CSS, dropped when the /test/ redesign went live 2026-08-27 10:47:49 -04:00
posts Add three posts: the IP scanner, The Vortex, and BBS HQ 2026-09-16 11:19:38 -04:00
build.py Add card icons for IP Scanner, Vortex BBS, and BBS HQ 2026-09-14 17:08:34 -04:00
index.html Add three posts: the IP scanner, The Vortex, and BBS HQ 2026-09-16 11:19:38 -04:00
README.md Correct the deploy docs: oracle1, opc ownership, dirty-tree trap 2026-09-14 17:11:44 -04:00
styles.css Restore Contact form CSS, dropped when the /test/ redesign went live 2026-08-27 10:47:49 -04:00

badcluster.dev

Source for the Bad Cluster site. Static HTML/CSS, no JS framework, no build tooling beyond a small Python script that generates the blog pages from Markdown.

Structure

index.html          Generated - home page (don't hand-edit, see build.py)
styles.css           Shared stylesheet for every page
assets/              Images
blog/                Generated - blog index + one folder per post
contact/             Generated - contact page
posts/               Source - Markdown files, one per blog post
build.py             Regenerates index.html, blog/, and contact/ from
                     the content below and posts/*.md

Anything under blog/ or contact/, plus index.html itself, is build output. Hand-editing those will work until the next python3 build.py, which overwrites them. Edit posts/*.md for blog content, or build.py itself for the home page, header/footer, or page templates.

Adding a blog post

Create a new file in posts/, named however you like (the date in the filename is just for sorting convenience - the real date comes from frontmatter). Format:

---
title: Your Post Title
date: 2026-07-04
summary: One sentence, shown on the blog index page.
---

Body in Markdown from here on. Headings, links, lists, `inline code`,
and fenced code blocks all work.

Then rebuild:

python3 build.py

Requires the markdown package: pip install markdown.

Editing the home page or templates

The home page content, header, footer, and page shell are all defined as Python strings in build.py (see build_home(), render_header(), render_footer(), page_shell()). Edit those, then rerun the build.

Contact form

The contact form posts to Formspree. To make it actually deliver messages to you:

  1. Create a free account at formspree.io and add a new form.
  2. Copy the form endpoint URL it gives you (looks like https://formspree.io/f/abcd1234).
  3. In build.py, replace the FORM_ACTION placeholder with that URL.
  4. Rerun python3 build.py and redeploy.

Until that's done, the form is wired up correctly but has nowhere real to send to - build.py will print a reminder every time you build if the placeholder is still in place.

Local preview

Browser security blocks the page's absolute paths (/styles.css, etc.) when opened directly as a file:// URL. Serve it locally instead:

python3 -m http.server 8000

Then open http://localhost:8000/.

Deploying

Hosted on oracle1, served by nginx, git-based: /var/www/badcluster.dev on the server is a clone of this repo (origin = git.badcluster.dev/arelas/badcluster-site). No GitHub Pages, no CNAME file, no separate build step - the generated files are committed, so a pull is the whole deploy.

You log in as ubuntu, but the checkout is owned by opc, so run git as opc:

ssh [email protected]
sudo -u opc -H git -C /var/www/badcluster.dev pull

Plain git as ubuntu fails with "detected dubious ownership". Don't fix that by adding the directory to ubuntu's safe.directory - the warning goes away, but the pull then writes ubuntu-owned files into an opc tree and the next deploy breaks in a stranger way.

The pull also refuses to run if the server's working tree is dirty, which is what happens when a page gets edited on the server directly. Check sudo -u opc -H git -C /var/www/badcluster.dev status first, and make sure whatever is in there has been brought back into this repo (as a build.py change, rebuilt) before discarding it:

sudo -u opc -H git -C /var/www/badcluster.dev checkout -- .