Skip to content

Modernize: Go 1.26, dependency bumps, CI/CD - #65

Merged
jbpratt merged 3 commits into
masterfrom
bump-things
Aug 16, 2026
Merged

Modernize: Go 1.26, dependency bumps, CI/CD#65
jbpratt merged 3 commits into
masterfrom
bump-things

Conversation

@jbpratt

@jbpratt jbpratt commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Brings modbot up to current Go and current GitHub Actions, and fixes a handful of bugs found while reading the code. Three commits, each independently buildable.

Rebased onto master after #64 and the PM-reply/delcommand work landed — see "Rebase notes" at the bottom for how the overlapping changes were merged.

Behavior changes worth knowing about

!drop was always reporting failure. banATuser unmarshalled the response into the *http.Response instead of its error struct, so the call always errored. The ban itself went through — only the reply was broken. Mods will now get a success reply where they previously saw an error. Worth a heads-up so it doesn't read as a regression.

Also:

  • !check no longer proceeds on zero-valued data when angelthump returns an empty result
  • !roll 2d2 + 100 now applies the modifier — Atoi can't parse "+ 100", so a spaced modifier was silently dropped
  • Static commands match longest-prefix-first; previously !foo vs !foobar resolved arbitrarily depending on map iteration order
  • SIGTERM exits 0 instead of 1

Fixes

  • Six response bodies were never closed, leaking a connection on every !check, !stream, !imdb, !modify and !rename
  • renameUser built its JSON body with Sprintf; usernames are now marshalled and path-escaped
  • The static command map was read without a lock on every chat message

Modernization

  • Drop io/ioutil, math/randmath/rand/v2, range-over-int, min/max, slices.Backward/Concat, strings.CutPrefix
  • One shared http.Client with NewRequestWithContext, replacing a client built per call. A shutdown context is plumbed from main through the parser signature, so in-flight API calls are cancelled on SIGTERM rather than holding up shutdown.
  • main split into main/run so log.Fatal no longer skips deferred cleanup; signal.NotifyContext for INT/TERM, SIGHUP still rotates the log
  • Static commands moved behind an RWMutex in store.go with temp-file-and-rename writes
  • Stream modifiers are *bool with omitempty instead of strings string-replaced back into booleans. Same wire format, now covered by a test.
  • New -version flag reporting the commit via debug.ReadBuildInfo

Deps

dggchat 2020 snapshot → 2023-12-06, gorilla/websocket 1.4.2 → 1.5.3. No API changes needed. Prometheus from #64 is untouched.

CI/CD

  • CI adds vet, -race + coverage, golangci-lint (pinned v2.12.2), gofmt and go mod tidy -diff checks, and a Dockerfile build so a broken image fails here rather than on deploy. Go version now comes from go.mod instead of being hardcoded in two places. Adds permissions: contents: read and concurrency cancellation.
  • CD moves to buildx with GHA layer caching and pushes a sha- tag alongside latest, so a bad deploy can be rolled back to a known image. latest still points where the deploy hook expects. Token scoped to packages: write, deploys grouped so two can't race, and the ssh-action pin moved off a commit from February 2020 to v1.2.5.
  • Dockerfile pins its base, uses BuildKit cache mounts, and cross-compiles via TARGETOS/TARGETARCH instead of emulating under qemu. With -trimpath -ldflags="-s -w" the image is ~7MB. EXPOSE 9090 and the HEALTHCHECK from Expose metrics and a health check #64 are preserved.
  • Dependabot now weekly and grouped, covering github-actions and docker in addition to gomod

Adds a justfile (just check mirrors CI) and a README section for flags and dev workflow.

Rebase notes

Master and this branch both changed the parser signature. They were merged rather than either side winning:

  • Parser signature is now func(ctx context.Context, m message, s *dggchat.Session) — master's message/isPM reply routing plus this branch's context. sendMessageDedupe/sendPublicDedupe and the !m.isPM guard in rename are kept as-is.
  • !delcommand and normalizeCommandName are kept as master wrote them, re-pointed at the new store instead of the commands map + mutex.
  • Persistence diagnostics from b439eac (absolute path, entry count) moved into store.go, and commandPersistFailures.Inc() now fires from a single place covering both add and delete.
  • Three lint findings in the new metrics code are fixed rather than suppressed, since this PR is what introduces the linter: checkHealth and the metrics test now use NewRequestWithContext (noctx), and the test's poll loop closes its response body (bodyclose).
  • README had two lines that went stale on master: !addcommand still documented the _ deletion sentinel that b439eac replaced, and the footer still said PM replies come back in public chat. Both corrected, and !delcommand documented.

Verification

go build, go vet, golangci-lint run, go test -race, and go mod tidy -diff all clean on the rebased tree. Container image builds and -version reports the commit. I also ran the pinned golangci-lint release binary against the repo to confirm the CI lint job won't trip over Go 1.26.

Deliberately not done

  • loglog/slog: chatlog.log is the production chat log and modbot-rotate keys off it. Changing that output format is a separate decision.
  • Non-root container user: worth doing, but hooks/modbot.sh lives outside this repo and I couldn't check what owns the mounted chatlog dir. A wrong guess breaks the deploy silently.

🤖 Generated with Claude Code

jbpratt and others added 3 commits August 16, 2026 16:01
dggchat moves from the 2020 snapshot to 2023-12-06 and gorilla/websocket
from 1.4.2 to 1.5.3. No API changes needed on our side.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Brings the source up to current Go and fixes several bugs found while
reading it.

Fixes:
- !drop always reported failure. banATuser unmarshalled the response
  into the *http.Response rather than its error struct, so the call
  always errored. The ban itself went through; only the reply broke.
- Six response bodies were never closed, leaking a connection on every
  !check, !stream, !imdb, !modify and !rename.
- getATUserData returned a nil error on an empty result, so !check
  carried on with zero-valued data. It now returns errNotFound, matched
  with errors.Is instead of substring-matching "404".
- "!roll 2d2 + 100" silently dropped the modifier, since Atoi cannot
  parse "+ 100". Whitespace is stripped before parsing.
- Static commands were matched by ranging over a map, so "!foo" and
  "!foobar" resolved arbitrarily. Longest prefix now wins.
- renameUser built its JSON body with Sprintf. Usernames are now
  marshalled and path-escaped.

Modernization:
- Drop io/ioutil; math/rand -> math/rand/v2; range-over-int, min/max,
  slices.Backward/Concat, strings.CutPrefix.
- One shared http.Client with NewRequestWithContext, replacing a client
  built per call. A shutdown context is plumbed from main through the
  parser signature so in-flight API calls are cancelled on SIGTERM.
- Split main into main/run so log.Fatal no longer skips deferred
  cleanup. signal.NotifyContext handles INT/TERM, SIGHUP still rotates
  the log, and SIGTERM now exits 0 rather than 1.
- Move the static command map behind an RWMutex in store.go, with
  temp-file-and-rename writes, replacing two package globals and an
  unlocked read on every chat message.
- Stream modifiers are *bool with omitempty rather than strings
  string-replaced back into booleans. Same wire format, now tested.
- Add a -version flag reporting the commit via debug.ReadBuildInfo.

Tests are table-driven and cover the store, the modifier payload, and
the roll fixes. Adds .golangci.yaml.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CI now runs vet, tests under -race with coverage, golangci-lint (pinned
to v2.12.2), gofmt and `go mod tidy -diff` checks, and a Dockerfile
build so a broken image fails on the PR rather than on deploy. The Go
version comes from go.mod instead of being hardcoded in two places.
Adds `permissions: contents: read` and concurrency cancellation.

CD switches to buildx with GHA layer caching and pushes a sha- tag
alongside latest, so a bad deploy can be rolled back to a known image.
Scopes the token to `packages: write`, groups deploys so two cannot
race, and moves the ssh-action pin off a commit from February 2020 to
v1.2.5.

Dockerfile pins its base image, uses BuildKit cache mounts, and
cross-compiles via TARGETOS/TARGETARCH rather than emulating under qemu.
With -trimpath and -ldflags="-s -w" the image is ~7MB, and the commit is
stamped into the binary. Adds a .dockerignore.

Dependabot moves to weekly, grouped updates and now covers
github-actions and docker, not just gomod.

Adds a justfile (`just check` mirrors CI) and a README section covering
the flags and dev workflow.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jbpratt
jbpratt merged commit 6b5b860 into master Aug 16, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant