Initial commit: Hugo site with Terminal theme

Scripts: setup.sh, build.sh, serve.sh (Docker-based)
Content: about, config, software, posts sections
Custom: CSS overrides, HTML sitemap layout, extended_head partial
Theme: hugo-theme-terminal via Hugo modules (go.mod)
This commit is contained in:
Jean-Michel Tremblay 2026-04-03 16:30:38 -04:00
commit b2727be8ce
28 changed files with 697 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
site/public/
site/resources/
site/.hugo_build.lock

29
scripts/build.sh Executable file
View file

@ -0,0 +1,29 @@
#!/usr/bin/env bash
set -euo pipefail
# Build and serve the Hugo site locally using Docker.
# Usage: ./scripts/build.sh
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
SITE_DIR="${PROJECT_ROOT}/site"
HUGO_IMAGE="ghcr.io/gohugoio/hugo:latest"
PORT="${1:-1313}"
if [[ ! -d "${SITE_DIR}" ]]; then
echo "[build] Site directory not found: ${SITE_DIR}"
echo "[build] Run ./scripts/setup.sh first."
exit 1
fi
echo "[build] Serving site at http://localhost:${PORT}/"
echo "[build] Press Ctrl+C to stop."
docker run --rm \
-v "${SITE_DIR}:/src" \
-w /src \
-p "${PORT}:1313" \
"${HUGO_IMAGE}" server \
--bind 0.0.0.0 \
--baseURL "http://localhost:${PORT}/" \
--appendPort=false

29
scripts/serve.sh Executable file
View file

@ -0,0 +1,29 @@
#!/usr/bin/env bash
set -euo pipefail
# Serve the Hugo site locally using Docker.
# Usage: ./scripts/serve.sh
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
SITE_DIR="${PROJECT_ROOT}/site"
HUGO_IMAGE="ghcr.io/gohugoio/hugo:latest"
PORT="${1:-1313}"
if [[ ! -d "${SITE_DIR}" ]]; then
echo "[serve] Site directory not found: ${SITE_DIR}"
echo "[serve] Run ./scripts/setup.sh first."
exit 1
fi
echo "[serve] Serving site at http://localhost:${PORT}/"
echo "[serve] Press Ctrl+C to stop."
docker run --rm \
-v "${SITE_DIR}:/src" \
-w /src \
-p "${PORT}:1313" \
"${HUGO_IMAGE}" server \
--bind 0.0.0.0 \
--baseURL "http://localhost:${PORT}/" \
--appendPort=false

104
scripts/setup.sh Executable file
View file

@ -0,0 +1,104 @@
#!/usr/bin/env bash
set -euo pipefail
# Initialize a Hugo site with the Terminal theme using Docker.
# Usage: ./scripts/setup.sh
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
SITE_DIR="${PROJECT_ROOT}/site"
HUGO_IMAGE="ghcr.io/gohugoio/hugo:latest"
hugo() {
docker run --rm \
-v "${SITE_DIR}:/src" \
-w /src \
"${HUGO_IMAGE}" "$@"
}
if [[ -d "${SITE_DIR}" ]]; then
echo "[setup] Site directory already exists: ${SITE_DIR}"
echo "[setup] Delete it first if you want a fresh start."
exit 1
fi
echo "[setup] Creating new Hugo site in ${SITE_DIR}"
mkdir -p "${SITE_DIR}"
docker run --rm \
-v "${PROJECT_ROOT}:/src" \
-w /src \
"${HUGO_IMAGE}" new site site
echo "[setup] Initializing Hugo module"
hugo mod init gohugo-jm
echo "[setup] Writing hugo.toml"
cat > "${SITE_DIR}/hugo.toml" << 'EOF'
baseURL = "http://localhost:1313/"
languageCode = "en-us"
title = "JM's Site"
pagination.pagerSize = 5
[markup.highlight]
noClasses = false
[params]
contentTypeName = "posts"
showMenuItems = 2
fullWidthTheme = false
centerTheme = true
[languages]
[languages.en]
languageName = "English"
title = "JM's Site"
[languages.en.params]
subtitle = "hello, it's JM"
owner = "JM"
keywords = ""
copyright = ""
menuMore = "Show more"
readMore = "Read more"
readOtherPosts = "Read other posts"
newerPosts = "Newer posts"
olderPosts = "Older posts"
missingContentMessage = "Page not found..."
missingBackButtonLabel = "Back to home page"
minuteReadingTime = "min read"
words = "words"
[languages.en.params.logo]
logoText = "JM's Terminal"
logoHomeLink = "/"
[languages.en.menu]
[[languages.en.menu.main]]
identifier = "about"
name = "About"
url = "/about"
[module]
[[module.imports]]
path = 'github.com/panr/hugo-theme-terminal/v4'
EOF
echo "[setup] Fetching theme module"
hugo mod get github.com/panr/hugo-theme-terminal/v4
echo "[setup] Creating first post"
mkdir -p "${SITE_DIR}/content/posts"
cat > "${SITE_DIR}/content/posts/hello.md" << 'EOF'
---
title: "Hello, it's JM"
date: 2026-04-03
draft: false
---
Hello, it's JM. Welcome to my site.
This is my first post, built with [Hugo](https://gohugo.io/) and the
[Terminal](https://github.com/panr/hugo-theme-terminal) theme.
EOF
echo "[setup] Done. Run ./scripts/build.sh to serve the site."

View file

@ -0,0 +1,6 @@
+++
date = '{{ .Date }}'
draft = true
title = '{{ replace .File.ContentBaseName "-" " " | title }}'
tags = []
+++

View file

@ -0,0 +1,10 @@
/* Highlight the first two menu items (About, Sitemap) */
.navigation-menu__inner li:nth-child(-n+2) a,
.menu__dropdown li:nth-child(-n+2) a {
text-transform: uppercase;
letter-spacing: 0.08em;
border-bottom: 2px solid currentColor;
padding-bottom: 2px;
font-weight: bold;
font-size: 1.1em;
}

View file

@ -0,0 +1,4 @@
---
title: "About"
description: "Who I am, what I use, and what I'm up to."
---

13
site/content/about/now.md Normal file
View file

@ -0,0 +1,13 @@
---
title: "What I'm Doing Now"
date: 2026-04-03
draft: false
tags: ['personal', 'workflow']
---
A [/now page](https://nownownow.com/about). Update this periodically with
what you're currently focused on.
- Building a Hugo site
- Experimenting with local LLMs via Ollama
- Indexing documentation with ChromaDB

View file

@ -0,0 +1,20 @@
---
title: "Tools I Use"
date: 2026-04-02
draft: false
tags: ['tools', 'linux', 'workflow']
---
A list of the hardware, software, and services I rely on daily.
## Editor
VS Code with GitHub Copilot.
## Terminal
Bash on Linux.
## Languages
Python (via uv), Go, shell scripts.

View file

@ -0,0 +1,9 @@
---
title: "Who is JM"
date: 2026-04-01
draft: false
tags: ['intro', 'personal']
---
This is a short bio page. Replace this with a few paragraphs about yourself —
background, interests, what you do for work, whatever feels right.

View file

@ -0,0 +1,4 @@
---
title: "Config"
description: "Site configuration, conventions, and how things are set up."
---

View file

@ -0,0 +1,42 @@
---
title: "Automatic Dating with Git"
date: 2026-04-02
draft: false
tags: ['hugo', 'git', 'workflow']
---
Hugo can pull dates from Git history so you never have to update `lastmod`
by hand.
## Enable Git info
```toml
# hugo.toml
enableGitInfo = true
```
## Configure front matter date resolution
```toml
[frontmatter]
date = [':filename', ':default']
publishDate = [':filename', ':default']
lastmod = [':git', ':fileModTime']
```
## How it works
- `.Date` — tries the filename first (`2026-04-03-post.md`), then the
`date` field in front matter.
- `.Lastmod` — uses the Git author date of the last commit that touched
the file, falling back to filesystem mtime.
- `.PublishDate` — same resolution chain as `.Date`.
## Filename date formats
Hugo recognizes these patterns:
```
2026-04-03-my-post.md → date: 2026-04-03, slug: my-post
2026-04-03T14-30-00-post.md → date: 2026-04-03T14:30:00
```

View file

@ -0,0 +1,120 @@
---
title: "Organizing This Site"
date: 2026-04-03
draft: false
tags: ['hugo', 'workflow', 'meta']
---
A reference for how this Hugo site is organized and what configuration
options are available.
## Sections
Sections are created automatically from the directory tree under `content/`.
Any directory with an `_index.md` file becomes a section with its own list
page.
```
content/
├── _index.md ← home page
├── about/
│ ├── _index.md ← /about/ list page
│ ├── whoami.md
│ └── now.md
├── software/
│ ├── _index.md ← /software/ list page
│ ├── ollama.md
│ └── hugo-setup/ ← page bundle (leaf)
│ ├── index.md
│ └── architecture.svg
└── config/
├── _index.md ← /config/ list page
└── this-file.md
```
No config changes are needed — Hugo derives sections from the filesystem.
## Taxonomies
Tags and categories are enabled by default. Assign them in front matter:
```yaml
tags: ['hugo', 'docker', 'tools']
```
Hugo auto-generates pages at `/tags/`, `/tags/hugo/`, etc.
To define custom taxonomies beyond tags/categories:
```toml
# hugo.toml
[taxonomies]
tag = "tags"
category = "categories"
series = "series" # custom
```
## Page bundles
A **leaf bundle** is a directory with `index.md` (not `_index.md`). All
sibling files become page resources — images, data files, etc. — that
travel with the article.
A **branch bundle** uses `_index.md` and represents a section (it can have
children).
| File | Type | Has children? |
|---|---|---|
| `index.md` | leaf bundle | No |
| `_index.md` | branch bundle | Yes |
## Automatic dating
Three mechanisms, configured in `hugo.toml`:
```toml
enableGitInfo = true
[frontmatter]
date = [':filename', ':default']
lastmod = [':git', ':fileModTime']
```
- **`:filename`** — extracts date from `2026-04-03-my-post.md`.
- **`:default`** — falls back to the `date` field in front matter.
- **`:git`** — uses the Git author date of the last commit.
- **`:fileModTime`** — uses the file's mtime on disk.
## Automatic tagging
Hugo does **not** auto-tag content. Tags must be set manually in front
matter. The closest workaround is `cascade` in a section `_index.md`,
which pushes shared parameters to descendants — but it only works for
custom `.Params` fields, not real taxonomy terms.
For true auto-tagging, use an external script or LLM to populate front
matter before building.
## Menu
The Terminal theme reads `showMenuItems` from config. To add sections to
the nav menu:
```toml
[languages.en.menu]
[[languages.en.menu.main]]
identifier = "about"
name = "About"
url = "/about"
[[languages.en.menu.main]]
identifier = "software"
name = "Software"
url = "/software"
[[languages.en.menu.main]]
identifier = "config"
name = "Config"
url = "/config"
```
Set `showMenuItems = 3` (or more) to display them all without a "Show
more" toggle.

View file

@ -0,0 +1,48 @@
---
title: "Using Tags and Categories"
date: 2026-04-01
draft: false
tags: ['hugo', 'meta']
---
Hugo ships with two default taxonomies: **tags** and **categories**.
## Assigning terms
Add them to any article's front matter:
```yaml
---
title: "My Article"
tags: ['python', 'tools']
categories: ['tutorials']
---
```
## What Hugo generates
For each taxonomy, Hugo creates:
- A **taxonomy list** page: `/tags/` — shows all terms.
- A **term** page per value: `/tags/python/` — lists all articles with
that tag.
## Custom taxonomies
Define additional taxonomies in `hugo.toml`:
```toml
[taxonomies]
tag = "tags"
category = "categories"
series = "series"
```
Then use `series: ['my-series']` in front matter. Hugo generates `/series/`
and `/series/my-series/` automatically.
## Tag overlap
Tags are meant to cross-cut sections. An article in `software/` and one in
`config/` can share the tag `hugo` — the term page at `/tags/hugo/` will
list both.

View file

@ -0,0 +1,10 @@
---
title: "Hello, it's JM"
date: 2026-04-03
draft: false
---
Hello, it's JM. Welcome to my site.
This is my first post, built with [Hugo](https://gohugo.io/) and the
[Terminal](https://github.com/panr/hugo-theme-terminal) theme.

7
site/content/sitemap.md Normal file
View file

@ -0,0 +1,7 @@
---
title: "Sitemap"
date: 2026-04-03
draft: false
layout: "sitemap"
tags: []
---

View file

@ -0,0 +1,4 @@
---
title: "Software"
description: "Tools, setups, and projects I work with."
---

View file

@ -0,0 +1,26 @@
---
title: "Vector Search with ChromaDB"
date: 2026-04-02
draft: false
tags: ['chromadb', 'python', 'llm', 'tools']
---
ChromaDB is an embedding database for building search and retrieval systems.
## How I use it
I chunk documentation (VyOS, Hugo) into paragraphs, embed them with
`nomic-embed-text` via Ollama, and store the vectors in ChromaDB for
semantic search.
## Stack
```
Documents → Chunker → Ollama embeddings → ChromaDB → Query API
```
## Key concepts
- **Collection**: a named group of embeddings (like a table).
- **Document**: the raw text stored alongside the vector.
- **Metadata**: key-value pairs for filtering results.

View file

@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 200">
<rect x="10" y="10" width="120" height="60" rx="8" fill="#2d2d2d" stroke="#7c6f64" stroke-width="2"/>
<text x="70" y="45" text-anchor="middle" fill="#ebdbb2" font-size="14">content/</text>
<rect x="150" y="10" width="120" height="60" rx="8" fill="#2d2d2d" stroke="#7c6f64" stroke-width="2"/>
<text x="210" y="45" text-anchor="middle" fill="#ebdbb2" font-size="14">Hugo build</text>
<rect x="290" y="10" width="100" height="60" rx="8" fill="#2d2d2d" stroke="#7c6f64" stroke-width="2"/>
<text x="340" y="45" text-anchor="middle" fill="#ebdbb2" font-size="14">public/</text>
<line x1="130" y1="40" x2="150" y2="40" stroke="#ebdbb2" stroke-width="2" marker-end="url(#arrow)"/>
<line x1="270" y1="40" x2="290" y2="40" stroke="#ebdbb2" stroke-width="2" marker-end="url(#arrow)"/>
<defs><marker id="arrow" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#ebdbb2"/></marker></defs>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,46 @@
---
title: "Setting Up Hugo with Docker"
date: 2026-04-01
draft: false
tags: ['hugo', 'docker', 'tools']
---
This article is a **page bundle** (leaf bundle). Notice it lives at
`software/hugo-setup/index.md` — not `hugo-setup.md`.
## What makes this a page bundle?
The directory structure looks like this:
```
content/software/hugo-setup/
├── index.md ← this file (the page content)
├── architecture.svg ← co-located resource (image)
└── notes.txt ← co-located resource (data)
```
Everything in this folder **belongs to this page**. Hugo treats the sibling
files as *page resources* accessible via `.Resources` in templates.
## Why use bundles?
- **Co-location**: images and files live next to the article that uses them,
not in a global `static/` folder.
- **Resource processing**: Hugo can resize, crop, and fingerprint bundled
images at build time.
- **Portability**: move or delete the folder and everything travels together.
## Using a bundled image
In a template you'd access it with:
```go-html-template
{{ $img := .Resources.GetMatch "architecture.svg" }}
{{ with $img }}
<img src="{{ .RelPermalink }}" alt="Architecture diagram">
{{ end }}
```
Or reference it directly in Markdown:
![Architecture](architecture.svg)

View file

@ -0,0 +1,3 @@
This is a co-located resource file.
Hugo treats it as a page resource of the hugo-setup article.
It won't appear as its own page — it's just data attached to the bundle.

View file

@ -0,0 +1,26 @@
---
title: "Running Local LLMs with Ollama"
date: 2026-04-02
draft: false
tags: ['ollama', 'llm', 'tools', 'linux']
---
Ollama lets you run large language models locally with a single command.
## Quick start
```bash
ollama run llama3
```
## Why local?
- No API keys or rate limits.
- Data stays on your machine.
- Works offline.
## Models I use
- `llama3` — general purpose
- `codellama` — code generation
- `nomic-embed-text` — embeddings for vector search

View file

@ -0,0 +1,28 @@
---
title: "Python Packaging with uv"
date: 2026-04-03
draft: false
tags: ['python', 'tools', 'workflow']
---
`uv` is a fast Python package manager and project tool written in Rust.
## Why uv over pip/poetry/pipenv?
- 10100x faster dependency resolution.
- Single tool: replaces pip, pip-tools, virtualenv, and pyenv.
- Lockfile support via `uv.lock`.
## Common commands
```bash
uv init myproject # scaffold a new project
uv add requests # add a dependency
uv sync # install from lockfile
uv run pytest # run inside the managed venv
```
## Project convention
All Python projects in this workspace use `uv` exclusively — no raw
`pip install` or `python -m pytest`.

5
site/go.mod Normal file
View file

@ -0,0 +1,5 @@
module gohugo-jm
go 1.26.1
require github.com/panr/hugo-theme-terminal/v4 v4.2.3 // indirect

2
site/go.sum Normal file
View file

@ -0,0 +1,2 @@
github.com/panr/hugo-theme-terminal/v4 v4.2.3 h1:BU4x0qPDTZao6kzT4hWrOwGVaYgo5GesQZr+ATMsQoM=
github.com/panr/hugo-theme-terminal/v4 v4.2.3/go.mod h1:W0sFodm5ipL5gjRqOFjg4zD+euoQ8hCtyDDtqSpihxM=

68
site/hugo.toml Normal file
View file

@ -0,0 +1,68 @@
baseURL = "http://localhost:1313/"
languageCode = "en-us"
title = "JM's Site"
pagination.pagerSize = 5
enableGitInfo = true
[frontmatter]
date = [':filename', ':default']
lastmod = [':git', ':fileModTime']
[markup.highlight]
noClasses = false
[params]
contentTypeName = "posts"
showMenuItems = 5
fullWidthTheme = false
centerTheme = true
[languages]
[languages.en]
languageName = "English"
title = "JM's Site"
[languages.en.params]
subtitle = "hello, it's JM"
owner = "JM"
keywords = ""
copyright = ""
menuMore = "Show more"
readMore = "Read more"
readOtherPosts = "Read other posts"
newerPosts = "Newer posts"
olderPosts = "Older posts"
missingContentMessage = "Page not found..."
missingBackButtonLabel = "Back to home page"
minuteReadingTime = "min read"
words = "words"
[languages.en.params.logo]
logoText = "JM's Terminal"
logoHomeLink = "/"
[languages.en.menu]
[[languages.en.menu.main]]
identifier = "about"
name = "About"
url = "/about"
weight = 1
[[languages.en.menu.main]]
identifier = "sitemap"
name = "Sitemap"
url = "/sitemap"
weight = 2
[[languages.en.menu.main]]
identifier = "software"
name = "Software"
url = "/software"
weight = 10
[[languages.en.menu.main]]
identifier = "config"
name = "Config"
url = "/config"
weight = 11
[module]
[[module.imports]]
path = 'github.com/panr/hugo-theme-terminal/v4'

View file

@ -0,0 +1,17 @@
{{ define "main" }}
<h1>{{ .Title }}</h1>
{{ range .Site.Sections }}
<h2><a href="{{ .RelPermalink }}">{{ .Title }}</a></h2>
<ul>
{{ range .Pages }}
<li>
<a href="{{ .RelPermalink }}">{{ .Title }}</a>
{{ with .Date }}<small> — {{ .Format "Jan 2, 2006" }}</small>{{ end }}
{{ with .Params.tags }}<small> [{{ delimit . ", " }}]</small>{{ end }}
</li>
{{ end }}
</ul>
{{ end }}
{{ end }}

View file

@ -0,0 +1,2 @@
{{ $custom := resources.Get "css/custom.css" | minify | fingerprint }}
<link rel="stylesheet" href="{{ $custom.RelPermalink }}" integrity="{{ $custom.Data.Integrity }}">