The Dockerfile was broken since the MCP endpoint was added: it built a single file (cmd/domogeek/domogeek.go) instead of the whole package, so mcp.go's symbols (calendarDayFor, newMCPHandler) were undefined. Fixes that by building ./cmd/domogeek, pins the builder image to golang:1.27-alpine (module now requires go 1.25+), and drops the hardcoded GOOS/GOARCH=amd64 so multi-arch builds compile natively per platform instead of always producing an amd64 binary. Also fixes .Dockerignore (wrong case, never actually read by docker/buildah) by renaming it to .dockerignore, and anchors its "domogeek" pattern to /domogeek so it excludes only the compiled binary, not the whole cmd/domogeek source directory (same bug as the .gitignore fix in an earlier commit). Removes build-docker.sh: image releases now go through the existing homelab Tekton pipeline (gitea-docker-build-multiarch), triggered by a Gitea webhook on tag push, same pattern as other repos (e.g. dra-devices). Images move from docker.io/cyrilix/domogeek to git.cyrilix.bzh/cyrilix/domogeek. Documented in CLAUDE.md and README.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hu8wwahgecuCGfesTKCfEd
8.5 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
What this is
domogeek is a small Go HTTP service that exposes calendar/holiday information for home automation (domotic) use. It answers whether "today" is a working day, a French public holiday (ferie), and/or a personal holiday read from a CalDAV calendar. The same information is also exposed as MCP tools over a Streamable HTTP endpoint (/mcp), so LLM agents/MCP clients can query it directly.
Commands
Build and test use Go's standard tooling with vendored dependencies (-mod=vendor). The module requires Go 1.25+ (bumped from 1.18 to use the official MCP SDK). If the go binary on PATH resolves to an older toolchain (e.g. this box also has golang-1.18 installed and it can shadow the newer one), invoke a newer toolchain explicitly, overriding GOROOT if it's pinned to the old one (e.g. by an IDE's _INTELLIJ_FORCE_SET_GOROOT):
GOROOT=/usr/lib/go-1.27 /usr/lib/go-1.27/bin/go build -mod=vendor ./...
Otherwise, plain invocations work the same as before:
go build -mod=vendor ./... # build
go vet -mod=vendor ./... # vet
go test -mod=vendor ./... # run all tests
go test -mod=vendor ./pkg/calendar/ -run TestCalendar_IsWorkingDay -v # run a single test
Run locally (caldav flags are optional — omitting them disables CalDAV-based holiday checks):
go run -mod=vendor ./cmd/domogeek \
-port 8080 \
-caldav-url https://example.com \
-caldav-path /calendars/user/holidays/ \
-caldav-username user \
-caldav-password pass \
-caldav-summary-pattern Holidays
Running with no flags at all prints usage and exits (see the len(os.Args) <= 1 check in main). Note: despite the "caldav flags are optional" framing, main() currently calls calendar.NewCaldav unconditionally, even with an empty -caldav-url — with no caldav server to validate against, this hits the 1000-retry exponential backoff described below and the process never gets to http.ListenAndServe. This is a pre-existing quirk, not something introduced by the MCP work; if you need a quick local instance (e.g. to exercise /mcp or /calendar without a real CalDAV server), it's easiest to call newMCPHandler()/calendar.New(location) directly from a small throwaway main/test rather than running the built binary.
Docker image (multi-stage, distroless), for local testing:
docker build -t domogeek . # or: buildah bud -t domogeek .
Continuous builds
Pushing a Git tag matching v[0-9]* (existing convention: vX.Y.Z, e.g. v0.4.0) to
the Gitea remote triggers the homelab Tekton pipeline gitea-docker-build-multiarch,
which builds and pushes a multi-arch (amd64 + arm64) image plus a signed CycloneDX SBOM:
git.cyrilix.bzh/cyrilix/domogeek:<tag>
The pipeline uses its defaults — Dockerfile at the repo root, build context . — so
the Dockerfile must stay at the root. It builds each target platform natively (no
GOOS/GOARCH overrides in the go build step), so the Dockerfile must not hardcode a
single-arch GOOS/GOARCH — that was a real bug fixed alongside this: the builder image
is now pinned to golang:1.27-alpine (matching the go 1.25 module requirement) and the
go build step targets the whole package (./cmd/domogeek), not a single file.
There is no Tekton YAML in this repo: the pipeline and its Gitea webhook trigger are defined cluster-side. Watch a run: https://tekton.banquise.cyrilix.bzh
git tag v0.4.0
git push --tags
Note: this replaces the previous docker.io/cyrilix/domogeek:<git describe> releases
built locally via the now-removed build-docker.sh/buildah script.
Architecture
-
cmd/domogeek/domogeek.go— the binary entrypoint. Wires up flags, zap logging, thecalendar.Calendar, Prometheus metrics/instrumented handlers, and ahealth-gohealth check, then serves five HTTP routes:GET /calendar— returns aCalendarDayJSON payload (working_day,ferie,holiday,weekday) for "now" inEurope/Paris.GET /datetime— returns aCurrentDateTimeJSON payload (date_time) for "now" inEurope/Paris(DateTimeHandler).POST /mcp— MCP Streamable HTTP endpoint (seecmd/domogeek/mcp.gobelow).GET /metrics— Prometheus metrics (request counter/summary/histogram wrapping the calendar handler, defined ininit()).GET /status— health-go handler with two checks: a staticcalendarcheck and acaldavcheck that callscal.IsHolidaysFromCaldav.- The process blocks on
SIGTERM(notSIGINT) via a signal channel;http.ListenAndServeruns in a goroutine andzap.S().Fatals the process on error.
-
cmd/domogeek/mcp.go— MCP server built withgithub.com/modelcontextprotocol/go-sdk/mcp, mounted at/mcpviamcp.NewStreamableHTTPHandler. Exposes four tools, all reading the same package-levelcal/locationglobals as the/calendarand/datetimehandlers:get_current_datetime— same payload asGET /datetime: the current date and time.get_calendar_today— same payload asGET /calendar, for now.get_calendar_for_date— same payload for an arbitrarydate(YYYY-MM-DD); an invalid date is returned as a tool error (CallToolResult.IsError), not a protocol-level error, so an MCP client/LLM can see and self-correct.get_holidays— lists French public holidays (Calendar.GetHolidays) for a givenyear, defaulting to the current year if omitted.calendarDayForin this file is the single place that builds aCalendarDayfrom atime.Time; both the HTTP/calendarhandler and the MCP tools call it, so there's one code path for the working-day/ferie/holiday/weekday logic.cmd/domogeek/mcp_test.gounit-tests the handler functions directly;cmd/domogeek/mcp_e2e_test.godrives the real Streamable HTTP transport with an MCP client (httptest.Server+mcp.NewClient(...).Connect) to check the wiring end-to-end (tool listing, structured content, and the tool-error-vs-protocol-error distinction).cmd/domogeek/datetime_test.gocovers the/datetimeHTTP handler directly.
-
pkg/calendar/calendar.go— all domain logic, built around theCalendarstruct and functional options (WithCaldav,WithCaldavPath,WithCaldavSummaryPattern):GetEasterDayimplements the Gauss/Meeus algorithm to compute Easter Sunday for a given year (incal.Location);GetHolidaysderives the fixed-date French public holidays plus two computed from it: Easter Monday (Easter+1) and Ascension (Easter+39).IsHolidaycombines the static French holiday set withIsHolidaysFromCaldav.IsHolidaysFromCaldavqueries a CalDAV calendar for events on a given day and treats any event whoseSummarycontainscaldavSummaryPattern(default"Holidays") as a personal holiday. If noCaldavclient is configured, it returnsfalse, nil— CalDAV is optional.- The
Caldavinterface (QueryEvents) abstractsgithub.com/dolanor/caldav-go, letting tests substituteMockCaldav(seecalendar_test.go) instead of hitting a real server. NewCaldavvalidates the CalDAV connection at startup withavast/retry-go, retrying up to 1000 times with exponential backoff (max delay 24h) — this means a bad/unreachable CalDAV server does not fail fast;main()will effectively hang retrying rather than exiting quickly if the caldav server is down (it's onlyFatal'd ifNewCaldavitself returns a non-nil error, which currently only happens after all retries are exhausted).
-
pkg/metrics/metrics.go— currently a single unused/unconfigured Prometheus counter (emptyNamespace/Subsystem/Name/Help). Not wired intocmd/domogeek; treat as in-progress scaffolding rather than an established pattern to follow.
Dependencies
Dependencies are vendored under vendor/ and checked into the repo — always pass -mod=vendor (or rely on GOFLAGS=-mod=vendor if set) for build/test/vet so Go doesn't try to hit the network or the module cache. After changing go.mod/go.sum, run go mod vendor to keep vendor/ in sync (use the newer toolchain per the note above, and -mod=mod instead of -mod=vendor for the go get/go mod tidy step itself).
github.com/modelcontextprotocol/go-sdk (the official Go MCP SDK) is the dependency added for the /mcp endpoint; it pulled in google/jsonschema-go, segmentio/encoding, yosida95/uritemplate, and a few golang.org/x/* packages as transitive deps, and required bumping the go directive in go.mod from 1.18 to 1.25.0.