[ jd303 ]

I've been meaning to put something up at this domain for a while. Not a blog in the "please subscribe and smash that like button" sense. Just a place to write about stuff I'm actually doing. Tech, music, tools, the occasional rant. The usual.

The question was always what to build it on. The obvious answer is WordPress because it's the obvious answer, and that's exactly why I didn't want it. I already manage too much infrastructure at work. I'm not spending my free time chasing plugin updates and PHP vulnerabilities for a personal site. Ghost was the next candidate, cleaner than WordPress, decent API, but it's still a server you have to babysit. I wanted something I could git push and walk away from.

So: Hugo. Static site generator, posts as Markdown files, Claude Code as the CMS. No database, no runtime, nothing to patch. The whole thing deploys via rsync to DreamHost. This post is being written in a terminal.

The aesthetic was the other thing. I wanted it to look like it came out of the early 90s underground: ICE and ACiD ANSI pack energy, hacker zine aesthetic, not startup landing page. Electric blues and purples on near-black. Monospace everything. If it could pass for a BBS login screen in the right light, we're close.

[ claude ]

What You Need Before You Start

Two prereqs that this post assumes you already have:

GitHub account (free). You need a private repo to store the site source. If you already have a personal access token set up, you're good. If not, create one at github.com → Settings → Developer settings → Personal access tokens.

DreamHost account, or any shared host that gives you SSH access and lets you point a domain at a directory. DreamHost runs about $3-5/month on a shared plan. The important thing is shell access. Without it, rsync won't work and you're stuck with FTP like it's 2002.


Installing Hugo

On WSL2 (Ubuntu/Debian):

sudo apt update && sudo apt install hugo

Verify:

hugo version

You want 0.110+ for current template behavior. If your distro's package is ancient, install via the Hugo releases page or snap:

snap install hugo

Creating the Site

cd ~/projects
hugo new site conspicuoustechnologist
cd conspicuoustechnologist
git init

Hugo scaffolds the directory structure. The important ones:

content/        ← your posts go here
themes/         ← your theme goes here
hugo.toml       ← site config
static/         ← files served as-is (images, etc.)
public/         ← built output, gitignored

The Theme

No theme was downloaded or modified. This one was built from scratch inside themes/conspicuous/. The minimum Hugo theme needs:

themes/conspicuous/
  layouts/
    _default/
      baseof.html     ← base template everything extends
      list.html       ← category/tag listing pages
      single.html     ← individual post template
    index.html        ← homepage
    partials/
      header.html
      footer.html
    shortcodes/
      jd303.html      ← the jd303 voice block
      claude.html     ← the claude voice block
  static/
    css/
      main.css
  theme.toml

The color palette: near-black background (#050510), electric blue (#00aaff), cyan (#00d4ff), purple (#cc44ff). Two fonts pulled from Google Fonts: VT323 for display text (headers, title), Share Tech Mono for body copy. Everything monospace.

The two-voice shortcodes are the structural piece. In any post, jd303 and Claude sections are wrapped like this:

[ jd303 ]
...jd303 writes here...
[ claude ]
...claude writes here...

Each renders as a styled block with a [ jd303 ] or [ claude ] label. jd303 gets purple (#cc44ff), Claude gets cyan (#00d4ff). Different border treatment, different color accent. Two terminals talking to each other.


Site Config

hugo.toml at the root:

baseURL = "https://conspicuoustechnologist.com/"
languageCode = "en-us"
title = "CONSPICUOUS TECHNOLOGIST"
theme = "conspicuous"

[params]
  description = "Tech, music, and other noise from a GenX systems guy in Denver."
  author = "jd303"
  tagline = "// signal over noise //"

[markup]
  [markup.highlight]
    style = "monokai"
    lineNos = true

[[menus.main]]
  name = "home"
  url = "/"
  weight = 1

[[menus.main]]
  name = "posts"
  url = "/posts/"
  weight = 2

[[menus.main]]
  name = "about"
  url = "/about/"
  weight = 3

DreamHost Setup

In the DreamHost panel, point your domain at a directory. The default is usually /home/[username]/[domain.com]. Note your actual shell username. It is not the same as your panel login. This matters. More on this in a second.

Enable SSH access in the panel under Users → Manage Users. Once it's active:

# In a new terminal, proves it actually works
ssh your-shell-username@conspicuoustechnologist.com

If that connects, you're good.


The Deploy Script

deploy.sh in the project root:

#!/bin/bash
export PATH="$HOME/.local/bin:$PATH"

DREAMHOST_USER="your-shell-username"
DREAMHOST_HOST="conspicuoustechnologist.com"
DREAMHOST_PATH="/home/your-shell-username/conspicuoustechnologist.com"

set -e

for cmd in hugo rsync ssh; do
  if ! command -v $cmd &>/dev/null; then
    echo "ERROR: $cmd not found."
    exit 1
  fi
done

echo "// building site //"
hugo --minify

echo "// deploying to DreamHost //"
rsync -avz --delete public/ "${DREAMHOST_USER}@${DREAMHOST_HOST}:${DREAMHOST_PATH}/"

echo "// deployed //"

Make it executable:

chmod +x deploy.sh

To deploy:

./deploy.sh

That builds the site into public/ and rsyncs it to DreamHost. The --delete flag removes files from the server that no longer exist locally. First run will ask for your DreamHost SSH password. Set up SSH key auth if you want passwordless deploys.


GitHub

git add .
git commit -m "initial commit"
gh repo create conspicuoustechnologist --private --source=. --push

That's it. The repo is the source of truth. Posts are Markdown files. ./deploy.sh ships it.

[ jd303 ]

The build itself took one session. Most of it was writing the theme CSS, getting the glow effects right, the ANSI-adjacent border treatments on the voice blocks, the nav style. The .-{link}-. nav format was the first thing I locked in and everything else followed from it.

One thing that bit us: DreamHost has two usernames. There's the one you log into the panel with, and there's the actual shell user that your domain runs under. They are not the same. I was rsyncing to the wrong one for a few minutes wondering why nothing was showing up. Once we pointed DREAMHOST_USER at the right account (doetech, not jdoe) it deployed clean on the first try.

That was honestly the only friction. Hugo's fast, rsync is rsync, and having Claude Code in the loop for the theme scaffolding meant I wasn't starting from a blank file.

The result is what you're looking at. One evening, one session, zero plugin hell.

Shipped on my birthday, which was either intentional or a happy accident depending on when you ask me.