Skip to content

Commit 1301d1e

Browse files
committed
bootc: Adding a README to generic-iso
This adds a README to the root of the ISO that describes how to build it using image-builder. This also adds a somewhat generic method for adding files from the data store to the ISO. Resolves: HMS-10627
1 parent ca8cf25 commit 1301d1e

8 files changed

Lines changed: 219 additions & 4 deletions

data/files/iso/generic/README

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# bootc generic-iso
2+
3+
This ISO contains a bootable bootc root filesystem in /LiveOS/squashfs.img, the ISO
4+
is bootable as an ISO or as an image written to a USB flash drive.
5+
6+
This ISO is created from a bootc container using image-builder. You create a
7+
custom container using podman, then run image-builder to turn it into an ISO.
8+
9+
## bootc container
10+
11+
The bootc container has a few requirements:
12+
13+
* Be based on a bootc container, eg. quay.io/fedora/fedora-bootc:latest
14+
* Include the dracut-live, erofs-utils packages
15+
* Include grub2 ISO bootloader related tools
16+
- grub2-efi-*-cdboot xorriso isomd5sum shim
17+
* Configure dracut to add the dmsquash-live module
18+
* Configure ostree to not use composefs
19+
* Rebuild the initramfs so that it includes the dmsquash-live module
20+
* Optionally setup the ISO menus and kernel cmdline with iso.yaml
21+
22+
## Sample Fedora bootc container
23+
24+
This is a simple example using the `image-builder` cmdline tool and a local install of podman.
25+
26+
Save this in `Containerfile`:
27+
28+
```
29+
FROM quay.io/fedora/fedora-bootc:latest
30+
RUN dnf -y install grub2-efi-*-cdboot xorriso isomd5sum dracut-live erofs-utils shim && dnf clean all
31+
RUN mkdir /boot/efi && cp -r /usr/lib/efi/shim/*/EFI /boot/efi && cp -r /usr/lib/efi/grub2/*/EFI/* /boot/efi/EFI/
32+
33+
# Override using composefs for ostree (it is incompatible with the erofs rootfs)
34+
RUN cat <<EOF > /usr/lib/ostree/prepare-root.conf
35+
[composefs]
36+
enabled = no
37+
[sysroot]
38+
readonly = true
39+
EOF
40+
41+
# Include the dmsquash-live module in the initramfs
42+
RUN cat <<EOF > /usr/lib/dracut/dracut.conf.d/40-pxe.conf
43+
compress="xz"
44+
add_dracutmodules+=" qemu qemu-net livenet dmsquash-live "
45+
early_microcode="no"
46+
EOF
47+
48+
# Override the default ISO menus
49+
RUN mkdir -p /usr/lib/image-builder/bootc
50+
RUN cat <<EOF > /usr/lib/image-builder/bootc/iso.yaml
51+
label: bootc-generic
52+
kernel_args:
53+
- console=ttyS0
54+
grub2:
55+
timeout: 5
56+
entries:
57+
- name: Boot Linux
58+
linux: \${kernelpath} \${root}
59+
initrd: \${initrdpath}
60+
- name: Boot Linux With debug
61+
linux: \${kernelpath} \${root} rd.debug=1
62+
initrd: \${initrdpath}
63+
EOF
64+
65+
# Rebuild the initrd
66+
RUN set -xe; kver=$(ls /usr/lib/modules); env DRACUT_NO_XATTR=1 dracut -vf /usr/lib/modules/$kver/initramfs.img "$kver"
67+
68+
# Mask services that aren't compatible with running from an ISO
69+
RUN systemctl mask bootc-generic-growpart.service bootc-publish-rhsm-facts.service bootloader-update.service rpm-ostree-fix-shadow-mode.service
70+
71+
RUN bootc container lint
72+
```
73+
74+
Build this container using podman:
75+
```
76+
podman build -f ./Containerfile -t bootc-iso
77+
```
78+
79+
Run `image-builder` to create the ISO:
80+
```
81+
image-builder build --bootc-default-fs ext4 --bootc-ref localhost/bootc-iso:latest generic-iso
82+
```
83+
84+
If your container is on a remote system replace the
85+
`localhost/bootc-iso:latest` with the right url.
86+
87+
## Sample CentOS 10 bootc container
88+
89+
The CentOS container is slightly different from the Fedora container due to the
90+
bootloader files being in a different location.
91+
92+
Replace the top 3 lines with:
93+
```
94+
FROM quay.io/centos/centos-bootc:c10s
95+
RUN dnf -y install grub2-efi-*-cdboot xorriso isomd5sum dracut-live erofs-utils shim && dnf clean all
96+
RUN cp -r /usr/lib/bootupd/updates/EFI/* /boot/efi/EFI/
97+
```
98+
99+
The remainder of the Containerfile is identical to the Fedora example.
100+
101+
# User config
102+
103+
When building the ISO you can use the `image-builder --blueprint user.toml`
104+
option to customize the users, including root. See the documentation at
105+
https://osbuild.org/docs/user-guide/blueprint-reference/#additional-users
106+
107+
For example, to set the root password use a minimal blueprint like this:
108+
```
109+
name = "setup-root"
110+
version = "1.0.0"
111+
112+
[[customizations.user]]
113+
name = "root"
114+
password = "root-password"
115+
```
116+
117+
And run `image-builder` like so:
118+
```
119+
image-builder build --bootc-default-fs ext4 --bootc-ref localhost/bootc-iso:latest --blueprint setup-root.toml generic-iso
120+
```
121+
122+
# Troubleshooting
123+
124+
You can inspect the container you built by running bash:
125+
126+
```
127+
podman run --rm -it localhost/bootc-iso:latest /usr/bin/bash
128+
```
129+
130+
Check the contents of `/usr/lib/ostree/prepare-root.conf` and
131+
`/usr/lib/dracut/dracut.conf.d/40-pxe.conf` to make sure they were created
132+
correctly. You can also run `lsinitrd --mod /usr/lib/modules/*/initramfs.img`
133+
to check to make sure the new initramfs contains the dmsquash-live and ostree
134+
modules.
135+
136+
## Fails to mount the OSTree root
137+
138+
If you get an error like:
139+
140+
ostree-prepare-root[848]: ostree-prepare-root: Couldn't find specified OSTree root
141+
142+
Check that the grub.cfg `ostree=...` entry in grub.cfg points to the path in the
143+
rootfs.img. The build process sets this uuid from the ostree directory so this really should not happen with an ISO build unless you change the ISO contents yourself.
144+
145+
Or if the error looks like:
146+
147+
ostree-prepare-root: Failed to mount composefs: composefs: failed to mount: Input/output error
148+
149+
Check the prepare-root.conf file to make sure composefs has been disabled.

pkg/image/container_based_iso.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ func (img *ContainerBasedIso) InstantiateManifestFromContainer(m *manifest.Manif
112112
// Mount the rootfs, get the ostree boot path, update grub.cfg ostree= entry
113113
isoTreePipeline.SetOSTREE = true
114114
isoTreePipeline.RootfsType = rootfsPipeline.RootfsType
115+
// Add the README from the file storage
116+
isoTreePipeline.DataFiles = [][2]string{{"iso/generic/README", "/README"}}
115117

116118
isoPipeline := manifest.NewISO(buildPipeline, isoTreePipeline, img.ISOCustomizations)
117119
isoPipeline.SetFilename(img.filename)

pkg/manifest/iso_tree.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ type ISOTree struct {
3232
SetOSTREE bool
3333
// What is RootfsPath's file type, so it can be mounted for ostree examination
3434
RootfsType ISORootfsType
35+
36+
// DataFiles are src, dst pairs where the source is from the file storage and
37+
// the dst is the path on the ISO.
38+
DataFiles [][2]string
3539
}
3640

3741
func NewISOTree(buildPipeline Build, treePipeline TreePipeline, bootloaders []ISOBootloader) *ISOTree {
@@ -109,6 +113,16 @@ func (p *ISOTree) serialize() (osbuild.Pipeline, error) {
109113
p.files = append(p.files, files...)
110114
}
111115

116+
// Add files from the fileDataFS storage
117+
for _, sourceDest := range p.DataFiles {
118+
stages, file, err := p.addFile(sourceDest[0], sourceDest[1])
119+
if err != nil {
120+
return osbuild.Pipeline{}, fmt.Errorf("cannot add fileDataFS file: %w", err)
121+
}
122+
pipeline.AddStages(stages...)
123+
p.files = append(p.files, file)
124+
}
125+
112126
// Optional stage to set the ostree= value in the bootloader
113127
// This is used for bootc/ostree rootfs images
114128
if p.SetOSTREE {
@@ -144,6 +158,20 @@ func (p *ISOTree) serialize() (osbuild.Pipeline, error) {
144158
return pipeline, nil
145159
}
146160

161+
// addFile copies a file from the fileDataFS storage to the ISO root
162+
func (p *ISOTree) addFile(src, dst string) ([]*osbuild.Stage, *fsnode.File, error) {
163+
data, err := fileDataFS.ReadFile(src)
164+
if err != nil {
165+
return nil, nil, err
166+
}
167+
168+
f, err := fsnode.NewFile(dst, nil, nil, nil, data)
169+
if err != nil {
170+
return nil, nil, err
171+
}
172+
return osbuild.GenFileNodesStages([]*fsnode.File{f}), f, nil
173+
}
174+
147175
func (p *ISOTree) getInline() []string {
148176
inlineData := []string{}
149177

pkg/manifest/iso_tree_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,39 @@ func TestISOTreeWithErofsOSTREE(t *testing.T) {
114114
// Check ostree.grub2 mount
115115
assert.Equal(t, "org.osbuild.erofs", grub2Stage.Mounts[0].Type)
116116
}
117+
118+
func TestISOTreeREADME(t *testing.T) {
119+
isoTree := makeFakeISOTree(manifest.Grub2UEFIOnlyISOBoot, false)
120+
isoTree.DataFiles = [][2]string{{"iso/generic/README", "/README"}}
121+
122+
pipeline, err := isoTree.Serialize()
123+
require.NoError(t, err)
124+
125+
copyStages := findStages("org.osbuild.copy", pipeline.Stages)
126+
require.Greater(t, len(copyStages), 0)
127+
var fromPaths []string
128+
var toPaths []string
129+
for _, s := range copyStages {
130+
copyOptions := s.Options.(*osbuild.CopyStageOptions)
131+
for _, p := range copyOptions.Paths {
132+
fromPaths = append(fromPaths, p.From)
133+
toPaths = append(toPaths, p.To)
134+
}
135+
}
136+
// Check for the kernel/initrd/rootfs from the ostree deployment
137+
assert.Contains(t, fromPaths, "input://tree/vmlinuz")
138+
assert.Contains(t, fromPaths, "input://tree/initrd.img")
139+
assert.Contains(t, fromPaths, "input://tree/rootfs.img")
140+
assert.Contains(t, fromPaths, "input://root-tree/EFI")
141+
142+
// Check for final paths for kernel, initrd, rootfs (squashfs.img)
143+
assert.Contains(t, toPaths, "tree:///images/pxeboot/vmlinuz")
144+
assert.Contains(t, toPaths, "tree:///images/pxeboot/initrd.img")
145+
assert.Contains(t, toPaths, "tree:///LiveOS/squashfs.img")
146+
147+
// Make sure a /README is written
148+
assert.Contains(t, toPaths, "tree:///README")
149+
150+
// No ostree.grub2 stage
151+
require.Nil(t, findStage("org.osbuild.ostree.grub2", pipeline.Stages))
152+
}

test/data/manifest-checksums/bootc_test_os_1-aarch64-generic_iso-bootc_empty

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/data/manifest-checksums/bootc_test_os_1-aarch64-generic_iso-bootc_remote_empty

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/data/manifest-checksums/bootc_test_os_1-x86_64-generic_iso-bootc_empty

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/data/manifest-checksums/bootc_test_os_1-x86_64-generic_iso-bootc_remote_empty

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)