Skip to content

Commit 970be2b

Browse files
committed
Simplify
1 parent 29526ea commit 970be2b

File tree

7 files changed

+171
-88
lines changed

7 files changed

+171
-88
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
name: docker-rofl-container-builder
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
paths:
8+
- docker/rofl-container-builder/**
9+
- .github/workflows/docker-rofl-container-builder.yml
10+
tags:
11+
- 'rofl-container-builder/v[0-9]+.[0-9]+*'
12+
pull_request:
13+
paths:
14+
- docker/rofl-container-builder/**
15+
- .github/workflows/docker-rofl-container-builder.yml
16+
17+
permissions:
18+
contents: read
19+
packages: write
20+
21+
jobs:
22+
build-rofl-container-builder:
23+
name: build-rofl-container-builder
24+
runs-on: ubuntu-latest
25+
steps:
26+
- name: Checkout code
27+
uses: actions/checkout@v4
28+
with:
29+
ref: ${{ github.event.pull_request.head.sha }}
30+
31+
- name: Determine tag name
32+
id: determine-tag
33+
shell: bash
34+
run: |
35+
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
36+
echo "tag=pr-${{ github.event.pull_request.number }}" >> "$GITHUB_OUTPUT"
37+
elif [[ "${{ github.ref }}" == refs/tags/* ]]; then
38+
# Trim rofl-container-builder/v prefix from tag
39+
TAG="${{ github.ref_name }}"
40+
TAG="${TAG#rofl-container-builder/v}"
41+
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
42+
else
43+
echo "tag=latest" >> "$GITHUB_OUTPUT"
44+
fi
45+
echo "created=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> "$GITHUB_OUTPUT"
46+
47+
- name: Set up Docker Buildx
48+
uses: docker/setup-buildx-action@v3
49+
50+
- name: Login to ghcr.io
51+
uses: docker/login-action@v3
52+
with:
53+
registry: ghcr.io
54+
username: ${{ github.actor }}
55+
password: ${{ secrets.GITHUB_TOKEN }}
56+
57+
- name: "Build and push oasisprotocol/rofl-container-builder:${{ steps.determine-tag.outputs.tag }}"
58+
uses: docker/build-push-action@v6
59+
with:
60+
context: docker/rofl-container-builder
61+
file: docker/rofl-container-builder/Dockerfile
62+
tags: ghcr.io/oasisprotocol/rofl-container-builder:${{ steps.determine-tag.outputs.tag }}
63+
pull: true
64+
push: true
65+
labels: |
66+
org.opencontainers.image.source=${{ github.event.repository.html_url }}
67+
org.opencontainers.image.created=${{ steps.determine-tag.outputs.created }}
68+
org.opencontainers.image.revision=${{ github.sha }}
69+
70+
prune-old-images:
71+
name: prune-old-images
72+
if: ${{ always() }}
73+
needs: [build-rofl-container-builder]
74+
runs-on: ubuntu-latest
75+
steps:
76+
- name: Prune old ghcr.io/oasisprotocol/rofl-container-builder images
77+
uses: vlaurin/[email protected]
78+
with:
79+
token: ${{ secrets.GITHUB_TOKEN }}
80+
organization: oasisprotocol
81+
container: rofl-container-builder
82+
keep-younger-than: 7
83+
keep-last: 2
84+
prune-tags-regexes: ^pr-

build/rofl/artifacts.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
package rofl
22

3-
// LatestBuilderImage is the latest builder container image to use when building ROFL apps.
4-
const LatestBuilderImage = "ghcr.io/oasisprotocol/rofl-dev:v0.5.0@sha256:31573686552abeb0edebc450f6872831f0006a6cf38220cef7e0789d4376c2c1"
3+
// Builder images for different app kinds.
4+
const (
5+
// LatestBuilderImage is the full builder with Rust toolchain for raw apps.
6+
LatestBuilderImage = "ghcr.io/oasisprotocol/rofl-dev:v0.5.0@sha256:31573686552abeb0edebc450f6872831f0006a6cf38220cef7e0789d4376c2c1"
7+
// LatestContainerBuilderImage is the minimal builder for container apps.
8+
LatestContainerBuilderImage = "ghcr.io/oasisprotocol/rofl-container-builder:0.0.1@sha256:913ef97ab07dde31f08ce873f825bf3d4f32ad4102ff5797d7c3050c121c4dce"
9+
)
510

611
// LatestBasicArtifacts are the latest TDX ROFL basic app artifacts.
712
var LatestBasicArtifacts = ArtifactsConfig{

cmd/rofl/build/artifacts.go

Lines changed: 56 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -137,18 +137,33 @@ func cleanEnvForReproducibility() []string {
137137
return append(env, "LC_ALL=C", "TZ=UTC", "SOURCE_DATE_EPOCH=0")
138138
}
139139

140-
// ExtraFile represents a file to be injected into the rootfs.
141-
type ExtraFile struct {
142-
HostPath string // Path on host filesystem
143-
TarPath string // Path inside the tar/rootfs (e.g., "init" or "etc/config")
144-
Mode os.FileMode // File mode (e.g., 0o755 for executables)
140+
// extraFile represents a file to be injected into the rootfs.
141+
type extraFile struct {
142+
HostPath string
143+
TarPath string
144+
Mode os.FileMode
145+
}
146+
147+
// normalizeHeader normalizes a tar header for reproducible builds.
148+
// Uses GNU format because sqfstar < 4.6 has a bug with PAX headers
149+
// that incorrectly strips pathname components in linkpath for symlinks.
150+
func normalizeHeader(header *tar.Header) {
151+
header.Format = tar.FormatGNU
152+
header.Uid = 0
153+
header.Gid = 0
154+
header.Uname = ""
155+
header.Gname = ""
156+
header.ModTime = time.Unix(0, 0)
157+
header.AccessTime = time.Time{}
158+
header.ChangeTime = time.Time{}
159+
header.PAXRecords = nil
145160
}
146161

147162
// createSquashFsFromTar creates a squashfs filesystem by streaming a tar.bz2 template
148163
// and injecting extra files, without extracting to disk.
149164
//
150165
// Returns the size of the created filesystem image in bytes.
151-
func createSquashFsFromTar(buildEnv env.ExecEnv, outputFn, templateFn string, extraFiles []ExtraFile) (int64, error) {
166+
func createSquashFsFromTar(buildEnv env.ExecEnv, outputFn, templateFn string, extraFiles []extraFile) (int64, error) {
152167
const (
153168
sqfstarBin = "sqfstar"
154169
fakerootBin = "fakeroot"
@@ -216,114 +231,83 @@ func createSquashFsFromTar(buildEnv env.ExecEnv, outputFn, templateFn string, ex
216231
tarHasher := sha256.New()
217232
tarWriter := tar.NewWriter(io.MultiWriter(stdinPipe, tarHasher))
218233

234+
// Ensure cleanup on error.
235+
var cmdErr error
236+
defer func() {
237+
if cmdErr != nil {
238+
tarWriter.Close()
239+
stdinPipe.Close()
240+
cmd.Wait() //nolint:errcheck
241+
}
242+
}()
243+
219244
// Build a map of extra files by their tar path for quick lookup.
220-
extraFileMap := make(map[string]ExtraFile)
245+
extraFileMap := make(map[string]extraFile)
221246
for _, ef := range extraFiles {
222247
extraFileMap[ef.TarPath] = ef
223248
}
224249

225-
// Copy entries from template, potentially skipping ones we'll replace.
250+
// Copy entries from template, skipping ones we'll replace.
226251
for {
227252
header, err := tarReader.Next()
228253
if errors.Is(err, io.EOF) {
229254
break
230255
}
231256
if err != nil {
232-
tarWriter.Close()
233-
stdinPipe.Close()
234-
cmd.Wait() //nolint:errcheck
235-
return 0, fmt.Errorf("error reading template archive: %w", err)
236-
}
237-
238-
// Skip PAX/GNU special headers - these are metadata entries that tar.Reader
239-
// normally handles internally, but if we see them we should not emit them.
240-
// They have bodies that must be drained.
241-
switch header.Typeflag {
242-
case tar.TypeXHeader, tar.TypeXGlobalHeader, tar.TypeGNULongName, tar.TypeGNULongLink:
243-
//nolint:gosec // G110: This is trusted input from our own template archives.
244-
if _, err := io.Copy(io.Discard, tarReader); err != nil {
245-
tarWriter.Close()
246-
stdinPipe.Close()
247-
cmd.Wait() //nolint:errcheck
248-
return 0, fmt.Errorf("failed to skip special header content: %w", err)
249-
}
250-
continue
257+
cmdErr = fmt.Errorf("error reading template archive: %w", err)
258+
return 0, cmdErr
251259
}
252260

253-
// Normalize the path (remove leading ./).
261+
// Normalize the path.
254262
cleanPath := strings.TrimPrefix(header.Name, "./")
255263

256264
// Skip if this path will be replaced by an extra file.
257265
if _, willReplace := extraFileMap[cleanPath]; willReplace {
258-
// Skip this entry, we'll add the replacement later.
259-
if header.Typeflag == tar.TypeReg || header.Typeflag == tar.TypeRegA { //nolint:staticcheck // TypeRegA for backward compat
260-
// Drain the content.
261-
//nolint:gosec // G110: This is trusted input from our own template archives.
262-
if _, err := io.Copy(io.Discard, tarReader); err != nil {
263-
tarWriter.Close()
264-
stdinPipe.Close()
265-
cmd.Wait() //nolint:errcheck
266-
return 0, fmt.Errorf("failed to skip replaced file content: %w", err)
267-
}
268-
}
269266
continue
270267
}
271268

272269
// Normalize header for reproducibility.
273-
// Use GNU format because sqfstar < 4.6 has a bug with PAX headers
274-
// that incorrectly strips pathname components in linkpath for symlinks.
275-
header.Format = tar.FormatGNU
276-
header.Uid = 0
277-
header.Gid = 0
278-
header.Uname = ""
279-
header.Gname = ""
280-
header.ModTime = time.Unix(0, 0)
281-
header.AccessTime = time.Time{} // Zero value = not set
282-
header.ChangeTime = time.Time{} // Zero value = not set
283-
header.PAXRecords = nil
270+
normalizeHeader(header)
284271

285272
if err := tarWriter.WriteHeader(header); err != nil {
286-
tarWriter.Close()
287-
stdinPipe.Close()
288-
cmd.Wait() //nolint:errcheck
289-
return 0, fmt.Errorf("failed to write tar header: %w", err)
273+
cmdErr = fmt.Errorf("failed to write tar header: %w", err)
274+
return 0, cmdErr
290275
}
291276

292-
// Copy body for entry types that have content (regular files).
293-
if header.Typeflag == tar.TypeReg || header.Typeflag == tar.TypeRegA { //nolint:staticcheck // TypeRegA for backward compat
294-
//nolint:gosec // G110: This is trusted input from our own template archives.
295-
if _, err := io.Copy(tarWriter, tarReader); err != nil {
296-
tarWriter.Close()
297-
stdinPipe.Close()
298-
cmd.Wait() //nolint:errcheck
299-
return 0, fmt.Errorf("failed to copy file content: %w", err)
277+
// Copy body for regular files.
278+
if header.Typeflag == tar.TypeReg {
279+
const maxFileSize = 500 * 1024 * 1024 // 500 MB sanity limit
280+
if header.Size > maxFileSize {
281+
cmdErr = fmt.Errorf("file too large: %s (%d bytes)", header.Name, header.Size)
282+
return 0, cmdErr
283+
}
284+
if _, err := io.Copy(tarWriter, io.LimitReader(tarReader, header.Size)); err != nil {
285+
cmdErr = fmt.Errorf("failed to copy file content: %w", err)
286+
return 0, cmdErr
300287
}
301288
}
302289
}
303290

304291
// Add extra files.
305292
for _, ef := range extraFiles {
306293
if err := addFileToTar(tarWriter, ef.HostPath, ef.TarPath, ef.Mode); err != nil {
307-
tarWriter.Close()
308-
stdinPipe.Close()
309-
cmd.Wait() //nolint:errcheck
310-
return 0, fmt.Errorf("failed to add extra file %s: %w", ef.TarPath, err)
294+
cmdErr = fmt.Errorf("failed to add extra file %s: %w", ef.TarPath, err)
295+
return 0, cmdErr
311296
}
312297
}
313298

314299
// Close tar writer and stdin pipe.
315300
if err := tarWriter.Close(); err != nil {
316-
stdinPipe.Close()
317-
cmd.Wait() //nolint:errcheck
318-
return 0, fmt.Errorf("failed to close tar writer: %w", err)
301+
cmdErr = fmt.Errorf("failed to close tar writer: %w", err)
302+
return 0, cmdErr
319303
}
320304

321305
// Print tar hash for verification.
322306
fmt.Printf("TAR archive SHA256: %s\n", hex.EncodeToString(tarHasher.Sum(nil)))
323307

324308
if err := stdinPipe.Close(); err != nil {
325-
cmd.Wait() //nolint:errcheck
326-
return 0, fmt.Errorf("failed to close stdin pipe: %w", err)
309+
cmdErr = fmt.Errorf("failed to close stdin pipe: %w", err)
310+
return 0, cmdErr
327311
}
328312

329313
// Wait for sqfstar to finish.
@@ -353,15 +337,12 @@ func addFileToTar(tw *tar.Writer, hostPath, tarPath string, mode os.FileMode) er
353337
}
354338

355339
header := &tar.Header{
356-
Format: tar.FormatGNU,
357340
Name: "./" + tarPath,
358341
Mode: int64(mode),
359342
Size: fi.Size(),
360-
Uid: 0,
361-
Gid: 0,
362-
ModTime: time.Unix(0, 0),
363343
Typeflag: tar.TypeReg,
364344
}
345+
normalizeHeader(header)
365346

366347
if err := tw.WriteHeader(header); err != nil {
367348
return fmt.Errorf("failed to write header: %w", err)

cmd/rofl/build/container.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ func tdxBuildContainer(
3333
// Use the pre-built container runtime.
3434
initPath := artifacts[artifactContainerRuntime]
3535

36-
stage2, err := tdxPrepareStage2(buildEnv, tmpDir, artifacts, initPath, map[string]string{
37-
artifacts[artifactContainerCompose]: "etc/oasis/containers/compose.yaml",
36+
stage2, err := tdxPrepareStage2(buildEnv, tmpDir, artifacts, initPath, []extraFile{
37+
{HostPath: artifacts[artifactContainerCompose], TarPath: "etc/oasis/containers/compose.yaml", Mode: 0o644},
3838
})
3939
if err != nil {
4040
return err

cmd/rofl/build/tdx.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ func tdxPrepareStage2(
132132
tmpDir string,
133133
artifacts map[string]string,
134134
initPath string,
135-
extraFiles map[string]string,
135+
extraFiles []extraFile,
136136
) (*tdxStage2, error) {
137137
fmt.Println("Preparing stage 2 root filesystem...")
138138

@@ -144,12 +144,10 @@ func tdxPrepareStage2(
144144
fmt.Printf("Runtime hash: %s\n", initHash)
145145

146146
// Build list of files to inject into the rootfs.
147-
filesToInject := []ExtraFile{
147+
filesToInject := []extraFile{
148148
{HostPath: initPath, TarPath: "init", Mode: 0o755},
149149
}
150-
for src, dst := range extraFiles {
151-
filesToInject = append(filesToInject, ExtraFile{HostPath: src, TarPath: dst, Mode: 0o644})
152-
}
150+
filesToInject = append(filesToInject, extraFiles...)
153151

154152
// Create the root filesystem by streaming template and injecting files.
155153
fmt.Println("Creating squashfs filesystem...")

cmd/rofl/mgmt.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,16 +121,15 @@ var (
121121
switch appKind {
122122
case buildRofl.AppKindRaw:
123123
artifacts := buildRofl.LatestBasicArtifacts // Copy.
124+
artifacts.Builder = buildRofl.LatestBuilderImage
124125
manifest.Artifacts = &artifacts
125126
case buildRofl.AppKindContainer:
126127
artifacts := buildRofl.LatestContainerArtifacts // Copy.
128+
artifacts.Builder = buildRofl.LatestContainerBuilderImage
127129
artifacts.Container.Compose = detectOrCreateComposeFile()
128130
manifest.Artifacts = &artifacts
129131
default:
130132
}
131-
if manifest.Artifacts != nil {
132-
manifest.Artifacts.Builder = buildRofl.LatestBuilderImage
133-
}
134133
default:
135134
}
136135

@@ -564,11 +563,12 @@ var (
564563
switch manifest.Kind {
565564
case buildRofl.AppKindRaw:
566565
latestArtifacts = buildRofl.LatestBasicArtifacts // Copy.
566+
latestArtifacts.Builder = buildRofl.LatestBuilderImage
567567
case buildRofl.AppKindContainer:
568568
latestArtifacts = buildRofl.LatestContainerArtifacts // Copy.
569+
latestArtifacts.Builder = buildRofl.LatestContainerBuilderImage
569570
default:
570571
}
571-
latestArtifacts.Builder = buildRofl.LatestBuilderImage
572572
default:
573573
}
574574

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
FROM debian:bookworm-slim
2+
3+
ARG DEBIAN_FRONTEND=noninteractive
4+
5+
RUN apt-get update -qq && \
6+
apt-get install -qq --no-install-recommends \
7+
squashfs-tools \
8+
cryptsetup-bin \
9+
qemu-utils \
10+
fakeroot \
11+
&& apt-get clean && \
12+
rm -rf /var/lib/apt/lists/*
13+
14+
VOLUME /src
15+
WORKDIR /src

0 commit comments

Comments
 (0)