@@ -287,29 +287,108 @@ 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 (
308+ sqfstarBin = "sqfstar"
309+ fakerootBin = "fakeroot"
310+ )
311+ if err := ensureBinaryExists (buildEnv , sqfstarBin , "squashfs-tools" ); err != nil {
312+ return 0 , err
313+ }
314+ if err := ensureBinaryExists (buildEnv , fakerootBin , fakerootBin ); err != nil {
315+ return 0 , err
316+ }
317+
318+ // Create reproducible tar archive.
319+ tarPath := fn + ".tar"
320+ fmt .Printf ("Creating tar archive: %s\n " , tarPath )
321+ //nolint:gosec // tarPath is constructed internally, not from user input.
322+ tarCmd := exec .Command (
323+ "tar" ,
324+ "--create" ,
325+ "--file=" + tarPath ,
326+ "--format=gnu" ,
327+ "--sort=name" ,
328+ "--mtime=@0" ,
329+ "--owner=0" ,
330+ "--group=0" ,
331+ "--numeric-owner" ,
332+ "--mode=a-s" ,
333+ "." ,
334+ )
335+ tarCmd .Dir = dir
336+ tarCmd .Env = cleanEnvForReproducibility ()
337+
338+ var tarOut strings.Builder
339+ tarCmd .Stderr = & tarOut
340+ tarCmd .Stdout = & tarOut
341+ if err := buildEnv .WrapCommand (tarCmd ); err != nil {
297342 return 0 , err
298343 }
344+ if err := tarCmd .Run (); err != nil {
345+ return 0 , fmt .Errorf ("failed to create tar archive: %w\n %s" , err , tarOut .String ())
346+ }
347+
348+ // Compute and print tar archive hash for verification.
349+ tarFile , err := os .Open (tarPath )
350+ if err != nil {
351+ return 0 , fmt .Errorf ("failed to open tar archive for hashing: %w" , err )
352+ }
353+ tarHasher := sha256 .New ()
354+ if _ , err := io .Copy (tarHasher , tarFile ); err != nil {
355+ tarFile .Close ()
356+ return 0 , fmt .Errorf ("failed to hash tar archive: %w" , err )
357+ }
358+ tarFile .Close ()
359+ tarHash := hex .EncodeToString (tarHasher .Sum (nil ))
360+ fmt .Printf ("TAR archive SHA256: %s\n " , tarHash )
299361
300- // Execute mksquashfs .
362+ // Convert tar to squashfs using sqfstar under fakeroot .
301363 cmd := exec .Command (
302- mkSquashFsBin ,
303- dir ,
364+ fakerootBin ,
365+ "--" ,
366+ sqfstarBin ,
304367 fn ,
368+ "-reproducible" ,
305369 "-comp" , "gzip" ,
370+ "-b" , "1M" ,
371+ "-processors" , "1" ,
306372 "-noappend" ,
307- "-mkfs-time" , "1234 " ,
308- "-all-time" , "1234 " ,
309- "-root-time" , "1234 " ,
373+ "-mkfs-time" , "0 " ,
374+ "-all-time" , "0 " ,
375+ "-nopad " ,
310376 "-all-root" ,
311- "-reproducible" ,
377+ "-force-uid" , "0" ,
378+ "-force-gid" , "0" ,
312379 )
380+
381+ cmd .Env = cleanEnvForReproducibility ()
382+
383+ // Open tar file and pipe it to sqfstar's stdin.
384+ tarFile , err = os .Open (tarPath )
385+ if err != nil {
386+ return 0 , fmt .Errorf ("failed to open tar archive: %w" , err )
387+ }
388+ defer tarFile .Close ()
389+ defer os .Remove (tarPath )
390+
391+ cmd .Stdin = tarFile
313392 var out strings.Builder
314393 cmd .Stderr = & out
315394 cmd .Stdout = & out
0 commit comments