From 06834bc5fc90cb060d4a9a82a857ec42921fa6d6 Mon Sep 17 00:00:00 2001 From: Conall O'Brien Date: Wed, 6 May 2026 00:16:37 +0100 Subject: [PATCH 1/3] Embed release version via ldflags; expose as --version and /version - New version package holds Version="dev"; goreleaser overwrites it with the tag via -ldflags "-X .../version.Version={{.Version}}" - CLI: --version / -version / version subcommand prints the version and exits - Server: GET /version returns {"version":""} - goreleaser ldflags also strip debug info (-s -w) for smaller binaries --- .goreleaser.yaml | 6 +++-- cmd/coding-interview-pattern-drill/main.go | 18 +++++++++---- server/server.go | 31 +++++++++++++++------- version/version.go | 5 ++++ 4 files changed, 43 insertions(+), 17 deletions(-) create mode 100644 version/version.go diff --git a/.goreleaser.yaml b/.goreleaser.yaml index b6586df..5712e7f 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -12,6 +12,8 @@ builds: binary: coding-interview-pattern-drill env: - CGO_ENABLED=0 + ldflags: + - -s -w -X github.com/conallob/coding-interview-pattern-drill/version.Version={{.Version}} goos: - linux - darwin @@ -57,10 +59,10 @@ brews: - name: coding-interview-pattern-drill repository: owner: conallob - name: homebrew-tap + name: homebrew-taps token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}" directory: Formula - homepage: https://github.com/conallob/coding-interview-pop-quiz + homepage: https://github.com/conallob/coding-interview-pattern-drill description: "Pattern recognition trainer for coding interviews — CLI and browser UI" license: MIT test: | diff --git a/cmd/coding-interview-pattern-drill/main.go b/cmd/coding-interview-pattern-drill/main.go index b7b3b98..1bc7e6f 100644 --- a/cmd/coding-interview-pattern-drill/main.go +++ b/cmd/coding-interview-pattern-drill/main.go @@ -1,16 +1,24 @@ package main import ( + "fmt" "os" - "github.com/conallob/coding-interview-pop-quiz/cli" - "github.com/conallob/coding-interview-pop-quiz/server" + "github.com/conallob/coding-interview-pattern-drill/cli" + "github.com/conallob/coding-interview-pattern-drill/server" + "github.com/conallob/coding-interview-pattern-drill/version" ) func main() { - if len(os.Args) > 1 && os.Args[1] == "serve" { - server.Run(os.Args[2:]) - return + if len(os.Args) > 1 { + switch os.Args[1] { + case "--version", "-version", "version": + fmt.Println(version.Version) + return + case "serve": + server.Run(os.Args[2:]) + return + } } cli.Run(os.Args[1:]) } diff --git a/server/server.go b/server/server.go index ffcb04a..b98838a 100644 --- a/server/server.go +++ b/server/server.go @@ -11,20 +11,21 @@ import ( "strings" "sync" - "github.com/conallob/coding-interview-pop-quiz/cache" - "github.com/conallob/coding-interview-pop-quiz/config" - "github.com/conallob/coding-interview-pop-quiz/leetcode" - "github.com/conallob/coding-interview-pop-quiz/patterns" - "github.com/conallob/coding-interview-pop-quiz/quiz" + "github.com/conallob/coding-interview-pattern-drill/cache" + "github.com/conallob/coding-interview-pattern-drill/config" + "github.com/conallob/coding-interview-pattern-drill/leetcode" + "github.com/conallob/coding-interview-pattern-drill/patterns" + "github.com/conallob/coding-interview-pattern-drill/quiz" + "github.com/conallob/coding-interview-pattern-drill/version" ) // resultPayload is the JSON payload for a quiz answer result. type resultPayload struct { - Correct bool `json:"correct"` - ChoiceIndex int `json:"choiceIndex"` - AnswerIndex int `json:"answerIndex"` - PrimaryPattern patternInfo `json:"primaryPattern"` - SecondaryPatterns []patternInfo `json:"secondaryPatterns"` + Correct bool `json:"correct"` + ChoiceIndex int `json:"choiceIndex"` + AnswerIndex int `json:"answerIndex"` + PrimaryPattern patternInfo `json:"primaryPattern"` + SecondaryPatterns []patternInfo `json:"secondaryPatterns"` } type patternInfo struct { @@ -92,6 +93,7 @@ func Run(args []string) { mux.HandleFunc("/api/quiz/answer", app.handleQuizAnswer) mux.HandleFunc("/api/quiz/next", app.handleQuizNext) mux.HandleFunc("/api/cache/refresh", app.handleCacheRefresh) + mux.HandleFunc("/version", handleVersion) // Find a free port basePort := *port @@ -511,3 +513,12 @@ func (a *App) handleCacheRefresh(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, map[string]bool{"ok": true}) } + +// GET /version → {"version": "v1.2.3"} +func handleVersion(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + writeError(w, http.StatusMethodNotAllowed, "method not allowed") + return + } + writeJSON(w, http.StatusOK, map[string]string{"version": version.Version}) +} diff --git a/version/version.go b/version/version.go new file mode 100644 index 0000000..e64fef5 --- /dev/null +++ b/version/version.go @@ -0,0 +1,5 @@ +package version + +// Version is set at build time via -ldflags "-X github.com/conallob/coding-interview-pattern-drill/version.Version=". +// Falls back to "dev" for local builds. +var Version = "dev" From 48da4e6ba45e3e2a4a84f9b5a1481d0c37467b80 Mon Sep 17 00:00:00 2001 From: Conall O'Brien Date: Wed, 6 May 2026 00:23:51 +0100 Subject: [PATCH 2/3] Add container image build via goreleaser + podman MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Dockerfile: multi-stage scratch image — alpine:3 stage fetches the CA certificate bundle, final stage is scratch with just the certs and the pre-built binary; statically linked (CGO_ENABLED=0) so no libc needed - goreleaser: dockers section builds linux/amd64 and linux/arm64 images tagged ghcr.io/conallob/coding-interview-pattern-drill:-, all using podman; docker_manifests merges them into a multi-arch manifest at : and :latest - release workflow: adds packages:write permission and podman login to ghcr.io before goreleaser runs --- .github/workflows/release.yml | 4 +++ .goreleaser.yaml | 46 +++++++++++++++++++++++++++++++++-- Dockerfile | 11 +++++++++ 3 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 Dockerfile diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index db3da7d..a08ebee 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,6 +7,7 @@ on: permissions: contents: write + packages: write jobs: release: @@ -18,6 +19,9 @@ jobs: - uses: actions/setup-go@v5 with: go-version-file: go.mod + - name: Log in to ghcr.io + run: | + echo "${{ secrets.GITHUB_TOKEN }}" | podman login ghcr.io -u ${{ github.actor }} --password-stdin - uses: goreleaser/goreleaser-action@v6 with: distribution: goreleaser diff --git a/.goreleaser.yaml b/.goreleaser.yaml index b6586df..a042f62 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -12,6 +12,8 @@ builds: binary: coding-interview-pattern-drill env: - CGO_ENABLED=0 + ldflags: + - -s -w -X github.com/conallob/coding-interview-pattern-drill/version.Version={{.Version}} goos: - linux - darwin @@ -53,14 +55,54 @@ changelog: - Merge pull request - Merge branch +dockers: + - image_templates: + - "ghcr.io/conallob/coding-interview-pattern-drill:{{ .Tag }}-amd64" + dockerfile: Dockerfile + use: podman + goos: linux + goarch: amd64 + build_flag_templates: + - "--platform=linux/amd64" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.title={{.ProjectName}}" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=org.opencontainers.image.source=https://github.com/conallob/coding-interview-pattern-drill" + - image_templates: + - "ghcr.io/conallob/coding-interview-pattern-drill:{{ .Tag }}-arm64" + dockerfile: Dockerfile + use: podman + goos: linux + goarch: arm64 + build_flag_templates: + - "--platform=linux/arm64" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.title={{.ProjectName}}" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=org.opencontainers.image.source=https://github.com/conallob/coding-interview-pattern-drill" + +docker_manifests: + - name_template: "ghcr.io/conallob/coding-interview-pattern-drill:{{ .Tag }}" + use: podman + image_templates: + - "ghcr.io/conallob/coding-interview-pattern-drill:{{ .Tag }}-amd64" + - "ghcr.io/conallob/coding-interview-pattern-drill:{{ .Tag }}-arm64" + - name_template: "ghcr.io/conallob/coding-interview-pattern-drill:latest" + use: podman + image_templates: + - "ghcr.io/conallob/coding-interview-pattern-drill:{{ .Tag }}-amd64" + - "ghcr.io/conallob/coding-interview-pattern-drill:{{ .Tag }}-arm64" + brews: - name: coding-interview-pattern-drill repository: owner: conallob - name: homebrew-tap + name: homebrew-taps token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}" directory: Formula - homepage: https://github.com/conallob/coding-interview-pop-quiz + homepage: https://github.com/conallob/coding-interview-pattern-drill description: "Pattern recognition trainer for coding interviews — CLI and browser UI" license: MIT test: | diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e881527 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM alpine:3 AS certs +RUN apk add --no-cache ca-certificates + +FROM scratch +COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt +COPY coding-interview-pattern-drill /usr/local/bin/coding-interview-pattern-drill +EXPOSE 7777 +ENTRYPOINT ["coding-interview-pattern-drill"] +# Default to serve mode; pass LEETCODE_SESSION env var for credentials. +# Override with no args to use CLI mode (requires -it). +CMD ["serve", "--no-open"] From 4caa18863ce0c58b114e2cb5634befd6fa764ba7 Mon Sep 17 00:00:00 2001 From: Conall O'Brien Date: Mon, 11 May 2026 12:32:55 +0100 Subject: [PATCH 3/3] It's conallob/homebrew-tap, not conallob/homebrew-taps --- .goreleaser.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index a042f62..9da1304 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -99,7 +99,7 @@ brews: - name: coding-interview-pattern-drill repository: owner: conallob - name: homebrew-taps + name: homebrew-tap token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}" directory: Formula homepage: https://github.com/conallob/coding-interview-pattern-drill