add goatcounter tracking to all sites via nginx sub_filter
Deploy nginx / deploy (push) Successful in 4s

This commit is contained in:
2026-04-09 16:54:35 -04:00
parent 553b77788d
commit 449b763621
12 changed files with 38 additions and 2 deletions
+13
View File
@@ -133,6 +133,19 @@ If any secret is missing, deploy fails with an error (not a warning).
- **One domain per conf file** — `deploy.sh` derives the cert name from the
filename and collects all `server_name` values for that cert
## GoatCounter (analytics)
GoatCounter runs at `goatcounter.hantim.net` for privacy-friendly web analytics.
**Script injection:** nginx injects the GoatCounter tracking script into all
HTML responses via `sub_filter`. The shared include `conf.d/goatcounter.inc`
is added to each site's main HTTPS server block. New sites get it automatically
via `app.sh`.
**Config:** `docker/goatcounter/compose.yaml` uses the `baethon/goatcounter`
image. Password is stored in Bitwarden (`hantim-goatcounter-password`) and
injected via the `.env.keys` mechanism.
## URL routing
- All HTTP → HTTPS redirect
+1
View File
@@ -13,6 +13,7 @@ for technical deep-dives, USECASES.md for expected behaviors.
- `tools/remove-app.sh` -- Remove a static site (local files + Gitea repo)
- `docker/nginx/conf.d/` -- Per-app nginx server blocks
- `docker/<domain>/compose.yml` -- Per-app compose files
- `docker/goatcounter/` -- GoatCounter analytics (compose.yaml, .env.keys)
- `docker/garage/` -- Garage S3 object storage (Dockerfile, garage.toml, .env.keys)
- `.gitea/workflows/` -- Per-app deploy workflows + provision.yml
+2
View File
@@ -38,6 +38,7 @@ docker/
compose.yml
<domain>/ # Per-app compose files (e.g. example.com)
compose.yml
goatcounter/ # GoatCounter analytics
garage/ # Garage S3-compatible object storage
compose.yml
Dockerfile
@@ -195,6 +196,7 @@ Project: `hantim`. Fetched via `bws` CLI on the server.
| `hantim-garage-media-key-id` | S3 access key ID for media uploads | `app.sh`, `aws` CLI |
| `hantim-garage-media-secret-key` | S3 secret key for media uploads | `aws` CLI |
| `hantim-uptimerobot-api-key` | UptimeRobot API key for monitors | `app.sh` |
| `hantim-goatcounter-password` | GoatCounter admin password | `deploy.sh` |
Machine accounts: `hantim-server` (token at `/etc/bws-token`), `hantim-ci` (reserved).
+12 -2
View File
@@ -102,7 +102,17 @@ for stateless static sites.
- SSL cert issued for `<name>.hantim.net` (no www variant)
- Service ready for customization (edit compose.yml image, add `.env.keys` if needed)
## 9. Upload media files
## 9. Analytics tracking
**Trigger:** automatic — nginx injects the GoatCounter script into all HTML responses.
**Expected outcome:**
- Every site includes the GoatCounter tracking script (injected via `sub_filter`)
- Page views are recorded at `goatcounter.hantim.net`
- New sites provisioned with `app.sh` include tracking automatically
- GoatCounter dashboard itself is not tracked
## 10. Upload media files
**Trigger:** `aws s3 cp` or `aws s3 sync` to `s3://example.com/`, or `./media.sh push` from a site repo.
@@ -111,7 +121,7 @@ for stateless static sites.
- Replicated to both Garage nodes (hantim + argento)
- Accessible at `https://www.example.com/media/<path>`
## 10. Local development with media files
## 11. Local development with media files
**Trigger:** `./media.sh pull` from a site repo.
+3
View File
@@ -0,0 +1,3 @@
sub_filter '</body>' '<script data-goatcounter="https://goatcounter.hantim.net/count" async src="//goatcounter.hantim.net/count.js"></script></body>';
sub_filter_once on;
sub_filter_types text/html;
+1
View File
@@ -37,6 +37,7 @@ server {
ssl_certificate_key /etc/letsencrypt/live/haanmind.net/privkey.pem;
include /etc/nginx/conf.d/security-headers.inc;
include /etc/nginx/conf.d/goatcounter.inc;
location /media/ {
proxy_pass http://garage:3902/;
+1
View File
@@ -37,6 +37,7 @@ server {
ssl_certificate_key /etc/letsencrypt/live/hantim.net/privkey.pem;
include /etc/nginx/conf.d/security-headers.inc;
include /etc/nginx/conf.d/goatcounter.inc;
location /media/ {
proxy_pass http://garage:3902/;
+1
View File
@@ -37,6 +37,7 @@ server {
ssl_certificate_key /etc/letsencrypt/live/hcsuzuki.music/privkey.pem;
include /etc/nginx/conf.d/security-headers.inc;
include /etc/nginx/conf.d/goatcounter.inc;
location /media/ {
proxy_pass http://garage:3902/;
+1
View File
@@ -37,6 +37,7 @@ server {
ssl_certificate_key /etc/letsencrypt/live/hcsuzuki.net/privkey.pem;
include /etc/nginx/conf.d/security-headers.inc;
include /etc/nginx/conf.d/goatcounter.inc;
location /media/ {
proxy_pass http://garage:3902/;
+1
View File
@@ -37,6 +37,7 @@ server {
ssl_certificate_key /etc/letsencrypt/live/thekims.family/privkey.pem;
include /etc/nginx/conf.d/security-headers.inc;
include /etc/nginx/conf.d/goatcounter.inc;
location /media/ {
proxy_pass http://garage:3902/;
+1
View File
@@ -37,6 +37,7 @@ server {
ssl_certificate_key /etc/letsencrypt/live/timothykim.net/privkey.pem;
include /etc/nginx/conf.d/security-headers.inc;
include /etc/nginx/conf.d/goatcounter.inc;
location /media/ {
proxy_pass http://garage:3902/;
+1
View File
@@ -356,6 +356,7 @@ server {
ssl_certificate_key /etc/letsencrypt/live/$APP/privkey.pem;
include /etc/nginx/conf.d/security-headers.inc;
include /etc/nginx/conf.d/goatcounter.inc;
location /media/ {
proxy_pass http://garage:3902/;