Skip to content

Commit 28f66d9

Browse files
committed
cmd/rofl/build: Improve reproducible builds with squashfs-tools
1 parent 085bb7b commit 28f66d9

File tree

1 file changed

+85
-9
lines changed

1 file changed

+85
-9
lines changed

cmd/rofl/build/artifacts.go

Lines changed: 85 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -287,29 +287,105 @@ func ensureBinaryExists(buildEnv env.ExecEnv, name, pkg string) error {
287287
return nil
288288
}
289289

290+
// cleanEnvForReproducibility filters out locale and timestamp environment variables and sets
291+
// consistent values for reproducible builds.
292+
func cleanEnvForReproducibility() []string {
293+
env := []string{}
294+
for _, e := range os.Environ() {
295+
if !strings.HasPrefix(e, "LC_") && !strings.HasPrefix(e, "LANG=") && !strings.HasPrefix(e, "TZ=") && !strings.HasPrefix(e, "SOURCE_DATE_EPOCH=") {
296+
env = append(env, e)
297+
}
298+
}
299+
return append(env, "LC_ALL=C", "TZ=UTC", "SOURCE_DATE_EPOCH=0")
300+
}
301+
290302
// createSquashFs creates a squashfs filesystem in the given file using directory dir to populate
291303
// it.
292304
//
293305
// Returns the size of the created filesystem image in bytes.
294306
func createSquashFs(buildEnv env.ExecEnv, fn, dir string) (int64, error) {
295-
const mkSquashFsBin = "mksquashfs"
296-
if err := ensureBinaryExists(buildEnv, mkSquashFsBin, "squashfs-tools"); err != nil {
307+
const sqfstarBin = "sqfstar"
308+
if err := ensureBinaryExists(buildEnv, sqfstarBin, "squashfs-tools"); err != nil {
309+
return 0, err
310+
}
311+
if err := ensureBinaryExists(buildEnv, "fakeroot", "fakeroot"); err != nil {
297312
return 0, err
298313
}
299314

300-
// Execute mksquashfs.
315+
// Create reproducible tar archive.
316+
tarPath := fn + ".tar"
317+
fmt.Printf("Creating tar archive: %s\n", tarPath)
318+
//nolint:gosec // tarPath is constructed internally, not from user input.
319+
tarCmd := exec.Command(
320+
"tar",
321+
"--create",
322+
"--file="+tarPath,
323+
"--format=gnu",
324+
"--sort=name",
325+
"--mtime=@0",
326+
"--owner=0",
327+
"--group=0",
328+
"--numeric-owner",
329+
"--mode=a-s",
330+
".",
331+
)
332+
tarCmd.Dir = dir
333+
tarCmd.Env = cleanEnvForReproducibility()
334+
335+
var tarOut strings.Builder
336+
tarCmd.Stderr = &tarOut
337+
tarCmd.Stdout = &tarOut
338+
if err := buildEnv.WrapCommand(tarCmd); err != nil {
339+
return 0, err
340+
}
341+
if err := tarCmd.Run(); err != nil {
342+
return 0, fmt.Errorf("failed to create tar archive: %w\n%s", err, tarOut.String())
343+
}
344+
345+
// Compute and print tar archive hash for verification.
346+
tarFile, err := os.Open(tarPath)
347+
if err != nil {
348+
return 0, fmt.Errorf("failed to open tar archive for hashing: %w", err)
349+
}
350+
tarHasher := sha256.New()
351+
if _, err := io.Copy(tarHasher, tarFile); err != nil {
352+
tarFile.Close()
353+
return 0, fmt.Errorf("failed to hash tar archive: %w", err)
354+
}
355+
tarFile.Close()
356+
tarHash := hex.EncodeToString(tarHasher.Sum(nil))
357+
fmt.Printf("TAR archive SHA256: %s\n", tarHash)
358+
359+
// Convert tar to squashfs using sqfstar under fakeroot.
301360
cmd := exec.Command(
302-
mkSquashFsBin,
303-
dir,
361+
"fakeroot",
362+
"--",
363+
sqfstarBin,
304364
fn,
365+
"-reproducible",
305366
"-comp", "gzip",
367+
"-b", "1M",
368+
"-processors", "1",
306369
"-noappend",
307-
"-mkfs-time", "1234",
308-
"-all-time", "1234",
309-
"-root-time", "1234",
370+
"-mkfs-time", "0",
371+
"-all-time", "0",
372+
"-nopad",
310373
"-all-root",
311-
"-reproducible",
374+
"-force-uid", "0",
375+
"-force-gid", "0",
312376
)
377+
378+
cmd.Env = cleanEnvForReproducibility()
379+
380+
// Open tar file and pipe it to sqfstar's stdin.
381+
tarFile, err = os.Open(tarPath)
382+
if err != nil {
383+
return 0, fmt.Errorf("failed to open tar archive: %w", err)
384+
}
385+
defer tarFile.Close()
386+
defer os.Remove(tarPath)
387+
388+
cmd.Stdin = tarFile
313389
var out strings.Builder
314390
cmd.Stderr = &out
315391
cmd.Stdout = &out

0 commit comments

Comments
 (0)