Reproducible build - is it working?

I tried following the instructions for the reproducible build at the link below, using restic-0.8.2.tar.gz
https://github.com/restic/builder

I’ve not been able to match the checksums in the official release.
I also notice that if I run the command twice
docker run --rm --volume $PWD:/home/build restic/builder build.sh restic-0.8.2.tar.gz
I get a different set of checksums in the output folder from one run to the next.

Can anyone reproduce and confirm this? I’m wondering whether I might have set something up incorrectly.

docker version reports Version 17.12.0-ce

Thanks for giving this a try and reporting back! I was always complaining that nobody ever tries to reproduce the binaries :wink:

For the binaries to be exactly the same, the following things need to be at least the same:

  • The Go version
  • The source code
  • The build location
  • And maybe the build architecture and OS (I’m not entirely sure about that)

The builder image usually takes care of that, but you need to use exactly the same version that was current at the time the binaries were built.

Please retry with the latest version of restic, 0.8.3. Just before that release, I updated the scripts and the docker container, so maybe it’s that.

Which checksums differ? can you be a bit more specific?

What did you do exactly? Did you also build the container yourself? If not, what’s the image ID (run docker images)?

I’ve just discovered that Go 1.10 changed something and the compiler argument I’m using in build.go to remove the temporary directory from the binary does not work any more. Hm. The binaries will contain the temporary directory several times, which will change with every run:

$ strings restic | grep tmp | tail
/tmp/restic-build-630783517/src/github.com/restic/restic/vendor/github.com/spf13/pflag/golangflag.go
/tmp/restic-build-630783517/src/github.com/restic/restic/vendor/github.com/spf13/pflag/float64.go
/tmp/restic-build-630783517/src/github.com/restic/restic/vendor/github.com/spf13/pflag/float32.go
/tmp/restic-build-630783517/src/github.com/restic/restic/vendor/github.com/spf13/pflag/flag.go
/tmp/restic-build-630783517/src/github.com/restic/restic/vendor/github.com/spf13/pflag/duration.go
/tmp/restic-build-630783517/src/github.com/restic/restic/vendor/github.com/spf13/pflag/count.go
/tmp/restic-build-630783517/src/github.com/restic/restic/vendor/github.com/spf13/pflag/bool_slice.go
/tmp/restic-build-630783517/src/github.com/restic/restic/vendor/github.com/spf13/pflag/bool.go
/tmp/restic-build-630783517/src/github.com/restic/restic/internal/debug/debug_release.go
/tmp/restic-build-630783517/src/github.com/restic/restic/internal/backend/http_transport.go

Thanks for the response!

I just reran this with the new release 0.8.3. My docker image ID is ‘5590a1ad68f3’. I’m not very familiar with using docker. Should that ID exactly match yours?

When I said the checksums differ, I mean that the docker run... command appears to complete successfully, and creates a new directory (with a timestamp name) with a bunch of .bz2 output files. The checksums of those files don’t match the ones downloadable from https://github.com/restic/restic/releases/download/v0.8.3/SHA256SUMS
Also, when I re-run the same command, I again get a different set of checksums in the new directory. That suggests to me that perhaps the timestamp is getting embedded in the builds (so they would never be reproducible). But my lack of familiarity with docker may mean I am mistaken.

It’s not docker, it seems Go 1.10 has changed something that we now need to work around.

Aha… what makes you think so?

The program build.go will create a new GOPATH (e.g. in /tmp/restic-build-630783517) in a temporary directory, and then passes -gcflags -trimpath=/tmp/restic-build-630783517 to the compiler to make it strip the directory prefix and only record relative files in the resulting binary.

In the post above I showed the strings that I found in the binary, which still contains the temp dir.

Since the temp dir is different every time, the binaries will be different.

That makes sense. I’m afraid I don’t know how to fix that.

Just to avoid ambiguity, a small suggestion: maybe in future it would be worth adding a line to the release page specifying which commit from the builder repo should be used to reproduce the build. (I know it would normally be possible to figure out from the timestamps).

Reproducing the build requires a newer build.go, but then you can reproduce the official binaries as follows:

Run a new instance of the builder container:

$ docker run --rm -ti --volume $PWD:/home/build restic/builder

Within the container:

$ wget https://github.com/restic/restic/releases/download/v0.8.3/restic-0.8.3.tar.gz
[...]
$ tar xfz restic-0.8.3.tar.gz
$ cd restic-0.8.3
$ rm build.go
$ wget https://raw.githubusercontent.com/restic/restic/master/build.go
[...]

$ go run build.go --tempdir /tmp/restic-build-413028034 --goos linux --goarch amd64 -o restic_0.8.3_linux_amd64
$ bzip2 restic_0.8.3_linux_amd64
$ wget https://github.com/restic/restic/releases/download/v0.8.3/SHA256SUMS
[...]
$ sha256sum -c SHA256SUMS
[...]
restic_0.8.3_linux_amd64.bz2: OK
[...]

You could also patch build.sh to pass exactly this temp dir to build.go, to get the other files. Please note that the tempdir is different for each OS and architecture build of restic.

I’ll see how we can fix this. Sigh.

I see what you’ve done there. Just to confirm, I can now reproduce the same binary! Thank you.

1 Like

Turns out, I’m holding it wrong :slight_smile:

Go 1.10 changed the behavior for -gcflags, we now need to pass the prefix all= to get trimpath= applied to all packages, like this:

$ go build -gcflags=all=-trimpath=$GOPATH -asmflags=all=-trimpath=$GOPATH -o restic

I’ll integrate that into build.go, so the next restic release can be reproduced easier…

1 Like

I’ve fixed build.go so that it correctly generates easily reproducible binaries for the next release. For 0.8.3, we’ll keep it this way. I’ll add a note to the release notes.

1 Like