@@ -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.
294306func 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