Skip to content

Commit c427da4

Browse files
committed
Add script to commit repodata/
Let's match the securedrop-apt-prod process by generating metadata at commit-time instead of doing it on the server. The publish script takes care to generate reproducible output by fixing the mtime of all the RPMs and telling `createrepo_c` what the time should be. CI verifies the generated metadata is up to date and fully reproducible using the `--reproduce` flag.
1 parent e36ef1f commit c427da4

File tree

4 files changed

+102
-0
lines changed

4 files changed

+102
-0
lines changed

.gitattributes

+2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
*.deb filter=lfs diff=lfs merge=lfs -text
22
*.rpm filter=lfs diff=lfs merge=lfs -text
3+
*.gz filter=lfs diff=lfs merge=lfs -text
4+
*.bz2 filter=lfs diff=lfs merge=lfs -text

.github/workflows/ci.yml

+23
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ name: CI
33

44
on: [push, pull_request]
55

6+
defaults:
7+
run:
8+
shell: bash
9+
610
jobs:
711
tests:
812
runs-on: ubuntu-latest
@@ -33,3 +37,22 @@ jobs:
3337
- name: Output the hashes of all rpm artifacts without their signatures
3438
run: |
3539
./scripts/check.py --check-unsigned --all
40+
41+
metadata:
42+
runs-on: ubuntu-latest
43+
container: debian:bookworm
44+
steps:
45+
- name: Install dependencies
46+
run: |
47+
apt-get update && apt-get install --yes python3 git git-lfs createrepo-c
48+
- name: Checkout
49+
uses: actions/checkout@v3
50+
with:
51+
lfs: true
52+
fetch-depth: 0
53+
- name: Check repository metadata is up-to-date
54+
run: |
55+
git config --global --add safe.directory '*'
56+
./tools/publish-real --reproduce
57+
git status
58+
git diff --exit-code

tools/publish

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/bin/bash
2+
# Pull the latest image
3+
podman pull debian:bookworm
4+
# Mount the git repo to /srv, install necessary packages and run the publish script
5+
podman run --rm -it -v $(git rev-parse --show-toplevel):/srv:Z debian:bookworm \
6+
bash -c "apt-get update && apt-get install -y python3 createrepo-c && /srv/tools/publish-real $@"

tools/publish-real

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Script for generating yum repository metadata in a reproducible manner.
4+
5+
Files are copied into public/ and metadata is generated there. All RPMs
6+
have their mtime fixed to a specific timestamp, so the generated XML/SQLite
7+
files will be reproducible.
8+
"""
9+
10+
import os
11+
import shutil
12+
import subprocess
13+
import sys
14+
import time
15+
from pathlib import Path
16+
import xml.etree.ElementTree as ET
17+
18+
19+
def fetch_reproduce_timestamp(public: Path) -> int:
20+
repomd = next(public.glob("workstation/dom0/*/repodata/repomd.xml"))
21+
tree = ET.parse(repomd)
22+
root = tree.getroot()
23+
revision = root.find(
24+
"repo:revision", {"repo": "http://linux.duke.edu/metadata/repo"}
25+
)
26+
print(f"Will use a timestamp of {revision.text} (from {repomd})")
27+
return int(revision.text)
28+
29+
30+
def main():
31+
root = Path(__file__).parent.parent
32+
public = root / "public"
33+
workstation = root / "workstation"
34+
if "--reproduce" in sys.argv:
35+
try:
36+
timestamp = fetch_reproduce_timestamp(public)
37+
except Exception as err:
38+
raise RuntimeError("Failed to fetch timestamp from repomd.xml") from err
39+
else:
40+
# Use the current time
41+
timestamp = int(time.time())
42+
# Reset public, copy the workstation/ tree into it
43+
print("Creating public/ (from scratch)")
44+
if public.exists():
45+
shutil.rmtree(public)
46+
public.mkdir()
47+
shutil.copytree(workstation, public / "workstation")
48+
for rpm in public.glob("**/*.rpm"):
49+
os.utime(rpm, (timestamp, timestamp))
50+
# Folders are public/workstation/dom0/fXX, run createrepo_c in each one
51+
for folder in public.glob("*/*/*/"):
52+
if not folder.is_dir():
53+
continue
54+
print(f"Generating metadata for {folder}")
55+
# The <revision> and <timestamp> fields are set to the current UNIX time
56+
# unless we explicitly override them. Use our fixed time to ensure it's
57+
# consistent regardless of how long this command takes to run.
58+
subprocess.check_call(
59+
[
60+
"createrepo_c",
61+
"--revision",
62+
str(timestamp),
63+
"--set-timestamp-to-revision",
64+
str(folder),
65+
]
66+
)
67+
print("Done!")
68+
69+
70+
if __name__ == "__main__":
71+
main()

0 commit comments

Comments
 (0)