Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:

permissions:
contents: write # goreleaser uploads to GitHub Releases
packages: write # goreleaser pushes to ghcr.io/srcfl/hugin-agent

jobs:
goreleaser:
Expand All @@ -21,6 +22,22 @@ jobs:
go-version: "1.25"
cache: true

# Multi-arch Docker build for ghcr.io/srcfl/hugin-agent (linux/amd64
# + linux/arm64). buildx + qemu enable cross-arch compilation in the
# x86 GitHub runner.
- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- uses: goreleaser/goreleaser-action@v6
with:
version: "~> v2"
Expand Down
66 changes: 66 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,69 @@ changelog:
- "^test:"
- "^chore:"
- "Merge pull request"

# Multi-arch container image publish to GHCR. Same release tag drives
# tarballs (above) and ghcr.io/srcfl/hugin-agent:vX.Y.Z. The release
# workflow logs in to ghcr.io before goreleaser runs (see
# .github/workflows/release.yml).
#
# linux/amd64 + linux/arm64 cover Raspberry Pi (arm64) and most x86
# servers — the primary 42W bundling targets. Add more if a host
# platform shows up later.
dockers:
- id: hugin-agent-amd64
goos: linux
goarch: amd64
use: buildx
dockerfile: Dockerfile
image_templates:
- "ghcr.io/srcfl/hugin-agent:{{ .Version }}-amd64"
- "ghcr.io/srcfl/hugin-agent:v{{ .Major }}.{{ .Minor }}-amd64"
- "ghcr.io/srcfl/hugin-agent:latest-amd64"
build_flag_templates:
- "--platform=linux/amd64"
- "--label=org.opencontainers.image.title=hugin-agent"
- "--label=org.opencontainers.image.description=Local agent for the Hugin driver workbench"
- "--label=org.opencontainers.image.url=https://github.com/srcfl/hugin-agent"
- "--label=org.opencontainers.image.source=https://github.com/srcfl/hugin-agent"
- "--label=org.opencontainers.image.version={{ .Version }}"
- "--label=org.opencontainers.image.revision={{ .FullCommit }}"
- "--label=org.opencontainers.image.created={{ .Date }}"
- "--label=org.opencontainers.image.licenses=MIT"

- id: hugin-agent-arm64
goos: linux
goarch: arm64
use: buildx
dockerfile: Dockerfile
image_templates:
- "ghcr.io/srcfl/hugin-agent:{{ .Version }}-arm64"
- "ghcr.io/srcfl/hugin-agent:v{{ .Major }}.{{ .Minor }}-arm64"
- "ghcr.io/srcfl/hugin-agent:latest-arm64"
build_flag_templates:
- "--platform=linux/arm64"
- "--label=org.opencontainers.image.title=hugin-agent"
- "--label=org.opencontainers.image.description=Local agent for the Hugin driver workbench"
- "--label=org.opencontainers.image.url=https://github.com/srcfl/hugin-agent"
- "--label=org.opencontainers.image.source=https://github.com/srcfl/hugin-agent"
- "--label=org.opencontainers.image.version={{ .Version }}"
- "--label=org.opencontainers.image.revision={{ .FullCommit }}"
- "--label=org.opencontainers.image.created={{ .Date }}"
- "--label=org.opencontainers.image.licenses=MIT"

# Combine the per-arch images into one multi-arch manifest so
# `docker pull ghcr.io/srcfl/hugin-agent:v0.2.1` works on either
# host architecture and Docker picks the right one.
docker_manifests:
- name_template: "ghcr.io/srcfl/hugin-agent:{{ .Version }}"
image_templates:
- "ghcr.io/srcfl/hugin-agent:{{ .Version }}-amd64"
- "ghcr.io/srcfl/hugin-agent:{{ .Version }}-arm64"
- name_template: "ghcr.io/srcfl/hugin-agent:v{{ .Major }}.{{ .Minor }}"
image_templates:
- "ghcr.io/srcfl/hugin-agent:v{{ .Major }}.{{ .Minor }}-amd64"
- "ghcr.io/srcfl/hugin-agent:v{{ .Major }}.{{ .Minor }}-arm64"
- name_template: "ghcr.io/srcfl/hugin-agent:latest"
image_templates:
- "ghcr.io/srcfl/hugin-agent:latest-amd64"
- "ghcr.io/srcfl/hugin-agent:latest-arm64"
66 changes: 66 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# syntax=docker/dockerfile:1.6
#
# Dockerfile for hugin-agent — the local helper the workbench talks
# to. Published as ghcr.io/srcfl/hugin-agent:vX.Y.Z on every release
# tag via goreleaser (see .goreleaser.yml).
#
# Primary use case: bundling inside another product's docker-compose
# stack (e.g. forty-two-watts) as a sidecar. Single static Go binary,
# ~12 MB final image, no daemon, no telemetry.
#
# Browser auto-open is disabled by default in the container — there
# is no browser; the user opens the pairing URL on their host
# manually (or via the parent product's UI).

# ---- Build stage -----------------------------------------------------------
FROM golang:1.25-alpine AS builder

WORKDIR /src

RUN apk add --no-cache git

COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN CGO_ENABLED=0 go build \
Comment on lines +22 to +26

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid rebuilding from source in GoReleaser Docker stage

This Dockerfile assumes the repository source is present (COPY go.mod go.sum ./ and COPY . .), but GoReleaser’s Docker pipeline builds from a temporary artifact context rather than the repo root, so release-tag runs will fail with COPY failed: file not found in build context before any image can be pushed. In other words, this change breaks the new GHCR publish path in .github/workflows/release.yml; the Dockerfile used by GoReleaser needs to copy prebuilt artifacts from the context instead of running go build from source.

Useful? React with 👍 / 👎.

-ldflags "-s -w -X main.version=$(git describe --tags --always --dirty 2>/dev/null || echo docker)" \
-o /out/hugin-agent ./cmd/hugin-agent/

# ---- Runtime stage ---------------------------------------------------------
FROM alpine:3.21

RUN apk add --no-cache ca-certificates tzdata \
&& addgroup -S hugin \
&& adduser -S -G hugin -h /home/hugin hugin

COPY --from=builder /out/hugin-agent /usr/local/bin/hugin-agent

# Container-friendly defaults:
# - HUGIN_AGENT_HOST=0.0.0.0 so the agent is reachable from outside
# the container (the parent compose stack does port mapping or
# network_mode: host). The pairing token is still the auth boundary.
# - --no-browser passed via CMD because there's no browser to open.
# - Creds persist under /var/lib/hugin-agent so docker-compose
# volumes can keep them across container restarts.
ENV HUGIN_AGENT_HOST=0.0.0.0 \
HUGIN_AGENT_PORT=19090 \
HUGIN_AGENT_CREDS=/var/lib/hugin-agent/creds.json

RUN mkdir -p /var/lib/hugin-agent && chown hugin:hugin /var/lib/hugin-agent

USER hugin
WORKDIR /home/hugin
VOLUME ["/var/lib/hugin-agent"]

EXPOSE 19090

HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
CMD wget -qO- "http://127.0.0.1:${HUGIN_AGENT_PORT}/v1/health" \
| grep -q '"status":"ok"' || exit 1
# /v1/health is intentionally unauthenticated (see internal/server/server.go) —
# the agent's pairing-token gate sits on the work endpoints (scan, probe,
# run-lua), not on liveness probes.

ENTRYPOINT ["hugin-agent"]
CMD ["--no-browser"]
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,34 @@ scoop install hugin-agent
hugin-agent
```

### Docker

Published as a multi-arch image on every release tag. Use as a
sidecar in your existing compose stack (e.g. forty-two-watts):

```yaml
# docker-compose.hugin.yml
services:
hugin-agent:
image: ghcr.io/srcfl/hugin-agent:latest
restart: unless-stopped
network_mode: host # see Modbus devices on the host LAN
volumes:
- hugin-agent-data:/var/lib/hugin-agent
volumes:
hugin-agent-data:
```

```bash
docker compose -f docker-compose.hugin.yml up -d
docker logs hugin-agent # the pairing URL is printed on stderr
```

`/var/lib/hugin-agent/creds.json` persists NATS pairing across
restarts. Without `network_mode: host` the agent can still talk to
the workbench (publish port 19090) but won't see Modbus devices on
the user's LAN — pick whichever fits your security model.

### From source (any platform with Go 1.25+)

```bash
Expand Down