Add GoatCounter analytics, Miniflux, and update CLAUDE.md

- Add self-hosted GoatCounter via systemd binary service (stats.monotrope.au)
- Add Miniflux RSS reader via Docker Compose (reader.monotrope.au)
- Extend Ansible playbook with goatcounter and miniflux tags; all provisioning is idempotent
- Add Caddy reverse proxy blocks for both new services
- Inject GoatCounter script in baseof.html (production builds only)
- Add goatcounter and miniflux Makefile targets
- Rewrite CLAUDE.md to reflect actual project state

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Louis Simoneau
2026-04-09 15:09:53 +10:00
parent b090231557
commit 5a734d404b
20 changed files with 963 additions and 183 deletions

View File

@@ -6,10 +6,15 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ if not .IsHome }}{{ .Title }} · {{ end }}{{ .Site.Title }}</title>
<meta name="description" content="{{ with .Description }}{{ . }}{{ else }}{{ .Site.Params.description }}{{ end }}">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,300;0,400;0,500;1,400&family=Spectral:ital,wght@0,400;0,600;1,400&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/main.css">
{{ range .AlternativeOutputFormats -}}
{{ printf `
<link rel="%s" type="%s" href="%s" title="%s">` .Rel .MediaType.Type .Permalink $.Site.Title | safeHTML }}
{{ printf `<link rel="%s" type="%s" href="%s" title="%s">` .Rel .MediaType.Type .Permalink $.Site.Title | safeHTML }}
{{ end -}}
{{ if hugo.IsProduction }}
<script data-goatcounter="https://stats.monotrope.au/count" async src="https://stats.monotrope.au/count.js"></script>
{{ end -}}
</head>
@@ -18,7 +23,9 @@
<header class="site-header">
<a class="site-title" href="/">{{ .Site.Title }}</a>
<nav class="site-nav">
<a href="/posts/">Writing</a>
<a href="/about/"{{ if hasPrefix .RelPermalink "/about" }} class="active"{{ end }}>about</a>
<a href="/posts/"{{ if hasPrefix .RelPermalink "/posts" }} class="active"{{ end }}>writing</a>
<a href="/reviews/"{{ if hasPrefix .RelPermalink "/reviews" }} class="active"{{ end }}>reading</a>
</nav>
</header>
@@ -27,9 +34,10 @@
</main>
<footer class="site-footer">
<p>&copy; {{ now.Year }} Monotrope. Built with <a href="https://gohugo.io">Hugo</a>.</p>
<span>monotrope.au</span>
<span>&copy; {{ now.Year }}</span>
</footer>
</div>
</body>
</html>
</html>

View File

@@ -1,10 +1,17 @@
{{ define "main" }}
<h2 class="page-heading">{{ .Title }}</h2>
<p class="section-label">{{ .Title | lower }}</p>
<ul class="post-list">
{{ range .Pages.ByDate.Reverse }}
<li>
<time datetime="{{ .Date.Format "2006-01-02" }}">{{ .Date.Format "2006-01-02" }}</time>
<a href="{{ .Permalink }}">{{ .Title }}</a>
<div class="post-entry">
<a href="{{ .RelPermalink }}">{{ .Title }}</a>
{{ with .Params.tags }}
<div class="tags">
{{ range . }}<a class="tag" href="/tags/{{ . | urlize }}/">{{ . }}</a>{{ end }}
</div>
{{ end }}
</div>
</li>
{{ end }}
</ul>

View File

@@ -0,0 +1,39 @@
{{- $pctx := . -}}
{{- if .IsHome -}}{{ $pctx = .Site }}{{- end -}}
{{- $pages := slice -}}
{{- if or $.IsHome $.IsSection -}}
{{- $pages = $pctx.RegularPages -}}
{{- else -}}
{{- $pages = $pctx.Pages -}}
{{- end -}}
{{- $pages = where $pages "Type" "ne" "page" -}}
{{- $limit := .Site.Config.Services.RSS.Limit -}}
{{- if ge $limit 1 -}}
{{- $pages = $pages | first $limit -}}
{{- end -}}
{{- printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>{{ if eq .Title .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{ . }} on {{ end }}{{ .Site.Title }}{{ end }}</title>
<link>{{ .Permalink }}</link>
<description>Recent content {{ if ne .Title .Site.Title }}in {{ .Title }} {{ end }}on {{ .Site.Title }}</description>
<generator>Hugo</generator>
<language>{{ site.Language.LanguageCode }}</language>
{{ with .Site.Copyright }}<copyright>{{ . }}</copyright>{{ end }}
{{- if not .Date.IsZero -}}
<lastBuildDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</lastBuildDate>
{{- end -}}
{{- with .OutputFormats.Get "RSS" -}}
{{ printf "<atom:link href=%q rel=\"self\" type=%q />" .Permalink .MediaType | safeHTML }}
{{- end -}}
{{ range $pages }}
<item>
<title>{{ .Title }}</title>
<link>{{ .Permalink }}</link>
<pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
<guid>{{ .Permalink }}</guid>
<description>{{ with .Description }}{{ . | html }}{{ else }}{{ .Summary | html }}{{ end }}</description>
</item>
{{ end }}
</channel>
</rss>

View File

@@ -1,9 +1,17 @@
{{ define "main" }}
<article>
<h1>{{ .Title }}</h1>
<p class="post-meta">
<div class="post-meta">
<time datetime="{{ .Date.Format "2006-01-02" }}">{{ .Date.Format "2 January 2006" }}</time>
</p>
{{ with .ReadingTime }}<span>{{ . }} min read</span>{{ end }}
</div>
{{ .Content }}
{{ with .Params.tags }}
<div class="post-footer">
<div class="tags">
{{ range . }}<a class="tag" href="/tags/{{ . | urlize }}/">{{ . }}</a>{{ end }}
</div>
</div>
{{ end }}
</article>
{{ end }}

View File

@@ -1,10 +1,56 @@
{{ define "main" }}
<ul class="post-list">
{{ range (where .Site.RegularPages "Type" "posts").ByDate.Reverse }}
<li>
<time datetime="{{ .Date.Format "2006-01-02" }}">{{ .Date.Format "2006-01-02" }}</time>
<a href="{{ .Permalink }}">{{ .Title }}</a>
</li>
{{ end }}
</ul>
{{ end }}
<div class="home-prompt">
<span class="prompt-line">whoami</span>
<span class="prompt-response">{{ .Site.Params.description | default "writing on technology, books, and ideas."
}}</span>
<span class="prompt-line"><span class="cursor"></span></span>
</div>
<div class="home-sections">
<section>
<div class="home-section-head">
<h2>writing</h2>
<a class="see-all" href="/posts/">all posts</a>
</div>
<ul class="post-list">
{{ range first 5 (where .Site.RegularPages "Section" "posts").ByDate.Reverse }}
<li>
<time datetime="{{ .Date.Format " 2006-01-02" }}">{{ .Date.Format "2006-01-02" }}</time>
<div class="post-entry">
<a href="{{ .RelPermalink }}">{{ .Title }}</a>
{{ with .Params.tags }}
<div class="tags">
{{ range . }}<a class="tag" href="/tags/{{ . | urlize }}/">{{ . }}</a>{{ end }}
</div>
{{ end }}
</div>
</li>
{{ end }}
</ul>
</section>
<section>
<div class="home-section-head">
<h2>reading</h2>
<a class="see-all" href="/reviews/">all reviews</a>
</div>
<ul class="review-list">
{{ range first 3 (where .Site.RegularPages "Section" "reviews").ByDate.Reverse }}
{{ $rating := .Params.rating | default 0 }}
<li>
<span class="review-date">{{ .Params.date_read | default (.Date.Format "2006-01") }}</span>
<div class="review-entry">
<a href="{{ .RelPermalink }}">{{ .Title }}</a>
{{ with .Params.book_author }}<span class="review-author">{{ . }}</span>{{ end }}
<span class="review-rating">
{{- strings.Repeat $rating "▓" -}}{{- strings.Repeat (sub 5 $rating) "░" -}}
</span>
</div>
</li>
{{ end }}
</ul>
</section>
</div>
{{ end }}

View File

@@ -0,0 +1,6 @@
{{ define "main" }}
<article>
<h1>{{ .Title }}</h1>
{{ .Content }}
</article>
{{ end }}

View File

@@ -0,0 +1,24 @@
{{ define "main" }}
<p class="section-label">reading</p>
<ul class="review-list">
{{ range .Pages.ByDate.Reverse }}
{{ $rating := .Params.rating | default 0 }}
<li>
<span class="review-date">{{ .Params.date_read | default (.Date.Format "2006-01") }}</span>
<div class="review-entry">
<a href="{{ .RelPermalink }}">{{ .Title }}</a>
{{ with .Params.book_author }}<span class="review-author">{{ . }}</span>{{ end }}
<span class="review-rating">
{{- strings.Repeat $rating "▓" -}}{{- strings.Repeat (sub 5 $rating) "░" -}}
<span style="color: var(--muted); font-size: 0.8em; margin-left: 0.4rem;">{{ $rating }}/5</span>
</span>
{{ with .Params.tags }}
<div class="tags">
{{ range . }}<a class="tag" href="/tags/{{ . | urlize }}/">{{ . }}</a>{{ end }}
</div>
{{ end }}
</div>
</li>
{{ end }}
</ul>
{{ end }}

View File

@@ -0,0 +1,32 @@
{{ define "main" }}
{{ $rating := .Params.rating | default 0 }}
<article>
<h1>{{ .Title }}</h1>
<div class="book-meta">
{{ with .Params.book_author }}
<span class="meta-key">author</span>
<span class="meta-val author">{{ . }}</span>
{{ end }}
<span class="meta-key">read</span>
<span class="meta-val">{{ .Params.date_read | default (.Date.Format "January 2006") }}</span>
<span class="meta-key">rating</span>
<span class="meta-val">
<span class="rating-blocks">{{- strings.Repeat $rating "▓" -}}{{- strings.Repeat (sub 5 $rating) "░" -}}</span>
<span class="rating-num">{{ $rating }}/5</span>
</span>
</div>
{{ .Content }}
{{ with .Params.tags }}
<div class="post-footer">
<div class="tags">
{{ range . }}<a class="tag" href="/tags/{{ . | urlize }}/">{{ . }}</a>{{ end }}
</div>
</div>
{{ end }}
</article>
{{ end }}