Add SOURCE_DATE_EPOCH support for reproducible builds

Implement support for the SOURCE_DATE_EPOCH environment variable as
specified by reproducible-builds.org. When set, this variable overrides
the current timestamp in the Release file's Date and Valid-Until fields,
enabling reproducible filesystem publishes.

- Read SOURCE_DATE_EPOCH environment variable in Publish()
- Use the epoch timestamp for both Date and Valid-Until fields
- Gracefully fallback to current time if unset or invalid
- Add comprehensive tests for valid and invalid SOURCE_DATE_EPOCH values
This commit is contained in:
Tim Foerster
2026-02-20 06:01:53 +00:00
parent a65f79eb79
commit 49f342878a
8 changed files with 119 additions and 3 deletions
+10 -3
View File
@@ -9,6 +9,7 @@ import (
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"time"
@@ -1136,8 +1137,14 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
release["Suite"] = p.GetSuite()
release["Codename"] = p.GetCodename()
datetime_format := "Mon, 2 Jan 2006 15:04:05 MST"
date_now := time.Now().UTC()
release["Date"] = date_now.Format(datetime_format)
publishDate := time.Now().UTC()
if epoch := os.Getenv("SOURCE_DATE_EPOCH"); epoch != "" {
if sec, err := strconv.ParseInt(epoch, 10, 64); err == nil {
publishDate = time.Unix(sec, 0).UTC()
}
}
release["Date"] = publishDate.Format(datetime_format)
release["Architectures"] = strings.Join(utils.StrSlicesSubstract(p.Architectures, []string{ArchitectureSource}), " ")
if p.AcquireByHash {
release["Acquire-By-Hash"] = "yes"
@@ -1149,7 +1156,7 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
// is not present or if it is expired."
release["Signed-By"] = p.SignedBy
// Let's use a century as a "forever" value.
release["Valid-Until"] = date_now.AddDate(100, 0, 0).Format(datetime_format)
release["Valid-Until"] = publishDate.AddDate(100, 0, 0).Format(datetime_format)
}
release["Description"] = " Generated by aptly\n"
release["MD5Sum"] = ""
+41
View File
@@ -433,6 +433,47 @@ func (s *PublishedRepoSuite) TestPublishNoSigner(c *C) {
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/squeeze/main/binary-i386/Release"), PathExists)
}
func (s *PublishedRepoSuite) TestPublishSourceDateEpoch(c *C) {
// Test with SOURCE_DATE_EPOCH set
_ = os.Setenv("SOURCE_DATE_EPOCH", "1234567890")
defer os.Unsetenv("SOURCE_DATE_EPOCH")
err := s.repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false, "")
c.Assert(err, IsNil)
rf, err := os.Open(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/squeeze/Release"))
c.Assert(err, IsNil)
defer rf.Close()
cfr := NewControlFileReader(rf, true, false)
st, err := cfr.ReadStanza()
c.Assert(err, IsNil)
// Expected date for Unix timestamp 1234567890: Fri, 13 Feb 2009 23:31:30 UTC
c.Check(st["Date"], Equals, "Fri, 13 Feb 2009 23:31:30 UTC")
}
func (s *PublishedRepoSuite) TestPublishSourceDateEpochInvalid(c *C) {
// Test with invalid SOURCE_DATE_EPOCH (should fallback to current time)
_ = os.Setenv("SOURCE_DATE_EPOCH", "invalid")
defer os.Unsetenv("SOURCE_DATE_EPOCH")
err := s.repo2.Publish(s.packagePool, s.provider, s.factory, nil, nil, false, "")
c.Assert(err, IsNil)
rf, err := os.Open(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/maverick/Release"))
c.Assert(err, IsNil)
defer rf.Close()
cfr := NewControlFileReader(rf, true, false)
st, err := cfr.ReadStanza()
c.Assert(err, IsNil)
// Should have a valid Date field (not empty, not the fixed date from SOURCE_DATE_EPOCH)
c.Check(st["Date"], Not(Equals), "")
c.Check(st["Date"], Not(Equals), "Fri, 13 Feb 2009 23:31:30 UTC")
}
func (s *PublishedRepoSuite) TestPublishLocalRepo(c *C) {
err := s.repo2.Publish(s.packagePool, s.provider, s.factory, nil, nil, false, "")
c.Assert(err, IsNil)