Skip to content
openserbiaPublic

About

Pinned upstream Go releases (from go.dev) as a Nix flake — bridge for when nixpkgs lags behind a security patch.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

go-flake

OpenSSF Scorecard

Daily-refreshed Nix flake mirroring upstream Go and Go tooling. Use it when nixpkgs lags an upstream security patch or tool release.

At a glance

Tool Source Pinning Refresh
go go.dev/dl/?mode=json published SHA256 daily
golangci-lint GitHub releases per-release <name>-checksums.txt daily
goreleaser GitHub releases per-release checksums.txt daily
gofumpt GitHub releases per-asset API digest field daily
doclint GitHub releases (first-party) per-release checksums.txt daily
hugo (extended, Linux only) GitHub releases per-release hugo_<ver>_checksums.txt daily
govulncheck GitHub git tags source tarball + vendor SHA256 daily
gopls GitHub git tags source tarball + vendor SHA256 daily
delve GitHub git tags source tarball (vendored) daily
staticcheck GitHub git tags source tarball + vendor SHA256 daily

For the binary mirrors, every (os, arch) the upstream actually publishes is mirrored — coverage is sparse per tool, since a tool appears on a system only if its upstream ships a binary for that arch. govulncheck, gopls, delve, and staticcheck are the exceptions: none of them ship upstream binaries for Linux/Darwin/FreeBSD that we'd want to mirror, so the flake builds them from source via buildGoModule and exposes them on every system the flake spans. All four are compiled against this flake's own latest Go rather than nixpkgs' Go — gopls and staticcheck misbehave when built with a Go minor older than the project they're analyzing, and delve needs to track Go's binary symbol format. Pinning the toolchain to the latest mirrored Go is what makes the "no nixpkgs lag" guarantee hold for these tools too.

delve's release tarballs ship a vendor/ directory, so its data file records vendor = null; rather than a vendor SRI hash — buildGoModule uses the bundled vendor tree directly.

Quick start

devbox

Add the flake reference directly to your packages list in devbox.json:

{
  "packages": [
    "github:openserbia/go-flake#go",
    "github:openserbia/go-flake#golangci-lint",
    "github:openserbia/go-flake#goreleaser",
    "github:openserbia/go-flake#gofumpt",
    "github:openserbia/go-flake#doclint",
    "github:openserbia/go-flake#hugo",
    "github:openserbia/go-flake#govulncheck",
    "github:openserbia/go-flake#gopls",
    "github:openserbia/go-flake#delve",
    "github:openserbia/go-flake#staticcheck"
  ]
}

Do not use the <pkg>@github:... syntax; Devbox treats flake references as full package identifiers. Pin a specific version with _<major>_<minor>_<patch>, e.g. #go_1_26_5. The short fragment lets Devbox/Nix infer the current system, which is the most portable form when it resolves cleanly.

If Devbox reports that a pinned flake package is ambiguous between packages and legacyPackages, qualify the standard flake output explicitly. In that form the system is part of the flake output path, so replace x86_64-linux with the system you are building on:

{
  "packages": [
    "github:openserbia/go-flake#packages.x86_64-linux.go_1_26_5"
  ]
}

For object-style packages, use the fully qualified reference as the key:

{
  "packages": {
    "github:openserbia/go-flake#packages.x86_64-linux.go_1_26_5": ""
  }
}

This works around Devbox resolver paths that see both packages.<system>.<attr> and legacyPackages.<system>.<attr> for the same short fragment and refuse to pick one.

If you get a "package not found" error when pinning a version, you may need to update your flake lock to pull the latest versions from this repository:

devbox update go

To pull the latest daily refresh for a package, run:

devbox update github:openserbia/go-flake#go

plain Nix

nix run   github:openserbia/go-flake#go -- version
nix shell github:openserbia/go-flake#golangci-lint
nix build github:openserbia/go-flake#gofumpt_0_10_0

Pointing JetBrains IDEs (GoLand / IntelliJ) at the SDK

The Go package installs under $out/share/go/ (matching the nixpkgs layout), with $out/bin/{go,gofmt} as symlinks into it. devbox merges the package into its profile, so just point GoLand / IntelliJ's Go SDK at <project>/.devbox/nix/profile/default/share/go — it has bin/, src/, and VERSION at the top level, which is what the IDE validates against.

Avoiding GitHub API rate limits

The github: URL scheme hits the GitHub API to resolve refs, which has a low unauthenticated rate limit. For CI or shared environments that hit it, swap in the tarball+codeload URL — same content, served via the codeload CDN:

{
  "packages": [
    "tarball+https://codeload.github.com/openserbia/go-flake/tar.gz/main#packages.x86_64-linux.go_1_26_5"
  ]
}

Builder image

docker/Dockerfile builds ghcr.io/openserbia/go-builder, a builder-stage base with a pinned devbox and a Nix store already holding the toolchain every openserbia service shares (Go, go-task, golangci-lint, govulncheck). Services start their Dockerfile from it instead of installing devbox and provisioning Nix on every build:

FROM ghcr.io/openserbia/go-builder:latest AS builder
COPY devbox.json devbox.lock ./
RUN devbox install            # near no-op when the lock matches the warm store
COPY . .
RUN devbox run -- task build

latest is the intended tag for consumers. The base warms the flake's newest Go and linters (the unversioned #go, #golangci-lint, #govulncheck attributes) and is republished whenever a mirrored upstream release lands, so when the services bump their pinned Go the base already holds it. A service's own devbox.lock still decides what gets built: a base holding a different version only costs warm-store hits. The devbox<version> tag and the digest the publish job prints in its run summary exist for anyone who needs a byte-for-byte reproducible builder. The image is published by .github/workflows/docker.yml on every push to main that touches docker/, and it is a builder only: runtime images stay distroless.

Discover available versions

nix flake show github:openserbia/go-flake

Or browse the data files directly:

Attribute naming: <tool>_<major>_<minor>_<patch> (dots → underscores). The bare attribute <tool> is an alias for the newest version available on the current system. default (and go) point at the latest Go.

Platforms

Coverage matches each upstream's published artifacts; the flake exposes a tool on a Nix system iff that system maps to a platform key in the data file. Currently exposed systems:

Linux Darwin FreeBSD
x86_64-linux x86_64-darwin x86_64-freebsd
aarch64-linux aarch64-darwin
i686-linux
armv6l-linux
armv7l-linux
riscv64-linux
powerpc64le-linux

Run nix flake show on your system to see which tools and versions resolve.

How versions are refreshed

A daily self-hosted GitHub Actions workflow (.github/workflows/update-versions.yml) runs five idempotent updater scripts, then commits any diff straight to main:

Script Updates Trust source
scripts/update-versions.py versions.nix go.dev JSON's sha256 field
scripts/update-github-tool.py --tool golangci-lint golangci-lint-versions.nix per-release golangci-lint-<ver>-checksums.txt
scripts/update-github-tool.py --tool goreleaser goreleaser-versions.nix per-release checksums.txt
scripts/update-github-tool.py --tool gofumpt gofumpt-versions.nix GitHub asset API digest: sha256:… field
scripts/update-github-tool.py --tool doclint doclint-versions.nix per-release checksums.txt (first-party)
scripts/update-github-tool.py --tool hugo hugo-versions.nix per-release hugo_<ver>_checksums.txt
scripts/update-source-tool.py --tool govulncheck govulncheck-versions.nix nix-prefetch-url --unpack + buildGoModule vendor discovery
scripts/update-source-tool.py --tool gopls gopls-versions.nix nix-prefetch-url --unpack + buildGoModule vendor discovery
scripts/update-source-tool.py --tool delve delve-versions.nix nix-prefetch-url --unpack (vendor=null; bundled vendor/)
scripts/update-source-tool.py --tool staticcheck staticcheck-versions.nix nix-prefetch-url --unpack + buildGoModule vendor discovery

Each script supports --check (exit non-zero if its data file is stale, no write). All three updaters accept --min-version to lower the floor; gofumpt's floor is fixed at 0.9.0 because GitHub only populates asset digests for releases uploaded after mid-2024.

Trust chain for the binary mirrors: SHA256 comes from the channel above, then Nix's own fetchurl verifies the downloaded archive against that hash at build time — no intermediate proxy, URLs point at go.dev and github.com/<repo>/releases/download/... directly. For govulncheck the chain is two-step: the source tarball SHA pins the GitHub archive, and the vendor hash pins the resolved Go module set against go.sum at build time.

Why this exists

When CVEs land in the Go stdlib (DNS resolver, net/mail, HTTP/2, etc.), upstream ships a patch within days. nixpkgs maintainers usually follow within a week, but production builds can be left exposed in the interim. The same lag hits major tool releases (golangci-lint, gofumpt) and frequently slips on Go sub-minor cadence. This flake mirrors upstream exactly, so a one-line data-file change is enough to be back on a clean stdlib or a current linter.

License

This flake is MIT. Each mirrored binary is licensed by its upstream project (Go: BSD-3-Clause; golangci-lint: GPL-3.0-or-later; goreleaser: MIT; gofumpt: BSD-3-Clause; govulncheck: BSD-3-Clause; doclint: MIT, openserbia first-party; hugo: Apache-2.0).

About

Pinned upstream Go releases (from go.dev) as a Nix flake — bridge for when nixpkgs lags behind a security patch.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages