Skip to content

Make goreleaser archives reproducible#6299

Merged
derekcollison merged 1 commit intonats-io:mainfrom
alexbozhenko:goreleaser_reproducible
Dec 23, 2024
Merged

Make goreleaser archives reproducible#6299
derekcollison merged 1 commit intonats-io:mainfrom
alexbozhenko:goreleaser_reproducible

Conversation

@alexbozhenko
Copy link
Copy Markdown
Member

@alexbozhenko alexbozhenko commented Dec 23, 2024

Use commit time in mod_timestamp, as documented in:
https://goreleaser.com/customization/builds/#reproducible-builds
https://goreleaser.com/blog/reproducible-builds/
https://goreleaser.com/customization/templates/?h=templates#common-fields

Test plan:

Before.

Build two times:

goreleaser release --snapshot --clean -f .goreleaser.yml
mv dist/ ~/tmp/dist_before
goreleaser release --snapshot --clean -f .goreleaser.yml 
vimdiff dist/SHA256SUMS ~/tmp/dist_before/SHA256SUMS

Observe all the shasums are different:
image

After:

Do the build two times,

goreleaser release --snapshot --clean -f .goreleaser.yml
mv dist/ ~/tmp/dist_after
goreleaser release --snapshot --clean -f .goreleaser.yml 
vimdiff dist/SHA256SUMS ~/tmp/dist_after/SHA256SUMS

Observe that only rpm and deb packages are different
image

There was a feature added to goreleaser to make packages reproducible too, but I haven't figured out how to use it yet:
goreleaser/nfpm#748
I asked in Discord. We can tackle that separately

Signed-off-by: Alex Bozhenko alex@synadia.com

@alexbozhenko alexbozhenko force-pushed the goreleaser_reproducible branch from a6e8d60 to 48d31ed Compare December 23, 2024 20:04
Signed-off-by: Alex Bozhenko <alexbozhenko@gmail.com>
@alexbozhenko alexbozhenko force-pushed the goreleaser_reproducible branch from 48d31ed to 7751bc9 Compare December 23, 2024 20:06
@alexbozhenko alexbozhenko changed the title use commit time in mod_timestamp Make goreleaser archives reproducible Dec 23, 2024
@alexbozhenko alexbozhenko marked this pull request as ready for review December 23, 2024 20:15
@alexbozhenko alexbozhenko requested a review from a team as a code owner December 23, 2024 20:15
Copy link
Copy Markdown
Member

@wallyqs wallyqs left a comment

Choose a reason for hiding this comment

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

LGTM, the binary is already reproducible but this would make the tarballs from the release reproducible as well.

@derekcollison derekcollison merged commit c4b778c into nats-io:main Dec 23, 2024
derekcollison added a commit that referenced this pull request Jan 23, 2025
Similar to #6299
Make packages set mtime to `commitdate`.

Upgrade goreleaser to the latest 2.6.1, which includes the feature used
in this PR: goreleaser/goreleaser#5392

```
go install github.com/goreleaser/goreleaser/v2@v2.6.1
goreleaser release --snapshot --clean -f .goreleaser.yml
mv dist/ ~/tmp/dist_before
goreleaser release --snapshot --clean -f .goreleaser.yml 

# now, all the artifacts of the two builds are exactly the same. Only sbom files are different(because those include timestamps)
vimdiff dist/SHA256SUMS ~/tmp/dist_before/SHA256SUMS
```

![image](https://github.com/user-attachments/assets/6ca9bc37-301a-4cda-b4ab-4d724762a31c)



Signed-off-by: Alex Bozhenko <alex@synadia.com>
derekcollison added a commit that referenced this pull request Feb 20, 2025
## Details
We have reproducible binaries,
[rpms/debs](#6359) and
[archives](#6299).
And `goreleaser.yml` holds [all the
knobs](https://goreleaser.com/customization/builds/go/) that can be
tuned to build the binary.

The only thing that affects the build and is not defined in the
goreleaser config is the go toolchain version.
Currently, we set the version of the go toolchain that is used for
releases in Travis:

<https://github.com/nats-io/nats-server/blob/5e6017135b4b1d333b435ff55b720275a39bb7af/.travis.yml#L11-L12>

<https://github.com/nats-io/nats-server/blob/5e6017135b4b1d333b435ff55b720275a39bb7af/.travis.yml#L67>

I spent some time [trying to understand the behavior of go and toolchain
directives](https://alexbozhenko.github.io/posts/2024-12-19-understand-go-toolchain-directive-or-your-money-back/).
I think setting the toolchain used for releases in .goreleser.yml would
make it more explicit and future-proof.


With this change, any human or script who has `go>1.21.0` and goreleaser
installed can checkout the repo at any commit, run one command, and get
binaries that will be _exactly_ as if we were cutting a release on that
commit.

```
goreleaser build --snapshot --clean --single-target
```

Several places would benefit from not having to worry about keeping
toolchain and all the go build flags in sync:

* get-nats.io(already uses gorelaser, but toolchain used to build
depends on the build host):

<https://github.com/ConnectEverything/client-tools/blob/eba999ac9a1e107205fbf89ba230df3f80458028/build-nightlies.sh#L184-L188>
* <redacted> number of private repos

If we land this, we can update the above places to use `goreleaser
build`, thus making sure we use _exactly the same_ binary everywhere,
and forever forget about managing/updating go versions in other places
that need to build the binary

## Reproducible test plan

### Before

Behavior is not hermetic. We depend on toolchain that happens to be
installed on the build host.

1.
    <details>
<summary>Local go(`1.21.0`) < one that is specified in go.mod. Go will
download and use toolchain from go.mod:</summary>

        ```
        # go version
        go version go1.21.0 linux/amd64

# TARGET='linux_amd64' goreleaser build --snapshot --clean
--single-target
# go version -m dist/nats-server_linux_amd64_v1/nats-server | grep go1
        dist/nats-server_linux_amd64_v1/nats-server: go1.22.8
        ```
    </details>

2.
    <details>
<summary>Local go(`1.23.3`) > one that is specified in go.mod. Local
toolchain will be used:</summary>

    ```
    # wget https://go.dev/dl/go1.23.3.linux-amd64.tar.gz
# sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf
go1.23.3.linux-amd64.tar.gz
    # go version
    go version go1.23.3 linux/amd64
    
# TARGET='linux_amd64' goreleaser build --snapshot --clean
--single-target
# go version -m dist/nats-server_linux_amd64_v1/nats-server | grep go1
    dist/nats-server_linux_amd64_v1/nats-server: go1.23.3
    ```

    </details>

### After

1.
    <details>
<summary>Local go version (1.23.4) did not affect what was used for the
binary</summary>

It will download (just like it [downloads all the
modules](https://youtu.be/KqTySYYhPUE?feature=shared&t=229))
the version of the toolchain specified in goreleaer config, and use it
for building the binary.

    ```

    # go version
    go version go1.23.4 linux/amd64

# TARGET='linux_amd64' goreleaser build --snapshot --clean
--single-target
# go version -m dist/nats-server_linux_amd64_v1/nats-server | grep go1
    dist/nats-server_linux_amd64_v1/nats-server: go1.23.5

    # sha256sum  dist/nats-server_linux_amd64_v1/nats-server
95d52ed8656f74abd0c0576d8d9be50fcc00562c8e18215d1f1f04b8c0b6fc3d
dist/nats-server_linux_amd64_v1/nats-server
    ```

    </details>

2.
    <details>
<summary>Any go >= go1.21.0 (released 2023-08-08) will build exactly the
same binary:</summary>

    ```
    # wget <https://go.dev/dl/go1.21.0.linux-amd64.tar.gz>
# sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf
go1.21.0.linux-amd64.tar.gz
    # go version
    go version go1.21.0 linux/amd64

# TARGET='linux_amd64' goreleaser build --snapshot --clean
--single-target
# go version -m dist/nats-server_linux_amd64_v1/nats-server | grep go1
    dist/nats-server_linux_amd64_v1/nats-server: go1.23.5

    # sha256sum  dist/nats-server_linux_amd64_v1/nats-server
95d52ed8656f74abd0c0576d8d9be50fcc00562c8e18215d1f1f04b8c0b6fc3d
dist/nats-server_linux_amd64_v1/nats-server
    ```

    </details>

3. To build using specific toolchain one would set the
`GORELEASER_TOOLCHAIN` env variable:

    ```
# GORELEASER_TOOLCHAIN="go1.22.8" TARGET='linux_amd64' goreleaser build
--snapshot --clean --single-target
# go version -m dist/nats-server_linux_amd64_v1/nats-server | grep go1
    dist/nats-server_linux_amd64_v1/nats-server: go1.22.8
    ```


Note that starting from go1.24.0 we can use the [tool
directive](https://tip.golang.org/doc/modules/managing-dependencies#tools
) in go.mod, and even version goreleaser itself. It will look something
like this:

```
go tool goreleaser build --snapshot --clean --single-target
```
neilalexander added a commit that referenced this pull request Mar 13, 2025
Follow up to #6299
The modification time of LICENSE and README.md in the archive is the one
when the job
https://github.com/nats-io/nats-server/actions/runs/13816873025/job/38652255585
checked out the code:
```
wget https://github.com/nats-io/nats-server/releases/download/v2.11.0-RC.3/nats-server-v2.11.0-RC.3-linux-amd64.tar.gz
tar -xvf nats-server-v2.11.0-RC.3-linux-amd64.tar.gz
ll nats-server-v2.11.0-RC.3-linux-amd64
total 16080
drwxr-xr-x  2 alex alex      100 Mar 12 12:58 .
drwxrwxrwt 48 root root     1280 Mar 12 12:57 ..
-rw-r--r--  1 alex alex    11357 Mar 12 09:54 LICENSE
-rwxr-xr-x  1 alex alex 16442017 Mar 12 09:43 nats-server
-rw-r--r--  1 alex alex     4404 Mar 12 09:54 README.md
```
And that works as designed:
actions/checkout#364 (comment)

Those timestamps make archive not reproducible. Fix it by setting to
commitDate.

## Test plan:
```
 touch LICENSE README.md
```
```
goreleaser  release --snapshot --clean -f .goreleaser.yml
```
Timestamp of all the files is the same(commit date), making archives
reproducible:
```
tar -xvf dist/nats-server-v2.11.0-RC.3-linux-amd64.tar.gz
# ll nats-server-v2.11.0-RC.3-linux-amd64/
total 16088
drwxr-xr-x  2 alex alex     4096 Mar 12 12:53 .
drwxr-xr-x 16 alex alex     4096 Mar 12 12:53 ..
-rw-r--r--  1 alex alex    11357 Mar 12 09:43 LICENSE
-rwxr-xr-x  1 alex alex 16442017 Mar 12 09:43 nats-server
-rw-r--r--  1 alex alex     4404 Mar 12 09:43 README.md
```



Signed-off-by: Alex Bozhenko <alex@synadia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants