diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index db3ee6c..51418b6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,6 +1,7 @@ # Release pipeline. Triggers on `v*` tag push; cuts a GitHub Release with # cross-platform binaries (linux/darwin/windows × amd64/arm64), a CycloneDX -# SBOM per archive, and cosign keyless signatures for every artefact. +# SBOM per archive, multi-arch Docker images on ghcr.io, and cosign +# keyless signatures for every artefact (binaries AND container manifests). # # To cut a release: # git tag vYY.NN @@ -43,6 +44,22 @@ jobs: - name: Install syft for SBOM generation uses: anchore/sbom-action/download-syft@v0 + # QEMU lets the amd64 runner build linux/arm64 image layers. + # Buildx is the modern build frontend that goreleaser's + # `dockers:` block invokes via `docker buildx build`. + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to ghcr.io + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Run goreleaser uses: goreleaser/goreleaser-action@v6 with: diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 5873f98..770adcd 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -88,6 +88,77 @@ checksum: name_template: 'checksums.txt' algorithm: sha256 +# Per-arch Docker images published to GitHub Container Registry. Each +# build target consumes the matching goreleaser-produced binaries (no +# in-image rebuild), then docker_manifests stitches the per-arch tags +# into a single multi-arch :version tag operators can pull without +# knowing what their host arch is. +# +# `use: buildx` is required for the --platform flag; release.yml sets +# up QEMU before goreleaser runs so cross-arch builds succeed on the +# amd64 GitHub runner. +dockers: + - image_templates: + - 'ghcr.io/cryptojones/networkinventoryagent:{{ .Version }}-amd64' + - 'ghcr.io/cryptojones/networkinventoryagent:latest-amd64' + dockerfile: Dockerfile.goreleaser + use: buildx + goarch: amd64 + extra_files: + - README.md + - LICENSE + - SECURITY.md + - ChangeLog.md + build_flag_templates: + - --platform=linux/amd64 + - --label=org.opencontainers.image.title=NetworkInventoryAgent + - --label=org.opencontainers.image.description=Two-agent network inventory with mutual watchdog + - --label=org.opencontainers.image.version={{ .Version }} + - --label=org.opencontainers.image.revision={{ .FullCommit }} + - --label=org.opencontainers.image.source=https://github.com/CryptoJones/NetworkInventoryAgent + - --label=org.opencontainers.image.licenses=Apache-2.0 + - image_templates: + - 'ghcr.io/cryptojones/networkinventoryagent:{{ .Version }}-arm64' + - 'ghcr.io/cryptojones/networkinventoryagent:latest-arm64' + dockerfile: Dockerfile.goreleaser + use: buildx + goarch: arm64 + extra_files: + - README.md + - LICENSE + - SECURITY.md + - ChangeLog.md + build_flag_templates: + - --platform=linux/arm64 + - --label=org.opencontainers.image.title=NetworkInventoryAgent + - --label=org.opencontainers.image.description=Two-agent network inventory with mutual watchdog + - --label=org.opencontainers.image.version={{ .Version }} + - --label=org.opencontainers.image.revision={{ .FullCommit }} + - --label=org.opencontainers.image.source=https://github.com/CryptoJones/NetworkInventoryAgent + - --label=org.opencontainers.image.licenses=Apache-2.0 + +docker_manifests: + - name_template: 'ghcr.io/cryptojones/networkinventoryagent:{{ .Version }}' + image_templates: + - 'ghcr.io/cryptojones/networkinventoryagent:{{ .Version }}-amd64' + - 'ghcr.io/cryptojones/networkinventoryagent:{{ .Version }}-arm64' + - name_template: 'ghcr.io/cryptojones/networkinventoryagent:latest' + image_templates: + - 'ghcr.io/cryptojones/networkinventoryagent:latest-amd64' + - 'ghcr.io/cryptojones/networkinventoryagent:latest-arm64' + +# cosign keyless OIDC signing for the published manifests. Uses the same +# Fulcio + Rekor flow as the binary signing in the `signs:` section. +docker_signs: + - cmd: cosign + artifacts: manifests + output: true + args: + - sign + - --yes + - --oidc-issuer=https://token.actions.githubusercontent.com + - ${artifact} + # cosign keyless OIDC signing. Requires GitHub Actions OIDC tokens, so this # only works from the .github/workflows/release.yml job — never from a # laptop. The transparency log entry is uploaded to Rekor by default. diff --git a/ChangeLog.md b/ChangeLog.md index 168739a..e731847 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -17,6 +17,51 @@ _No unreleased changes._ --- +## 26.10 — 2026-05-27 + +Container distribution. Adds multi-arch Docker images on the GitHub +Container Registry alongside the existing binary archives, with the same +cosign keyless OIDC signing flow extended to the image manifests. + +### Added + +- **`ghcr.io/cryptojones/networkinventoryagent:`** — multi-arch + manifest covering linux/amd64 + linux/arm64. `:latest` resolves to + the same image as the most recent `vYY.NN` tag. Default entrypoint is + `agent`; `wintermute` and `neuromancer` are present in the same image + and reachable via `--entrypoint /usr/local/bin/wintermute` etc. +- **`Dockerfile.goreleaser`** — slim COPY-only Dockerfile consumed by + goreleaser. Pre-built binaries are copied in rather than recompiled + per arch (the host-level goreleaser build matrix already produced + them). The existing top-level `Dockerfile` is unchanged so + `docker build .` from a checkout still works. +- **`cosign` signing on the published manifests.** Verify with: + ``` + cosign verify ghcr.io/cryptojones/networkinventoryagent:26.10 \ + --certificate-identity-regexp 'https://github.com/CryptoJones/NetworkInventoryAgent/' \ + --certificate-oidc-issuer https://token.actions.githubusercontent.com + ``` + +### Changed + +- **`.github/workflows/release.yml`** now sets up QEMU + Docker Buildx + and logs in to ghcr.io before invoking goreleaser, so the cross-arch + `linux/arm64` layer builds succeed on the amd64-only GitHub runner. +- **README** gains a Docker section with the `docker pull` quickstart + and the `cosign verify` snippet. + +### Notes + +- The image's default entrypoint is `agent` (standalone single-agent + binary). For the Wintermute/Neuromancer pair, the existing + `docker-compose.yml` still applies — point its `image:` at + `ghcr.io/cryptojones/networkinventoryagent:26.10` and you skip the + `docker build` step. +- Pulls are unauthenticated for public images; `docker login ghcr.io` + is only needed if a future release flips visibility to private. + +--- + ## 26.09 — 2026-05-27 Post-Planning.md tightening pass — clears the lint debt that 26.06's diff --git a/Dockerfile.goreleaser b/Dockerfile.goreleaser new file mode 100644 index 0000000..64fd88f --- /dev/null +++ b/Dockerfile.goreleaser @@ -0,0 +1,41 @@ +# Dockerfile.goreleaser — slim image consumed by `goreleaser release`. +# +# The main Dockerfile builds the binaries inside the image (multi-stage, +# good for `docker build .`). goreleaser already cross-compiles every +# binary at the host level, so this variant just COPYs the resulting +# artefacts in and avoids the duplicated Go toolchain pass per arch. +# +# Pinned by sha256 so rebuilds are reproducible and Renovate has a +# concrete handle to bump (renovate.json automerges digest updates). +FROM alpine:3.20@sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc + +RUN addgroup -S inventory && adduser -S -G inventory inventory + +# goreleaser places each per-binary build under its own dist subdir and +# passes the directory contents as the docker build context, so the +# binaries are at the top of the build context here — no per-arch dance. +COPY agent /usr/local/bin/agent +COPY wintermute /usr/local/bin/wintermute +COPY neuromancer /usr/local/bin/neuromancer +COPY console /usr/local/bin/console + +# Bundled docs / configs so `docker run --rm cat /etc/inventory/...` +# gives operators reference material without a separate volume mount. +COPY README.md /usr/share/doc/inventory/README.md +COPY LICENSE /usr/share/doc/inventory/LICENSE +COPY SECURITY.md /usr/share/doc/inventory/SECURITY.md +COPY ChangeLog.md /usr/share/doc/inventory/ChangeLog.md + +RUN mkdir -p /data && chown inventory:inventory /data +VOLUME ["/data"] + +USER inventory + +# Default ports — health 8080, admin 9090. Override at run time with +# -e or by mounting a config file under /data and pointing -config at it. +EXPOSE 8080 9090 + +# The `agent` entrypoint is the standalone single-agent binary. To run +# Wintermute/Neuromancer instead: +# docker run --rm --entrypoint /usr/local/bin/wintermute ... +ENTRYPOINT ["/usr/local/bin/agent"] diff --git a/README.md b/README.md index 61f534e..cd5e877 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,33 @@ No C toolchain is required. The SQLite driver (`modernc.org/sqlite`) is pure Go. ## Installation +### Docker (multi-arch, signed) + +```bash +docker pull ghcr.io/cryptojones/networkinventoryagent:latest +docker run --rm ghcr.io/cryptojones/networkinventoryagent:latest -version +``` + +The `:latest` and `:` tags both point at multi-arch manifests +(linux/amd64 + linux/arm64); your Docker client picks the right one for +the host. The manifests are signed with `cosign` keyless OIDC — verify with: + +```bash +cosign verify \ + --certificate-identity-regexp 'https://github.com/CryptoJones/NetworkInventoryAgent/' \ + --certificate-oidc-issuer https://token.actions.githubusercontent.com \ + ghcr.io/cryptojones/networkinventoryagent: +``` + +The image's default entrypoint is `agent` (standalone). To run the paired +Wintermute/Neuromancer mode, override the entrypoint: + +```bash +docker run --rm \ + --entrypoint /usr/local/bin/wintermute \ + ghcr.io/cryptojones/networkinventoryagent:latest -version +``` + ### Pre-built binaries (signed) Tagged releases at