diff --git a/.dockerignore b/.dockerignore index de1f140..2d61bb9 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,6 @@ # We can only ignore eventual build binary file. # Everything that related to git, or is committed in git repository *MUST NOT* be ignored. # In particular, `.git` repository *MUST* be preserved because we use it to detect the version, which is included in the build binary file. -# This is necessary to ensure reproductible build (of the binary inside the Docker image). +# This is necessary to ensure reproducible build (of the binary inside the Docker image). ue-lite diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index 8ab5a35..2cdcaf3 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -36,7 +36,7 @@ jobs: - name: Install depends run: go get . - name: Build - run: go build -v -o ${{ steps.repo.outputs.repo }}-${{ steps.version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }} + run: go build -v -trimpath -o ${{ steps.repo.outputs.repo }}-${{ steps.version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }} - name: Generate artifact attestation uses: actions/attest@v4 with: diff --git a/Dockerfile b/Dockerfile index 8885677..933eb21 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,13 +6,18 @@ FROM golang:1.26.1 AS builder WORKDIR /src COPY go.mod go.sum ./ -RUN go mod download && go mod verify +RUN --mount=type=cache,target=/go/pkg/mod go mod download && go mod verify COPY . . -RUN CGO_ENABLED=0 go build -o /usr/local/bin/ue-lite +# To make reproducible the `COPY --from=builder` layer reproducible, we set modification time to build timestamp +# and we will copy the directory at once to avoid /usr/local/bin being "created" instead of copied (resulting in wrong a newer modification time). +RUN --mount=type=cache,target=/root/.cache/go-build CGO_ENABLED=0 go build -trimpath -o /usr/local/bin/ue-lite && touch --no-dereference --date="@$(ue-lite --build-timestamp)" /usr/local/bin /usr/local/bin/ue-lite FROM alpine:3.23.4 -RUN apk add --no-cache iptables iproute2 -COPY --from=builder /usr/local/bin/ue-lite /usr/local/bin/ue-lite +COPY --from=builder /usr/local/bin /usr/local/bin +# Even when cache is not created and logs are not written by apk-tools itself, adding some packages always updates modification time of a lot of files. +# To make this layer reprodicible, we need to reset the modification time to build timestamp. +# Some files are read-only filesystem (mounted by Docker), so we make sure to exclude them. +RUN apk add --no-cache --logfile=no iptables iproute2 && find /bin /etc /usr /lib /sbin /var -newer /usr/local/bin/ue-lite -not -path /etc/hosts -not path /etc/resolv.conf -print0 | xargs -0r touch --no-dereference --date="@$(ue-lite --build-timestamp)" ENTRYPOINT ["ue-lite"] CMD ["--help"] HEALTHCHECK --interval=1m --timeout=1s --retries=3 --start-period=5s --start-interval=100ms \ diff --git a/Makefile b/Makefile index 2acd584..fbe04cd 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ MKDIRP = mkdir -p .PHONY: install uninstall build clean default default: build build: - go build + CGO_ENABLED=0 go build -trimpath clean: go clean reinstall: uninstall install diff --git a/internal/app/build-time/build-time.go b/internal/app/build-time/build-time.go new file mode 100644 index 0000000..9f617cd --- /dev/null +++ b/internal/app/build-time/build-time.go @@ -0,0 +1,41 @@ +// Copyright Louis Royer and the NextMN contributors. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. +// SPDX-License-Identifier: MIT + +package buildtime + +import ( + "context" + "fmt" + "os" + "runtime/debug" + "time" + + "github.com/urfave/cli/v3" +) + +// Get the build time if defined, or unix epoch +func getBuildTime() time.Time { + if info, ok := debug.ReadBuildInfo(); ok { + for _, s := range info.Settings { + if s.Key == "vcs.time" { + if t, err := time.Parse(time.RFC3339, s.Value); err == nil { + return t + } + return time.UnixMicro(0) + } + } + return time.UnixMicro(0) + } + return time.UnixMicro(0) +} + +// Print build time (or Unix epoch when build time is not set) and exit the program +func PrintBuildTime(ctx context.Context, cmd *cli.Command, b bool) error { + if b { + fmt.Println(getBuildTime().Unix()) + os.Exit(0) + } + return nil +} diff --git a/main.go b/main.go index 506c60f..51f44eb 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ import ( "github.com/nextmn/logrus-formatter/logger" "github.com/nextmn/ue-lite/internal/app" + "github.com/nextmn/ue-lite/internal/app/build-time" "github.com/nextmn/ue-lite/internal/config" "github.com/sirupsen/logrus" @@ -40,6 +41,13 @@ func main() { }, Version: version, Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "build-timestamp", + Usage: "print timestamp of build creation", + Hidden: true, + Local: true, + Action: buildtime.PrintBuildTime, + }, &cli.StringFlag{ Name: "config", TakesFile: true,