Compare commits

..

164 Commits

Author SHA1 Message Date
Didier Roche-Tolomelli
1975bbd52b releasing package livecd-rootfs version 26.04.26 2026-03-27 15:32:10 +01:00
Didier Roche-Tolomelli
916b693130 Ensure snapd tracks stable and not edge anymore.
We did remove it from multiple places, but this one was left and as a
consequence, the latest iso was still having snapd edge.
2026-03-27 15:30:31 +01:00
michael.hudson@canonical.com
a5ed2589f6
releasing package livecd-rootfs version 26.04.25 2026-03-20 06:47:52 +13:00
michael.hudson@canonical.com
2a5f5e74dd
update changelog 2026-03-20 06:47:08 +13:00
root
cbb597a46c
fix: bake LIVECD_ROOTFS_ROOT into config/functions
Hooks that only source config/functions (not config/common) would fail
with "LIVECD_ROOTFS_ROOT: parameter not set" when calling setup_mountpoint
or any function that transitively calls it. This affected ubuntu, ubuntu-cpc,
and buildd hooks.

Rather than adding `. config/common` to every hook, prepend the
LIVECD_ROOTFS_ROOT assignment to config/functions at config time so it is
available to all callers automatically.
2026-03-20 06:33:06 +13:00
michael.hudson@canonical.com
7252138aa9
releasing package livecd-rootfs version 26.04.24 2026-03-16 11:05:40 +13:00
michael.hudson@canonical.com
5e4e502c76
Merge branch 'better-local-experience' into ubuntu/master 2026-03-16 10:50:17 +13:00
Allen Abraham
bf2eb46fc8 fix: Resolving comments on MP
- Adding parition ordering using imagecraft
- Removing redundant locales install
- Better logging for empty ARCH and SUBPROJECT
- Using mktemp for mountpoint
2026-03-11 15:07:29 -04:00
Allen Abraham
f45429018c feat: Produce a working minimal image using imagecraft in a livcd-rootfs hook
- Added manifest and filelist generation
- Currently, we only produce minimal images for amd64
- Tested the changes by booting an instance on OVH
- Hook was also tested to be run in a local CPC Jenkins pipeline
2026-03-03 16:59:47 -05:00
michael.hudson@canonical.com
dad4a04751
releasing package livecd-rootfs version 26.04.23 2026-03-02 10:52:01 +13:00
michael.hudson@canonical.com
6e25a58c56
make ubuntu-test-iso project use KERNEL_FLAVOURS to exercise more machinery 2026-03-02 10:50:15 +13:00
michael.hudson@canonical.com
2b2c057acf
build-livefs-lxd: configure apt proxy and package_update via cloud-init
Read http-proxy from --http-proxy flag or build-livefs.conf and pass it
to the VM at launch time via cloud-init user-data (apt.http_proxy /
apt.https_proxy). Also set package_update: true so cloud-init runs
apt-get update on first boot, removing the need for an explicit call.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 17:57:11 +13:00
michael.hudson@canonical.com
cdd2b7fb01
configure snapd to use a proxy if one is configured 2026-02-27 14:45:56 +13:00
michael.hudson@canonical.com
e9d67eecd6
changelog 2026-02-27 14:45:52 +13:00
michael.hudson@canonical.com
6175dfb3ac
build-livefs-lxd: helper to run build-livefs inside an LXD VM
Creates (or reuses) a per-suite LXD VM, mounts the livecd-rootfs
checkout into it, waits for the VM to be ready, installs dependencies,
and runs build-livefs inside the VM — keeping the host clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 14:43:39 +13:00
michael.hudson@canonical.com
0e292ea3f2
Add build-livefs CLI tool
Provides a single command to run a livecd-rootfs build, replacing the
manual setup of auto/ symlinks and env vars that lpbuildd's
build_livefs.py encapsulates. Works from a git checkout, an installed
deb, or via the /usr/bin/build-livefs symlink.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 14:43:38 +13:00
michael.hudson@canonical.com
7620f798f4
an attempt to be able to run from a git checkout 2026-02-27 14:43:34 +13:00
michael.hudson@canonical.com
04e2aea648
Add PROJECT=ubuntu-test-iso for fast end-to-end ISO testing
Builds a real Ubuntu ISO in ~2-3 minutes (vs 30-60 for production)
by keeping the package set minimal (casper + linux-generic only) and
using lz4 squashfs compression.

Design:
- Two-pass layered build (PASSES_TO_LAYERS=true):
  - base: installs casper
  - base.generic (LIVE_PASSES): installs linux-generic, triggers
    update-initramfs with CASPER_GENERATE_UUID=1 to embed UUID
- No package pool (POOL_SEED_NAME=), no universe, amd64 only
- SQUASHFS_COMP=lz4 (~10x faster than xz); create_squashfs() now
  respects ${SQUASHFS_COMP:-xz} so all other projects are unaffected
- Germinate is skipped (only add_package is used, no add_task/pool)

New files:
- live-build/ubuntu-test-iso/hooks/01-test-iso.chroot_early
- live-build/ubuntu-test-iso/hooks/02-test-iso-kernel.binary

Modified:
- live-build/functions: honour $SQUASHFS_COMP in create_squashfs()
- live-build/gen-iso-ids: add ubuntu-test-iso -> Ubuntu-Test-ISO
- live-build/auto/config: IMAGEFORMAT, fs, COMPONENTS, germinate
  skip, SQUASHFS_COMP default + per-project override, project block,
  write SQUASHFS_COMP to config/common, lxd exclusion, hook copying

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 14:42:52 +13:00
michael.hudson@canonical.com
9861d393aa
changelog for Tobias' change 2026-02-27 14:40:30 +13:00
Tobias Heider
0b30131aae Properly handle non -generic kernel and initramfs flavour
Determine used flavour by looking at the suffix of kernel.
hwe is currently the only one that needs to have a prefix
added so we can hardcode that.
2026-02-26 02:08:26 +01:00
michael.hudson@canonical.com
301cf9622c
releasing package livecd-rootfs version 26.04.22 2026-02-26 10:57:27 +13:00
michael.hudson@canonical.com
a3c8532764
changelog for tobhe's changes 2026-02-26 10:32:54 +13:00
michael.hudson@canonical.com
92e680cc2c
Merge remote-tracking branch 'tobhe/extra_ppas' into ubuntu/master 2026-02-26 10:31:11 +13:00
Tobias Heider
b1c61d8bf1 Fix extra_ppas
ignore launchpad repos in in_release_path because it
can only handle a single repo.
2026-02-25 22:22:51 +01:00
michael.hudson@canonical.com
9c4ce17909
Merge branch 'more-iso-fixes' into ubuntu/master 2026-02-26 10:15:59 +13:00
michael.hudson@canonical.com
b1ca2ae69b
changelog 2026-02-26 10:13:24 +13:00
michael.hudson@canonical.com
31a0c2716c
place kernel at /casper/vmlinux on riscv64
For no very good reason this is what debian-cd does, and what the grub
config isobuilder generates still expects. It probably makes sense to
switch to 'vmlinuz' like every other arch apart from ppc64el does but
for now I want to maintain compatibility with the old ISOs.
2026-02-26 10:13:23 +13:00
michael.hudson@canonical.com
a66fbc54b7
make sure the for-iso squashfs gets created in the right place for kubuntu 2026-02-26 10:13:21 +13:00
Olivier Gayot
9819eae23c ubuntu: pull model from Launchpad canonical-model git repo
Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
2026-02-25 10:34:19 +01:00
Didier Roche
b135edf10c
releasing package livecd-rootfs version 26.04.21 2026-02-25 08:40:18 +01:00
Didier Roche
112153325c
Update changelog 2026-02-25 08:39:57 +01:00
Didier Roche
39ab3ea113
Update signed ubuntu classic model to ship new nvidia drivers 2026-02-25 08:38:03 +01:00
michael.hudson@canonical.com
a6ac43fcda
releasing package livecd-rootfs version 26.04.20 2026-02-20 12:46:15 +13:00
michael.hudson@canonical.com
92c29ecbf2
Merge branch 'fix-boot-paths' into ubuntu/master 2026-02-20 12:44:49 +13:00
michael.hudson@canonical.com
7fdf181234
fix changelog merge 2026-02-20 12:44:02 +13:00
michael.hudson@canonical.com
49a0415299
Merge branch 'no-tpm-arm64' into ubuntu/master 2026-02-20 12:43:14 +13:00
michael.hudson@canonical.com
96f41a4cf5
add changelog entry for optional sbom change 2026-02-20 12:43:06 +13:00
michael.hudson@canonical.com
2579dc30cb
Merge branch 'CPC-8952-make-sbom-optional' into ubuntu/master 2026-02-20 12:42:04 +13:00
michael.hudson@canonical.com
77db814700
fix version number 2026-02-20 12:40:03 +13:00
Michael Raymond
a1c8d7f3bd add changelog entry 2026-02-19 16:34:04 -05:00
Allen Abraham
f734d8cb8c feat(ubuntu-cpc): make SBOM generation optional in create_manifest function
There are case in CPC built images where we don't want to create an SBOM.
Add an argument in create_manifest which defaults to creating an SBOM, but can also skip generating an SBOM
2026-02-19 15:30:28 -05:00
Michael Raymond
7e3c74afac feat: Use same keyring for all releases
A change in 2024 [0] was made to debootstrap in which the keyring is now
switched from ubuntu-archive-keyring.gpg to
ubuntu-archive-removed-keys.gpg after a given release goes EOL. This
means that the Release signature cannot be verified after EOL since the
Release is signed with the ubuntu-archive-keyring.gpg. It is expected
that we can continue to build any release even after the suite is
closed.
This change adds a debootstrap configuration to override this behavior
and ensure all of our images are verified against the main archive key.

Refs: [0] https://git.launchpad.net/ubuntu/+source/debootstrap/commit/?id=4f8b3405097b9f655938528ae7105ec534eb7d1b
2026-02-19 14:48:18 -05:00
michael.hudson@canonical.com
7c4588191c
e030-ubuntu-live-system-seed.binary: do not run if there is no layer to install the system, in particular on arm64. 2026-02-19 19:57:16 +13:00
michael.hudson@canonical.com
42df11d4cc
add changelog entry 2026-02-19 15:06:08 +13:00
michael.hudson@canonical.com
6db44c1ea6
make a small change to copy_unsigned_monolithic_grub 2026-02-19 15:06:04 +13:00
michael.hudson@canonical.com
081981e650
simplify scratch directory handling a bit 2026-02-19 15:06:02 +13:00
michael.hudson@canonical.com
a2a166d93d
Standardize whitespace in GRUB menu entry stanzas
Use consistent formatting across all architectures: 4-space indent,
two spaces after "linux", one space after "initrd". Also fix an extra
blank line before "fi" in amd64's UEFI section caused by f-string
interpolation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 15:06:00 +13:00
michael.hudson@canonical.com
a6466ab0a3
Make generate_grub_config return strings instead of writing files
Separate config generation from file I/O by having generate_grub_config()
and its helpers return strings. The base class make_bootable() now handles
writing grub.cfg.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 15:05:53 +13:00
michael.hudson@canonical.com
a5cffa8414
place boot-related files directly into the ISO root
The debian-cd scripts did this game of placing boot-related files in a
separate directory that was then passed to xorriso to include on the
ISO. Stop doing that and just put the files directly into the ISO root
that is already passed to xorriso.
2026-02-19 15:05:47 +13:00
michael.hudson@canonical.com
ff3addb2f8
Extract packages to scratch dirs instead of boot tree subdirs
Package contents were being extracted into subdirectories of the boot
tree (grub_dir, shim_dir), which meant the boot tree contained both
the final boot files and the raw package extractions. Extract packages
into scratch directories instead, copying only the needed files into
the boot tree. This also removes the grub_dir/shim_dir instance
variables and the create_dirs overrides, and moves copy_grub_modules
to a standalone function in grub.py.
2026-02-19 13:17:35 +13:00
michael.hudson@canonical.com
460037fb4d
releasing package livecd-rootfs version 26.04.19 2026-02-17 11:17:10 +13:00
michael.hudson@canonical.com
b70abe0d1f
add changelog entry 2026-02-17 10:52:43 +13:00
michael.hudson@canonical.com
166e83f5d8
Move ubuntu-mini-iso to use isobuilder instead of debian-cd
Set MAKE_ISO=yes so ubuntu-mini-iso uses the standard isobuilder
flow in auto/build. The binary hook is simplified to just creating
kernel/initrd artifacts; isobuilder handles .disk metadata, boot
configuration, and ISO creation.

The mini-iso's custom grub.cfg (single iso-chooser-menu entry) is
generated by a project-specific path in AMD64BootConfigurator.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 10:47:18 +13:00
michael.hudson@canonical.com
5c6a3fba53
move the calamares-using projects into a global with a meaningful name 2026-02-17 10:47:17 +13:00
michael.hudson@canonical.com
edc2fa0fc5
Use match statement with local imports in boot configurator factory
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 10:47:15 +13:00
michael.hudson@canonical.com
516d8b8913
Use Python boot package instead of debian-cd shell scripts
Replace the debian-cd git clone and shell script invocation in
ISOBuilder with the new Python boot configurators.

Key changes to builder.py:
- make_bootable() creates a boot configurator and calls its
  make_bootable() method instead of cloning debian-cd
- make_iso() gets mkisofs_opts directly from the configurator
  instead of reading a serialized file
- add_live_filesystem() links kernel/initrd with names expected
  by the boot configurators (vmlinuz/initrd, hwe-vmlinuz/hwe-initrd)
- _extract_casper_uuids() updated for the new initrd naming scheme
- Refactor config storage to use a single _config dict
- Add limit_length parameter to Logger for long xorriso commands

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 10:47:14 +13:00
michael.hudson@canonical.com
edf0acbeac
Add Python boot configuration package
Add architecture-specific boot configurators that translate the
debian-cd boot shell scripts (boot-amd64, boot-arm64, boot-ppc64el,
boot-riscv64, boot-s390x) into Python.

The package uses a class hierarchy:
- BaseBootConfigurator: abstract base with common functionality
- GrubBootConfigurator: shared GRUB config generation
- UEFIBootConfigurator: UEFI-specific shim/ESP handling
- Architecture classes: AMD64, ARM64, PPC64EL, RISCV64, S390X

A factory function make_boot_configurator_for_arch() creates the
appropriate configurator for each architecture.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 10:47:13 +13:00
michael.hudson@canonical.com
6a6b00d68b
Add download_direct method to AptStateManager
Extract a download_direct() method from download() to enable downloading
packages to an arbitrary directory with an arbitrary specification string.

This will be used by the boot configuration code to download bootloader
packages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 10:47:08 +13:00
Utkarsh Gupta
e4b17221a0 Update d/ch for 26.04.18 release 2026-02-16 16:14:56 +05:30
Utkarsh Gupta
435c4e1895 Merge remote-tracking branch 'mwhudson/fix-kylin' into ubuntu/master
* mwhudson/fix-kylin:
  isobuilder: pass ignore_dangling_symlinks=True when copying apt config
2026-02-16 16:13:53 +05:30
michael.hudson@canonical.com
0733a7626a
Merge branch 'update-README.parameters' into ubuntu/master 2026-02-16 21:50:54 +13:00
michael.hudson@canonical.com
f0c7c28618
isobuilder: pass ignore_dangling_symlinks=True when copying apt config
ubuntukylin's /etc/apt/trusted.gpg.d/ubuntukylin-archive-keyring.gpg
contains a symlink to
"/usr/share/keyrings/ubuntukylin-archive-keyring.gpg" as an absolute
path. This obviously doesn't work when not chrooted into the chroot but
we don't need to copy it over to the apt config used to build the pool
as no package from any archive signed by this key is going to be
included in the pool...
2026-02-16 21:24:46 +13:00
Olivier Gayot
3645bdf230 Release livecd-rootfs 26.04.17
Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
2026-02-12 10:25:26 +01:00
Olivier Gayot
c3671c739d ubuntu: update model to latest stable model
Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
2026-02-12 10:25:02 +01:00
Olivier Gayot
733ad14e33 ubuntu: for the stable image, use the stable model
Let's stop leaning on overrides for now.

Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
2026-02-12 10:24:30 +01:00
michael.hudson@canonical.com
01d94b279c
document ARCH_VARIANT and BUILD_TYPE in README.parameters 2026-02-12 10:27:54 +13:00
Utkarsh Gupta
e26de340e2 Merge build-status into ubuntu/master [a=utkarsh] [r=]
Rename ISO_STATUS to BUILD_TYPE for image builds

MP: https://code.launchpad.net/~utkarsh/livecd-rootfs/+git/livecd-rootfs/+merge/500253

* build-status:
  Update d/ch for 26.04.16 release
  Rename ISO_STATUS to BUILD_TYPE for image builds
2026-02-12 01:53:18 +05:30
Utkarsh Gupta
7f1c505f20 Update d/ch for 26.04.16 release 2026-02-12 01:41:28 +05:30
Utkarsh Gupta
6d954c975d Rename ISO_STATUS to BUILD_TYPE for image builds 2026-02-12 01:41:06 +05:30
michael.hudson@canonical.com
73035c0b19
releasing package livecd-rootfs version 26.04.15 2026-02-11 10:07:53 +13:00
michael.hudson@canonical.com
84760de4da
rename the Daily|Release in .disk/info from "official" to "iso_status" 2026-02-11 09:42:44 +13:00
michael.hudson@canonical.com
2c2f7d5e5c
fix xorriso -map to include target path for riscv64
The -map option requires two arguments: the source filesystem path and
the target path in the ISO. Without the "/" target, xorriso fails.
This only affects riscv64, which uses native xorriso mode rather than
mkisofs compatibility mode.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-11 09:42:43 +13:00
michael.hudson@canonical.com
45aa1e4550
run add_riscv_gpt on riscv64 2026-02-11 09:42:42 +13:00
michael.hudson@canonical.com
c1edc22c24
xorriso is not run with -as mkisofs for whatever reason 2026-02-11 09:42:40 +13:00
michael.hudson@canonical.com
9add6d4ab8
do not truncate xorriso invocation in output 2026-02-11 09:42:39 +13:00
michael.hudson@canonical.com
acd63ee3e4
Make sure the unlayered ISO has a cdrom.sources in as well. 2026-02-11 09:42:38 +13:00
michael.hudson@canonical.com
ab2b82e3c2
a more generic way to make sure all artefacts get a for-iso path 2026-02-11 09:42:37 +13:00
michael.hudson@canonical.com
9a9ca07a76
Copy-edit Claude's comments a bit. 2026-02-11 09:42:36 +13:00
michael.hudson@canonical.com
4d8cfd89b8
Update changelog for ISO build support 2026-02-11 09:42:20 +13:00
michael.hudson@canonical.com
ce809612c4
Add CI lint checks for Python code
Add a lint job to the Launchpad CI pipeline that runs mypy, black, and
flake8 on the new Python code (gen-iso-ids, isobuild, isobuilder).
2026-02-11 09:41:08 +13:00
michael.hudson@canonical.com
b3fdc4e615
Add isobuild tool to build installer ISOs
This adds a new tool, isobuild, which replaces the ISO-building
functionality previously provided by live-build and cdimage. It is
invoked from auto/build when MAKE_ISO=yes.

The tool supports:
 - Layered desktop images (Ubuntu Desktop, flavors)
 - Non-layered images (Kubuntu, Ubuntu Unity)
 - Images with package pools (most installers)
 - Images without pools (Ubuntu Core Installer)

The isobuild command has several subcommands:
 - init: Initialize the ISO build directory structure
 - setup-apt: Configure APT for package pool generation
 - generate-pool: Create the package pool from a seed
 - generate-sources: Generate cdrom.sources for the installed system
 - add-live-filesystem: Add squashfs and kernel/initrd to the ISO
 - make-bootable: Add GRUB and other boot infrastructure
 - make-iso: Generate the final ISO image

auto/config is updated to:
 - Set MAKE_ISO=yes for relevant image types
 - Set POOL_SEED_NAME for images that need a package pool
 - Invoke gen-iso-ids to compute ISO metadata

auto/build is updated to:
 - Remove old live-build ISO handling code
 - Invoke isobuild at appropriate points in the build

lb_binary_layered is updated to create squashfs files with
cdrom.sources included for use in the ISO.
2026-02-11 09:41:06 +13:00
michael.hudson@canonical.com
3112c5f175
Add gen-iso-ids tool to compute ISO metadata
Add a script to compute the values for .disk/info, the ISO volume ID,
and the "capproject" (capitalized project name) used in various places
in the ISO boot configuration.

This replaces the logic that was previously scattered across live-build
and cdimage.
2026-02-11 09:41:01 +13:00
Matthew Hagemann
8e26b08f59
changelog 2026-02-05 13:27:01 +02:00
Matthew Hagemann
7cbabf55d5
ubuntu: delay display manager until snapd seeding completes
Add systemd drop-in to wait for snapd seeding completion before starting the
display manager. This improves the user experience as users now wait in
Plymouth for the installer to finish being seeded, instead of in GDM with only
the wallpaper visible. When GDM starts, the installer launches with minimal
delay.
2026-02-05 13:25:28 +02:00
Dan Bungert
ddbf8bf828 releasing package livecd-rootfs version 26.04.14 2026-01-22 09:21:55 -07:00
Olivier Gayot
74f5986230 changelog
Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
2026-01-22 16:22:21 +01:00
Olivier Gayot
563d142029 ubuntu: build with snapd from beta
Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
2026-01-22 16:22:21 +01:00
Didier Roche
755f0b0d15
Allow force reexecution of snapd snap version
When developping and using snapd from edge on cross-team efforts like
TPM/FDE, allow snapd to reexec to the snap version unconditionnaly,
on live system.
.
This is commented so that the future revert to stable include it and
we don’t forget to readd that next time this kind of effort is needed.
2026-01-13 11:57:31 +01:00
Michael Hudson-Doyle
d756afd205
releasing package livecd-rootfs version 26.04.13 2026-01-06 22:03:29 +13:00
Michael Hudson-Doyle
9c5d326e56
Bootstrap and install variant packages if ARCH_VARIANT is set. 2026-01-06 21:26:40 +13:00
Olivier Gayot
383a1206cc releasing package livecd-rootfs version 26.04.12 2025-12-17 17:29:46 -07:00
Olivier Gayot
2f918331fb ubuntu: use the same for in construct when getting snap args
Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
2025-12-16 16:14:53 +01:00
Olivier Gayot
e6558e2541 ubuntu: add a filter for snaps too
We now filter snaps using jq rather than grep. The change has a slight impact
because snapd-desktop-integration was filtered out by "grep snapd" but isn't
filtered out anymore with jq.

Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
2025-12-16 16:14:53 +01:00
Olivier Gayot
01c80d8d0a ubuntu: fix snap components pulled from the wrong model
We have a mechanism in place to override a snap when building an image.
Unfortunately, we didn't factor this in when forcing optional components to be
included in the image.

This was okay before because the stable model and the dangerous model had the
same components declared.

But now that pc-kernel has different components in the stable and the dangerous
model, things are broken.

Indeed, when building the stable image, we tried to include the pc-kernel from
the stable model with the pc-kernel components from the dangerous model. But
they are not compatible.

Fixed by including components from the right model. If we're overriding a snap
with a definition from a different model, then pull the components from that
same model.

Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
2025-12-16 16:14:53 +01:00
Olivier Gayot
72511a0381 ubuntu: declare variables from stable & dangerous models
Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
2025-12-16 14:56:13 +01:00
Dan Bungert
c147c15291 releasing package livecd-rootfs version 26.04.11 2025-12-11 17:24:44 -07:00
Olivier Gayot
856f14edee changelog
Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
2025-12-11 17:24:44 -07:00
Olivier Gayot
caf4f1030a ubuntu: when building stable image, don't take pc-kernel from beta
The pc-kernel version in 26.04/beta is kernel 6.17, which uses different
components from what is currently declared in the model.

This used to be necessary when there was no kernel in 26.04/stable, but now
there is a 6.8 version in 26.04/stable. The available components match what's
in the model.

Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
2025-12-11 17:24:40 -07:00
Olivier Gayot
49e1ab15cd ubuntu: use a local variable where possible
Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
2025-12-11 16:52:22 +01:00
Olivier Gayot
5cbea9f677 ubuntu: add --comp for each optional component from the model
Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
2025-12-11 16:52:22 +01:00
Valentin Haudiquet
a19f30b9d6 changelog
Signed-off-by: Valentin Haudiquet <valentin.haudiquet@canonical.com>
2025-12-11 09:29:06 +01:00
Valentin Haudiquet
44c14b799f refactor: added a function to generate grub config for netboot
Signed-off-by: Valentin Haudiquet <valentin.haudiquet@canonical.com>
2025-12-11 09:27:56 +01:00
Valentin Haudiquet
9fdbaf8d6d riscv/server: add grub efi bootloader in netboot tarballs
This allows netboot tarballs to be PXE booted on QEMU; previously, the tarball was missing bootloader.

Signed-off-by: Valentin Haudiquet <valentin.haudiquet@canonical.com>
2025-12-11 09:27:56 +01:00
Michael Hudson-Doyle
ae1e5005aa
releasing package livecd-rootfs version 26.04.10 2025-12-09 21:08:13 +13:00
Michael Hudson-Doyle
c327ab7bd7
Build Ubuntu Server images with the 'restricted' component enabled. 2025-12-09 12:18:25 +13:00
Michael Hudson-Doyle
ab943acf44
Merge branch 'README.parameters' into ubuntu/master 2025-12-09 12:02:48 +13:00
Michael Hudson-Doyle
827d87bd7f
document format of EXTRA_PPAS a bit 2025-12-09 12:00:55 +13:00
Michael Hudson-Doyle
562e589cd1
include more information about how the parameters get from request to build 2025-12-09 09:50:33 +13:00
Michael Hudson-Doyle
65dad6ccc0
be a bit more accurate about IMAGEFORMAT 2025-12-02 18:47:55 +13:00
Dan Bungert
0fc035c8ba changelog 2025-12-01 12:52:00 -07:00
Olivier Gayot
e5ef47f7dd ubuntu: fix typo in comment
Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
2025-12-01 12:51:10 -07:00
Didier Roche
69ee041674
releasing package livecd-rootfs version 26.04.9 2025-11-26 08:53:00 +01:00
Didier Roche
e78505a5f3
Refresh new signed models with mesa
This mesa snap is needed starting with core24 apps.
2025-11-26 08:52:16 +01:00
Didier Roche
544aa0299e
releasing package livecd-rootfs version 26.04.8 2025-11-25 11:40:15 +01:00
Didier Roche
98c75ef41b
Switch to new GNOME platform snap 2025-11-25 11:38:49 +01:00
Michael Hudson-Doyle
5e00e3ecb2
add some kind of documentation of the parameters livecd-rootfs takes 2025-11-25 16:46:36 +13:00
Olivier Gayot
d280d58a7d Releasing 26.04.7
Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
2025-11-19 10:41:30 +01:00
Olivier Gayot
e2c8b4b1ad changelog
Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
2025-11-19 10:38:49 +01:00
Olivier Gayot
b3ddf6a78a ubuntu: use snapd, desktop-security-center and firmware-updater from edge for TPM/FDE
Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
2025-11-19 10:32:21 +01:00
Dan Bungert
218ad9af27 releasing package livecd-rootfs version 26.04.6 2025-11-18 15:42:58 -07:00
Olivier Gayot
befd8ddadf changelog
Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
2025-11-18 18:24:10 +01:00
Olivier Gayot
21372df0f9 ubuntu: update the models for 26.04
Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
2025-11-18 18:24:10 +01:00
Olivier Gayot
001aed3b3b ubuntu: use an array for prepare_args
Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
2025-11-18 18:24:09 +01:00
Olivier Gayot
9164e58d83 ubuntu: build with grade: dangerous but keep most snaps from stable
Because some snaps are not yet in their respective stable channel in 26.04, the
build fails. When preparing the image we can add --snap options to override the
channel of the different snaps. But we can only do that if we're building with
grade: dangerous. As a workaround this issue, we build with the non-dangerous
ISO with the dangerous model, but keep the snaps on their original channel
defined in the non dangerous model.

Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
2025-11-18 18:23:27 +01:00
Sebastien Bacher
18c3471930
releasing package livecd-rootfs version 26.04.5 2025-11-17 12:05:43 +01:00
Dan Bungert
466d9683c5 changelog 2025-11-14 08:20:14 -07:00
Dan Bungert
ec74e75597 desktop: handle unset SUBPROJECT 2025-11-14 08:19:17 -07:00
Dan Bungert
432785a2fe releasing package livecd-rootfs version 26.04.4 2025-10-31 16:35:56 +01:00
Dan Bungert
596d6d8464 desktop: update dangerous model for 26.04 2025-10-31 11:14:42 +01:00
Dan Bungert
1fca197379 releasing package livecd-rootfs version 26.04.3 2025-10-29 13:14:40 +01:00
Dan Bungert
f6e3d2aedd desktop: use snapd from edge 2025-10-29 13:09:11 +01:00
Dan Bungert
918bc13a3a desktop: use dangerous model until stable channel snaps are available 2025-10-29 13:09:11 +01:00
Michael Hudson-Doyle
92471dcb76
releasing package livecd-rootfs version 26.04.2 2025-10-23 12:51:27 +13:00
Michael Hudson-Doyle
a53da7e27b
Merge ~gjolly/livecd-rootfs/+git/livecd-rootfs/+merge/493969 into ubuntu/master 2025-10-23 12:43:36 +13:00
Michael Hudson-Doyle
922faa0d12
update version number 2025-10-23 12:42:12 +13:00
Chad Smith
e0f4fd8109 releasing package livecd-rootfs version 26.04.1 2025-10-20 17:25:27 -06:00
Chad Smith
78d502951b fix(functions): update cloud-init-network.service override for v. 25.3
Update /etc/systemd/system/cloud-init-network.service override to
sync with latest netcat changes in Desktop images.

Resolve traceback:

netcat: /run/cloud-init/share/network.sock: Protocol wrong type for socket

LP: #2128887
2025-10-20 17:07:13 -06:00
Gauthier Jolly
287bf91450 d/changelog 2025-10-20 09:00:54 +02:00
Gauthier Jolly
ff6b3824d8 cpc/UEFI: name the GPT partition cloudimg-rootfs
To boot initrdless, the kernel supports a limited number of ways to
specify the location of the root filesystem[1]. One of them is to use
the PARTUUID (which will be different for every cloud-image), another is
to use the PARTLABEL (partition name). To allow the use of PARTLABEL in
the kernel command line and make our cloud-images more self-describing,
set the PARTLABEL to cloudimg-rootfs which is the same label we use for
the file system inside this partition.

[1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/block/early-lookup.c#n217
2025-10-20 09:00:54 +02:00
Gauthier Jolly
ddff3faba3 cpc/UEFI: set the right partition type for the rootfs
To make our disk images more discoverable, we should use the correct
partition type for the root filesystem. This aligns with the
Discoverable Disk Image (DDI) specification developed by the UAPI
group[1] and makes our images more self-describing, e.g. with fdisk,
before:

Device         Start     End Sectors  Size Type
/dev/nbd0p1  2324480 7339998 5015519  2.4G Linux filesystem
...

and now after:

Device         Start     End Sectors  Size Type
/dev/nbd0p1  2324480 7339998 5015519  2.4G Linux root (x86-64)
...

[1] https://uapi-group.org/specifications/specs/discoverable_partitions_specification/
2025-10-20 08:59:26 +02:00
Michael Hudson-Doyle
9172378dae
update changelog some more 2025-10-17 15:13:02 +13:00
Heinrich Schuchardt
e35eb63edc Update changelog
Signed-off-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
2025-10-14 09:40:52 +02:00
Heinrich Schuchardt
82a239e39c riscv/server: GRUB_TERMINAL=console
Allow using the U-Boot menu both from the serial and the graphical console.

Signed-off-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
2025-10-14 09:40:52 +02:00
Heinrich Schuchardt
b8520530c9 riscv/server: enforce our RISC-V specific grub configuration
/etc/default/grub.d/50-cloudimg-settings.cfg is currently overriding our
RISC-V specific configuration. Remove it.

Signed-off-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
2025-10-14 09:40:52 +02:00
Heinrich Schuchardt
7957008902 server/riscv: remove unsupported boards
Since release 25.10 we require support for the rva23s64 profile.
Remove all code relating for boards that do not match this requirement.

Signed-off-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
2025-10-14 09:40:52 +02:00
Dan Bungert
4b4d3de818 releasing package livecd-rootfs version 25.10.24 2025-09-19 13:47:24 -06:00
Dan Bungert
355f6d5b26 Merge remote-tracking branch 'kajiya/increase-image-size-questing' into ubuntu/master 2025-09-19 09:18:50 -06:00
Chad Smith
39e1066593 changelog for LP: #2119020 2025-09-19 09:09:59 -06:00
Chloé Smith
12545fb878
d/ch entry for questing (LP: #2115811) 2025-09-19 13:38:20 +01:00
Chloé Smith
f7e0f39a1d
fix: Increase CPC disk-image base imagesize to 2.5GB
In the ubuntu-cpc disk-image binary we need to avail of the ever increasing size
of packages. 2.2GB is now just a bit too small leading to `No space
left on device` errors when the binary hits `grub-install`. This commit
increases $imagesize to 2.5GB (in the binary as an override initially
implemented in ecaaf0484).

This commit also runs `df` just after the grub-pc && grub2-common
installs to make for easier debugging in the future.

Refs: LP: #2115811
2025-09-19 13:37:56 +01:00
Chad Smith
f7ed2d271e fix(functions): permissions too broad on /etc/netplan/01-network-manager.yaml
netplan apply warns about any /etc/netplan/*.yaml file permissions which
are globally readable.  Set permissions 600 for
/etc/netplan/01-network-manager.yaml in target chroot.

LP: #2119020
2025-09-18 21:39:49 -06:00
Thomas Bechtold
ae0be803f1
Add debian/changelog entry 2025-09-18 13:30:25 +02:00
Thomas Bechtold
ab658bce7d
Add 6.17 kernel apparmor features preseeds
Questing is currently on kernel 6.17 so preseeding fails with a apparmor
feature mismatch given that the live-build/apparmor/generic tree is
used. Adding a 6.17 tree solves this.
2025-09-18 11:53:10 +02:00
Michael Hudson-Doyle
1501b3776c releasing package livecd-rootfs version 25.10.22 2025-09-16 08:51:45 +12:00
Michael Hudson-Doyle
8de7b2eb10 Disable apparmor_restrict_unprivileged_userns in the live layers. (LP: #2122675) 2025-09-15 12:28:30 +12:00
Michael Hudson-Doyle
2fd6cb1609 add missing file, oops 2025-09-15 12:27:44 +12:00
Michael Hudson-Doyle
6dbce04781 releasing package livecd-rootfs version 25.10.21 2025-09-15 12:16:34 +12:00
Michael Hudson-Doyle
785c4c53d3 Fix daily-dangerous builds:
* Fix daily-dangerous builds:
  - Copy hooks.
  - Mangle the channel of seeded snaps to use the edge risk of whichever
    track they are taken from.
  - Update the dangerous model to reference tracks that actually exist.
  - Include providers of content plugs when seeding snaps and creating
    TPMFDE system.
  - Do not attempt to build an UEFI boot image or hyperv desktop image for
    this project/subproject combination.
2025-09-15 09:09:02 +12:00
Dan Bungert
762108eaef releasing package livecd-rootfs version 25.10.20 2025-09-10 17:15:46 -06:00
Dan Bungert
fddd7b7595 edubuntu: set NEEDS_DRACUT=yes 2025-09-10 09:05:30 -06:00
Dan Bungert
2aeaaf1815 releasing package livecd-rootfs version 25.10.19 2025-09-09 16:54:54 -06:00
Dan Bungert
ecaaf04844 Merge remote-tracking branch 'dlalaj/fix-buildd-imagesize' into ubuntu/master 2025-09-09 16:52:30 -06:00
Denis Lalaj
eda621a927 Add debian/changelog entry 2025-09-05 12:10:18 -07:00
Denis Lalaj
12c92b2cbf fix(buildd): Increase default image size for buildd
Builds for buildd questing are failing due to the increasing pkg sizes,
this calls for an adjustment of the default image size
2025-09-05 12:10:13 -07:00
117 changed files with 4214 additions and 603 deletions

12
.launchpad.yaml Normal file
View File

@ -0,0 +1,12 @@
pipeline:
- [lint]
jobs:
lint:
series: noble
architectures: amd64
packages:
- black
- mypy
- python3-flake8
run: ./check-lint

263
README.parameters Normal file
View File

@ -0,0 +1,263 @@
Understanding the parameters used by livecd-rootfs
==================================================
livecd-rootfs is a confusing codebase. One of the confusing things is
how information flows into and around the image build process. There
is IMAGEFORMAT and IMAGE_TARGETS and PROJECT and many other
variables. It is not obvious when looking at the code if a given
variable is something passed as a parameter or something derived from
it.
All (or almost all) production use of livecd-rootfs is via
launchpad-buildd so the set of potential parameters is limited by the
set of environment variables launchpad-build can set in response to
the build request.
The process from build request to environment live-build is run is a
little convoluted. The build request takes:
an archive -- where to get livecd-rootfs from
a distro_arch_series -- the series to get livecd-rootfs and build
a pocket -- pocket to get livecd-rootfs from, also influences if proposed is
used as a package source for the image being built
unique_key -- you cannot have more than one pending livefs build with the same
unique_key. does not affect the build at all.
version -- optional version string, see below. often a serial like 20250525.1
metadata_override -- combined with the metadata on the livefs itself to make
the metadata for this build.
(ref: https://launchpad.net/+apidoc/devel.html#livefs-requestBuild)
These parameters are stored on the livefsbuild object (ref:
https://git.launchpad.net/launchpad/tree/lib/lp/soyuz/model/livefsbuild.py#n372)
and converted into a set of args passed to launchpad-build by the
LiveFSBuildBehaviour class (ref:
https://git.launchpad.net/launchpad/tree/lib/lp/soyuz/model/livefsbuildbehaviour.py#n99).
Inside launchpad-build, these arguments are inspected by the
LiveFilesystemBuildManager.initiate method (ref:
https://git.launchpad.net/launchpad-buildd/tree/lpbuildd/livefs.py#n24)
which turns them into arguments for the BuildLiveFS lpbuild
"operation" which is what creates the environment live-build runs in
(ref:
https://git.launchpad.net/launchpad-buildd/tree/lpbuildd/target/build_livefs.py#n167).
These variables can be set for both lb config and lb build:
PROJECT (mandatory, comes from "project" in the metadata)
ARCH (set to the abi tag of the distroarchseries being built for)
ARCH_VARIANT (set to the isa tag of the distroarchseries being built for if it is
different from the abi tag)
SUBPROJECT (optional, comes from "subproject" in the metadata)
SUBARCH (optional, comes from "subarch" in the metadata)
CHANNEL (optional, comes from "subarch" in the metadata)
IMAGE_TARGETS (optional, comes from "image_targets" in the metadata
"image_targets" is a list. IMAGE_TARGETS is set to " ".join(image_targets))
REPO_SNAPSHOT_STAMP
(optional, comes from "repo_snapshot_stamp" in the metadata)
SNAPSHOT_SERVICE_TIMESTAMP
(optional, comes from "snapshot_snapshot_stamp" in the metadata)
COHORT_KEY
(optional, comes from "cohort-key" in the metadata)
launchpad-buildd also contains code to set http_proxy / HTTP_PROXY /
LB_APT_HTTP_PROXY but there does not appear to be any way to trigger
this when requesting a build.
In addition the following variables can be set for lb config only (why
are some things set for lb config only? no idea):
SUITE (set to the name of the distroarchseries being built for)
NOW (set to value of the 'version' argument to the build request,
defaults to strftime("%Y%m%d-%H%M%S"))
IMAGEFORMAT (optional, comes from "image_format" in the metadata)
PROPOSED (set to "1" if the pocket passed to the build request is proposed)
EXTRA_PPAS (optional, comes from "extra_ppas" in the metadata
"extra_ppas" is a list. EXTRA_PPAS is set to " ".join(extra_ppas))
EXTRA_SNAPS (optional, comes from "extra_snaps" in the metadata
"extra_snaps" is a list. EXTRA_SNAPS is set to " ".join(extra_snaps))
BUILD_TYPE (optional, the "type" (i.e. Daily or Release) of ISO being built,
goes into .disk/info on the ISO, defaults to Daily)
Here is an opinionated and slightly angry attempt to describe what
each of these is for:
PROJECT
-------
This is the big one, the main variable that defines what is being
built. It can be ubuntu, ubuntu-server, xubuntu, ubuntu-mini-iso, that
sort of thing. Generally PROJECT determines the set of packages
installed but it (unfortunately?) has a bit more impact than that.
It's unarguable that we need a parameter like this.
ARCH
----
The architecture being built for. This is always the same as `dpkg
--print-architecture` for us, we don't do any cross builds.
It's kind of redundant but it's not really a problem that this exists.
ARCH_VARIANT
------------
The "variant" being built for, i.e. the ISA tag of the
distroarchseries. Only set if this is different from the ABI tag.
This is definitely needed to be able to build images for variants.
SUBPROJECT
----------
This is used for some builds to build a different sort of build of the
project. It can be set to:
* "minimized" for ubuntu-cpc builds to make a minimal cloud image
* "minimal" for xubuntu builds to make a smaller ISO
* "desktop-preinstalled" for ubuntu builds to make a preinstalled
image instead of the parts for an installer.
* "buildd" for images to be used as build images by craft tools, and also
buildd chroots used on launchpad builders?
* "live" for ubuntu-server builds, historically to distinguish d-i
style installers from subiquity style installers
* "desktop" for ubuntu-core-installer builds, to influence which
model is use to build the ubuntu core system that will be
installed.
_This_ parameter is a total mess. The desktop-preinstalled use feels
particularly egregious.
SUBARCH
-------
This identifies the target machine more specifically than ARCH,
e.g. "tegra-jetson" or "licheerv". Used mostly but not exclusively for
preinstalled builds.
We probably do need something like this.
CHANNEL
-------
Influences which channel snaps included in the build are taken from
(via a few different mechanisms).
IMAGE_TARGETS
-------------
Passed for CPC (and ubuntu-oem, for some reason) builds to
`config/hooks.d/make-hooks` which uses it to select which binary hooks
to run (and so determines which artifacts get produced).
It is probably reasonable that this exists.
REPO_SNAPSHOT_STAMP
-------------------
Currently unused.
SNAPSHOT_SERVICE_TIMESTAMP
--------------------------
Also currently unused, and unclear how it differs from
REPO_SNAPSHOT_STAMP.
COHORT_KEY
----------
Used to make sure that different builds run at the same time don't get
different versions of snaps due to phasing differences.
This is a totally valid thing to need to supply.
http_proxy / HTTP_PROXY / LB_APT_HTTP_PROXY
-------------------------------------------
Nothing complex here!
SUITE
-----
This is the series being built (e.g. noble, questing). It is misnamed
really -- a suite is usually a combination of a series and a pocket
(noble-proposed, questing-security).
As with ARCH this is sort of redundant as we do builds in a chroot of
the series being built but OTOH it is definitely information the
build needs to know!
NOW
---
The serial for the build, e.g. 20250519 or 20240418.4.
It is a totally reasonable parameter.
IMAGEFORMAT
-----------
This is one of the more incoherently handled parameters. In rough
outline it is the filesystem of the image we produce.
Installer builds do not produce raw images, so this ends up being set
to 'plain' (which causes live-build to just leave the rootfs as a
directory tree) or 'none' (which causes live-build to do roughly the
same thing but in a different way?).
Image builds that use ubuntu-image set it to "ubuntu-image". These
builds do not call 'lb build' or 'lb binary'.
Other preinstalled images (mostly cpc images) set it to ext4 (but then
use live-build/ubuntu-cpc/hooks.d/remove-implicit-artifacts to remove
the output file that this causes live-build to produce...). Some
projects rely on this being set via metadata when building the project
it seems.
It can be set when starting an image build, but most builds do not and
the behavior when it is not set explicitly is pretty confusing.
This place is not a place of honor.
PROPOSED
--------
Should packages from proposed by included?
This is not really as useful as it used to be for a bunch of reasons
but it conceptually makes sense.
EXTRA_PPAS
----------
Extra archives to get packages from.
This is a space separated list by the time it gets to
livecd-rootfs. Each element of the list is of the form USER/NAME[:PIN]
where user is a Launchpad user/team name, NAME is the name of the ppa
to add and the optional colon-PIN at the end is the value to pin (in
the "man 5 apt_preferences: sense) packages from this PPA at.
Production builds shouldn't really use this but it's definitely useful
for development.
EXTRA_SNAPS
-----------
Extra snaps to include (but only for ubuntu-image based builds).
BUILD_TYPE
----------
Before release, the .disk/info on an ISO looks like:
Ubuntu-Server 26.04 LTS "Resolute Raccoon" - Daily amd64 (20260210)
after release it looks like:
Ubuntu-Server 26.04 LTS "Resolute Raccoon" - Release amd64 (20270210)
We could do a livecd-rootfs upload to change this (it only changes
once per cycle), but it's quicker and easier to manage this from the
code that triggers the livefs builds.

11
check-lint Executable file
View File

@ -0,0 +1,11 @@
#!/bin/sh
set -eux
export MYPYPATH=live-build
mypy live-build/isobuilder live-build/isobuild
mypy live-build/gen-iso-ids
black --check live-build/isobuilder live-build/isobuild live-build/gen-iso-ids
python3 -m flake8 --max-line-length 88 --ignore E203 live-build/isobuilder live-build/isobuild live-build/gen-iso-ids

301
debian/changelog vendored
View File

@ -1,3 +1,304 @@
livecd-rootfs (26.04.26) resolute; urgency=medium
* Ensure snapd tracks stable and not edge anymore.
We did remove it from multiple places, but this one was left and as a
consequence, the latest iso was still having snapd edge.
-- Didier Roche-Tolomelli <didrocks@ubuntu.com> Fri, 27 Mar 2026 15:31:21 +0100
livecd-rootfs (26.04.25) resolute; urgency=medium
* bake LIVECD_ROOTFS_ROOT into config/functions, fixing some build failures
(for at least ubuntu and some ubuntu-cpc configurations).
-- Michael Hudson-Doyle <michael.hudson@ubuntu.com> Fri, 20 Mar 2026 06:47:44 +1300
livecd-rootfs (26.04.24) resolute; urgency=medium
[ Allen Abraham ]
* Added a hook to produce a working minimal Ubuntu image using imagecraft
[ Michael Hudson-Doyle ]
* Various quality of life improvements for hacking on livecd-rootfs:
- Add a "ubuntu-test-iso" project that builds a not very useful ISO in 2-5 minutes.
- Add a build-livefs script that takes care of copying the auto scripts and
invoking lb clean/config/build with the right environment.
- Add a build-livefs-lxd script to run the above script in a lxd vm.
-- Michael Hudson-Doyle <michael.hudson@ubuntu.com> Mon, 16 Mar 2026 11:05:13 +1300
livecd-rootfs (26.04.23) resolute; urgency=medium
[ Tobias Heider ]
* Fix ISO builds when KERNEL_FLAVOUR != generic.
-- Michael Hudson-Doyle <michael.hudson@ubuntu.com> Mon, 02 Mar 2026 10:51:47 +1300
livecd-rootfs (26.04.22) resolute; urgency=medium
[ Oliver Gayot ]
* Pull the model from Launchpad's lp:canonical-models
repo, instead of having it uploaded as part of livecd-rootfs. This
indirection makes it possible to update the models without requiring a new
upload of livecd-rootfs every time.
[ Michael Hudson-Doyle ]
* Fix two more problems with livefs-built ISOs:
- Generate the for-iso squashfs in the right place for Kubuntu.
- Fix confusion about the kernel path on the ISO on riscv64.
[ Tobias Heider ]
* Fix pool generation when using extra_ppas.
-- Michael Hudson-Doyle <michael.hudson@ubuntu.com> Thu, 26 Feb 2026 10:56:42 +1300
livecd-rootfs (26.04.21) resolute; urgency=medium
[ Dan Bungert ]
* Update new signed models to ship latest nvidia drivers for ubuntu hybrid.
-- Didier Roche-Tolomelli <didrocks@ubuntu.com> Wed, 25 Feb 2026 08:38:32 +0100
livecd-rootfs (26.04.20) resolute; urgency=medium
[ Michael Raymond ]
* Bug-fix: Only use main archive keyring when building with debootstrap
so EOL release signatures can be verified after EOL.
[ Allen Abraham ]
* Make SBOM generation optional in create_manifest function.
[ Michael Hudson-Doyle ]
* 030-ubuntu-live-system-seed.binary: do not run if there is no layer to
install the system, in particular on arm64.
* Fix some path confusion in the new isobuilder.boot package and refactor
grub config generation to be more string based.
-- Michael Hudson-Doyle <michael.hudson@ubuntu.com> Fri, 20 Feb 2026 12:45:41 +1300
livecd-rootfs (26.04.19) resolute; urgency=medium
* Translate the debian-cd tools/boot/$series/boot-$arch scripts to Python
and use that to make ISOs bootable rather than cloning debian-cd.
-- Michael Hudson-Doyle <michael.hudson@ubuntu.com> Tue, 17 Feb 2026 11:16:43 +1300
livecd-rootfs (26.04.18) resolute; urgency=medium
[ Michael Hudson-Doyle ]
* document ARCH_VARIANT and BUILD_TYPE in README.parameters
* isobuilder: pass ignore_dangling_symlinks=True when copying apt config
-- Utkarsh Gupta <utkarsh@ubuntu.com> Mon, 16 Feb 2026 16:14:03 +0530
livecd-rootfs (26.04.17) resolute; urgency=medium
* desktop: build the stable ISO using the stable model - essentially
reverting all the hacks.
* desktop: update the stable model to the latest. It has:
- components defined for the 6.19 kernel (nvidia 580 series)
- no core26: for TPM/FDE recovery testing, please install the core26 snap
from edge.
-- Olivier Gayot <olivier.gayot@canonical.com> Thu, 12 Feb 2026 10:25:15 +0100
livecd-rootfs (26.04.16) resolute; urgency=medium
* Rename ISO_STATUS to BUILD_TYPE for image builds.
-- Utkarsh Gupta <utkarsh@debian.org> Thu, 12 Feb 2026 01:41:11 +0530
livecd-rootfs (26.04.15) resolute; urgency=medium
[ Matthew Hagemann ]
* desktop: delay display manager starting until snapd seeding completes
[ Michael Hudson-Doyle ]
* Make an ISO in the livefs build when building an installer.
-- Michael Hudson-Doyle <michael.hudson@ubuntu.com> Wed, 11 Feb 2026 10:04:37 +1300
livecd-rootfs (26.04.14) resolute; urgency=medium
[ Olivier Gayot ]
* desktop: build stable image with snapd from beta. Snapd 2.74 has just been
uploaded to beta. Let's stop using the version declared in the dangerous model.
[ Didier Roche-Tolomelli ]
* desktop: add (commented out) config to force reexecution of snapd snap version
-- Olivier Gayot <olivier.gayot@canonical.com> Thu, 22 Jan 2026 10:13:36 +0100
livecd-rootfs (26.04.13) resolute; urgency=medium
* Bootstrap and install variant packages if ARCH_VARIANT is set.
-- Michael Hudson-Doyle <michael.hudson@ubuntu.com> Tue, 06 Jan 2026 22:03:15 +1300
livecd-rootfs (26.04.12) resolute; urgency=medium
* desktop: add variables pointing to the different models (stable & dangerous).
* desktop: fix snap components taken from original model when overriding a
snap with another model.
- if we decide to override the definition of a snap (i.e., by taking in
from a different model), we also need to override the definition of its
components.
* desktop: refactor how we filter the snaps when overriding
* desktop: update the dangerous model so that it includes core26 and the 6.17
kernel and components.
-- Olivier Gayot <olivier.gayot@canonical.com> Tue, 16 Dec 2025 14:54:17 +0100
livecd-rootfs (26.04.11) resolute; urgency=medium
[ Valentin Haudiquet ]
* refactor: added a function to generate grub config for netboot
* riscv/server: add grub efi bootloader in netboot tarballs
[ Olivier Gayot ]
* desktop: build with optional components included
* desktop: don't build the stable image with pc-kernel from 26.04/beta
- This was needed before because there was pc-kernel in 26.04/stable but
now there is one and it matches the components definition from the model.
-- Valentin Haudiquet <valentin.haudiquet@canonical.com> Thu, 11 Dec 2025 09:28:37 +0100
livecd-rootfs (26.04.10) resolute; urgency=medium
[ Olivier Gayot ]
* desktop: fix a comment typo
[ Michael Hudson-Doyle ]
* Build Ubuntu Server images with the 'restricted' component enabled.
-- Dan Bungert <daniel.bungert@canonical.com> Tue, 09 Dec 2025 21:07:54 +1300
livecd-rootfs (26.04.9) resolute; urgency=medium
* desktop: Add mesa to the hybrid model required by core24 apps.
-- Didier Roche-Tolomelli <didrocks@ubuntu.com> Wed, 26 Nov 2025 08:51:24 +0100
livecd-rootfs (26.04.8) resolute; urgency=medium
* desktop: update TPM/FDE ubuntu model to use a GNOME platform snap
compatible with core24.
-- Didier Roche-Tolomelli <didrocks@ubuntu.com> Tue, 25 Nov 2025 11:38:59 +0100
livecd-rootfs (26.04.7) resolute; urgency=medium
* desktop: build both ISOs with snapd, firmware-updater and
desktop-security-center from edge for TPM/FDE.
-- Olivier Gayot <olivier.gayot@canonical.com> Wed, 19 Nov 2025 10:41:17 +0100
livecd-rootfs (26.04.6) resolute; urgency=medium
* desktop: build classic ISO with "grade: dangerous" and pull pc-kernel from
edge to workaround unavailability of pc-kernel in the stable 26.04 channel.
* desktop: refresh models to 26.04
-- Olivier Gayot <olivier.gayot@canonical.com> Tue, 18 Nov 2025 17:01:47 +0100
livecd-rootfs (26.04.5) resolute; urgency=medium
[ Dan Bungert ]
* desktop: fix build error when SUBPROJECT is unset
-- Sebastien Bacher <seb128@ubuntu.com> Mon, 17 Nov 2025 12:05:26 +0100
livecd-rootfs (26.04.4) resolute; urgency=medium
* desktop: update dangerous model for 26.04
-- Dan Bungert <daniel.bungert@canonical.com> Fri, 31 Oct 2025 11:13:53 +0100
livecd-rootfs (26.04.3) resolute; urgency=medium
* desktop: use dangerous model for TPMFDE bits until snaps are available on
stable channels.
* desktop: use snapd from edge.
-- Dan Bungert <daniel.bungert@canonical.com> Wed, 29 Oct 2025 10:58:00 +0100
livecd-rootfs (26.04.2) resolute; urgency=medium
[ Gauthier Jolly ]
* ubuntu-cpc:
- Use the right specific UUID type for the root filesystem partition.
- Set a PARTLABEL (cloudimg-rootfs) on the root filesystem partition.
-- Gauthier Jolly <gauthier.jolly@canonical.com> Thu, 23 Oct 2025 12:50:07 +1300
livecd-rootfs (26.04.1) resolute; urgency=medium
[ Heinrich Schuchardt ]
* Remove unused riscv64 SUBARCHs
[ Chad Smith ]
* Refresh cloud-init service override for updated service netcat invocation
to cloud-init 25.3. (LP: #2128887)
-- Chad Smith <chad.smith@canonical.com> Mon, 20 Oct 2025 16:32:36 -0600
livecd-rootfs (25.10.24) questing; urgency=medium
[ Chad Smith ]
* Limit permissions for /etc/netplan/01-network-manager.yaml to
root read-write. (LP: #2119020)
[ Chloé 'kajiya' Smith ]
* Increase CPC disk-image base imagesize to 2.5GB
* In the ubuntu-cpc disk-image binary we need to avail of the ever increasing size
of packages. 2.2GB is now just a bit too small leading to `No space
left on device` errors when the binary hits `grub-install`. This change
increases $imagesize to 2.5GB (in the binary as an override, initially
implemented in ecaaf0484 by dlalaj). Also now run `df` just after the
grub-pc && grub2-common installs to make for easier debugging in the future.
(LP: #2115811)
-- Dan Bungert <daniel.bungert@canonical.com> Fri, 19 Sep 2025 13:47:20 -0600
livecd-rootfs (25.10.23) questing; urgency=medium
* Add 6.17 kernel apparmor features' preseeds.
-- Thomas Bechtold <thomasbechtold@jpberlin.de> Thu, 18 Sep 2025 13:29:42 +0200
livecd-rootfs (25.10.22) questing; urgency=medium
* Disable apparmor_restrict_unprivileged_userns in the live layers.
(LP: #2122675)
-- Michael Hudson-Doyle <michael.hudson@ubuntu.com> Tue, 16 Sep 2025 08:51:02 +1200
livecd-rootfs (25.10.21) questing; urgency=medium
* Fix daily-dangerous builds:
- Copy hooks.
- Mangle the channel of seeded snaps to use the edge risk of whichever
track they are taken from.
- Update the dangerous model to reference tracks that actually exist.
- Include providers of content plugs when seeding snaps and creating
TPMFDE system.
- Do not attempt to build an UEFI boot image or hyperv desktop image for
this project/subproject combination.
-- Michael Hudson-Doyle <michael.hudson@ubuntu.com> Mon, 15 Sep 2025 12:16:08 +1200
livecd-rootfs (25.10.20) questing; urgency=medium
* edubuntu: use dracut
-- Dan Bungert <daniel.bungert@canonical.com> Wed, 10 Sep 2025 17:15:45 -0600
livecd-rootfs (25.10.19) questing; urgency=medium
* Increase default image size for buildd.
-- Denis Lalaj <denis.lalaj@canonical.com> Tue, 09 Sep 2025 16:53:52 -0600
livecd-rootfs (25.10.18) questing; urgency=medium livecd-rootfs (25.10.18) questing; urgency=medium
* desktop: use dracut * desktop: use dracut

2
debian/control vendored
View File

@ -25,6 +25,7 @@ Depends: ${misc:Depends},
git, git,
gnupg, gnupg,
grep-dctrl, grep-dctrl,
jq,
kpartx, kpartx,
live-build (>= 3.0~a57-1ubuntu31~), live-build (>= 3.0~a57-1ubuntu31~),
lsb-release, lsb-release,
@ -36,6 +37,7 @@ Depends: ${misc:Depends},
procps, procps,
python3, python3,
python3-apt, python3-apt,
python3-click,
python3-launchpadlib [!i386], python3-launchpadlib [!i386],
python3-yaml, python3-yaml,
qemu-utils [!i386], qemu-utils [!i386],

1
debian/livecd-rootfs.links vendored Normal file
View File

@ -0,0 +1 @@
usr/share/livecd-rootfs/live-build/build-livefs usr/bin/build-livefs

View File

@ -0,0 +1 @@
0xffffff

View File

@ -0,0 +1 @@
yes

View File

@ -0,0 +1 @@
chown dac_override dac_read_search fowner fsetid kill setgid setuid setpcap linux_immutable net_bind_service net_broadcast net_admin net_raw ipc_lock ipc_owner sys_module sys_rawio sys_chroot sys_ptrace sys_pacct sys_admin sys_boot sys_nice sys_resource sys_time sys_tty_config mknod lease audit_write audit_control setfcap mac_override mac_admin syslog wake_alarm block_suspend audit_read perfmon bpf checkpoint_restore

View File

@ -0,0 +1 @@
acquire send receive

View File

@ -0,0 +1 @@
yes

View File

@ -0,0 +1 @@
yes

View File

@ -0,0 +1 @@
yes

View File

@ -0,0 +1 @@
yes

View File

@ -0,0 +1 @@
yes

View File

@ -0,0 +1 @@
yes

View File

@ -0,0 +1 @@
yes

View File

@ -0,0 +1 @@
yes

View File

@ -0,0 +1 @@
yes

View File

@ -0,0 +1 @@
yes

View File

@ -0,0 +1 @@
yes

View File

@ -0,0 +1 @@
yes

View File

@ -0,0 +1 @@
yes

View File

@ -0,0 +1 @@
yes

View File

@ -0,0 +1 @@
1.2

View File

@ -0,0 +1 @@
create read write exec append mmap_exec link lock

View File

@ -0,0 +1 @@
sqpoll override_creds

View File

@ -0,0 +1 @@
create read write open delete setattr getattr label

View File

@ -0,0 +1 @@
mount umount pivot_root

View File

@ -0,0 +1 @@
detached

View File

@ -0,0 +1 @@
userns_create

View File

@ -0,0 +1 @@
no

View File

@ -0,0 +1 @@
yes

View File

@ -0,0 +1 @@
pciu&

View File

@ -0,0 +1 @@
unspec unix inet ax25 ipx appletalk netrom bridge atmpvc x25 inet6 rose netbeui security key netlink packet ash econet atmsvc rds sna irda pppox wanpipe llc ib mpls can tipc bluetooth iucv rxrpc isdn phonet ieee802154 caif alg nfc vsock kcm qipcrtr smc xdp mctp

View File

@ -0,0 +1 @@
yes

View File

@ -0,0 +1 @@
yes

View File

@ -0,0 +1 @@
unspec unix inet ax25 ipx appletalk netrom bridge atmpvc x25 inet6 rose netbeui security key netlink packet ash econet atmsvc rds sna irda pppox wanpipe llc ib mpls can tipc bluetooth iucv rxrpc isdn phonet ieee802154 caif alg nfc vsock kcm qipcrtr smc xdp mctp

View File

@ -0,0 +1 @@
unspec unix inet ax25 ipx appletalk netrom bridge atmpvc x25 inet6 rose netbeui security key netlink packet ash econet atmsvc rds sna irda pppox wanpipe llc ib mpls can tipc bluetooth iucv rxrpc isdn phonet ieee802154 caif alg nfc vsock kcm qipcrtr smc xdp mctp

View File

@ -0,0 +1 @@
yes

View File

@ -0,0 +1 @@
0x000001

View File

@ -0,0 +1 @@
file tags

View File

@ -0,0 +1 @@
yes

View File

@ -0,0 +1 @@
yes

View File

@ -0,0 +1 @@
0x000001

View File

@ -0,0 +1 @@
allow deny subtree cond kill complain prompt audit quiet hide xindex tag label

View File

@ -0,0 +1 @@
0x000003

View File

@ -0,0 +1 @@
yes

View File

@ -0,0 +1 @@
0x000001

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1 @@
yes

View File

@ -0,0 +1 @@
yes

View File

@ -0,0 +1 @@
yes

View File

@ -0,0 +1 @@
yes

View File

@ -0,0 +1 @@
yes

View File

@ -0,0 +1 @@
read trace

View File

@ -0,0 +1 @@
yes

View File

@ -0,0 +1 @@
yes

View File

@ -0,0 +1 @@
allow deny audit quiet

View File

@ -0,0 +1 @@
cpu fsize data stack core rss nproc nofile memlock as locks sigpending msgqueue nice rtprio rttime

View File

@ -0,0 +1 @@
hup int quit ill trap abrt bus fpe kill usr1 segv usr2 pipe alrm term stkflt chld cont stop stp ttin ttou urg xcpu xfsz vtalrm prof winch io pwr sys emt lost

View File

@ -119,6 +119,12 @@ Expire-Date: 0
exit $ec exit $ec
fi fi
if [ -n "${ARCH_VARIANT:-}" ]; then
cat > chroot/etc/apt/apt.conf.d/90arch-variant <<EOF
APT::Architecture-Variants "${ARCH_VARIANT}";
EOF
fi
# Set locale to C.UTF-8 by default. This may be overridden later. # Set locale to C.UTF-8 by default. This may be overridden later.
echo "LANG=C.UTF-8" > chroot/etc/default/locale echo "LANG=C.UTF-8" > chroot/etc/default/locale
@ -202,6 +208,22 @@ EOF
undivert_update_initramfs undivert_update_initramfs
undivert_grub chroot undivert_grub chroot
fi fi
if [ "${MAKE_ISO}" = yes ]; then
isobuild init --disk-info "$(cat config/iso-ids/disk-info)" --series "${LB_DISTRIBUTION}" --arch "${ARCH}"
# Determine which chroot directory has the apt configuration to use.
# Layered builds (PASSES set) create overlay directories named
# "overlay.base", "overlay.live", etc. - we use the first one (base).
# Single-pass builds use the "chroot" directory directly.
if [ "${PASSES}" ]; then
CHROOT="overlay.$(set -- $PASSES; echo $1)"
else
CHROOT=chroot
fi
isobuild setup-apt --chroot $CHROOT
if [ -n "${POOL_SEED_NAME}" ]; then
isobuild generate-pool --package-list-file "config/germinate-output/${POOL_SEED_NAME}"
fi
fi
if [ -d chroot/etc/apt/preferences.d.save ]; then if [ -d chroot/etc/apt/preferences.d.save ]; then
# https://mastodon.social/@scream@botsin.space # https://mastodon.social/@scream@botsin.space
@ -353,7 +375,7 @@ EOF
(cd chroot && find usr/share/doc -maxdepth 1 -type d | xargs du -s | sort -nr) (cd chroot && find usr/share/doc -maxdepth 1 -type d | xargs du -s | sort -nr)
echo END docdirs echo END docdirs
/usr/share/livecd-rootfs/minimize-manual chroot ${LIVECD_ROOTFS_ROOT}/minimize-manual chroot
clean_debian_chroot clean_debian_chroot
fi fi
@ -421,13 +443,6 @@ if [ -e config/manifest-minimal-remove ]; then
cp config/manifest-minimal-remove "$PREFIX.manifest-minimal-remove" cp config/manifest-minimal-remove "$PREFIX.manifest-minimal-remove"
fi fi
for ISO in binary.iso binary.hybrid.iso; do
[ -e "$ISO" ] || continue
ln "$ISO" "$PREFIX.iso"
chmod 644 "$PREFIX.iso"
break
done
if [ -e "binary/$INITFS/filesystem.dir" ]; then if [ -e "binary/$INITFS/filesystem.dir" ]; then
(cd "binary/$INITFS/filesystem.dir/" && tar -c --sort=name --xattrs *) | \ (cd "binary/$INITFS/filesystem.dir/" && tar -c --sort=name --xattrs *) | \
gzip -9 --rsyncable > "$PREFIX.rootfs.tar.gz" gzip -9 --rsyncable > "$PREFIX.rootfs.tar.gz"
@ -552,3 +567,28 @@ case $PROJECT in
ubuntu-cpc) ubuntu-cpc)
config/hooks.d/remove-implicit-artifacts config/hooks.d/remove-implicit-artifacts
esac esac
if [ "${MAKE_ISO}" = "yes" ]; then
# Link build artifacts with "for-iso." prefix for isobuild to consume.
# Layered builds create squashfs via lb_binary_layered (which already
# creates for-iso.*.squashfs files). Single-pass builds only have
# ${PREFIX}.squashfs, which does not contain cdrom.sources, so we
# create a for-iso.filesystem.squashfs that does.
if [ -z "$PASSES" ]; then
isobuild generate-sources --mountpoint=/cdrom > chroot/etc/apt/sources.list.d/cdrom.sources
create_squashfs chroot ${PWD}/for-iso.filesystem.squashfs
fi
# Link kernel and initrd files. The ${thing#${PREFIX}} expansion strips
# the PREFIX, so "livecd.ubuntu-server.kernel-generic" becomes
# "for-iso.kernel-generic".
for thing in ${PREFIX}.kernel-* ${PREFIX}.initrd-*; do
for_iso_path=for-iso${thing#${PREFIX}}
if [ ! -f $for_iso_path ]; then
ln -v $thing $for_iso_path
fi
done
isobuild add-live-filesystem --artifact-prefix for-iso.
isobuild make-bootable --project "${PROJECT}" --capproject "$(cat config/iso-ids/capproject)" \
${SUBARCH:+--subarch "${SUBARCH}"}
isobuild make-iso --volid "$(cat config/iso-ids/vol-id)" --dest ${PREFIX}.iso
fi

View File

@ -1,6 +1,8 @@
#!/bin/bash #!/bin/bash
set -e set -e
LIVECD_ROOTFS_ROOT=${LIVECD_ROOTFS_ROOT:-/usr/share/livecd-rootfs}
case $ARCH:$SUBARCH in case $ARCH:$SUBARCH in
amd64:|amd64:generic|amd64:intel-iot|\ amd64:|amd64:generic|amd64:intel-iot|\
arm64:|arm64:generic|arm64:raspi|arm64:snapdragon|arm64:nvidia|\ arm64:|arm64:generic|arm64:raspi|arm64:snapdragon|arm64:nvidia|\
@ -9,9 +11,7 @@ case $ARCH:$SUBARCH in
armhf:|\ armhf:|\
i386:|\ i386:|\
ppc64el:|\ ppc64el:|\
riscv64:|riscv64:generic|riscv64:icicle|riscv64:jh7110|riscv64:licheerv|\ riscv64:|riscv64:generic|\
riscv64:milkvmars|riscv64:nezha|riscv64:pic64gx|riscv64:unmatched|\
riscv64:visionfive|riscv64:visionfive2|\
s390x:|\ s390x:|\
*appliance*) *appliance*)
;; ;;
@ -49,11 +49,14 @@ if [ -z "$MIRROR" ]; then
fi fi
mkdir -p config mkdir -p config
cp -af /usr/share/livecd-rootfs/live-build/functions config/functions echo "LIVECD_ROOTFS_ROOT=\"$LIVECD_ROOTFS_ROOT\"" > config/functions
cp -af /usr/share/livecd-rootfs/live-build/lb_*_layered config/ chmod --reference=${LIVECD_ROOTFS_ROOT}/live-build/functions config/functions
cp -af /usr/share/livecd-rootfs/live-build/snap-seed-parse.py config/snap-seed-parse cat ${LIVECD_ROOTFS_ROOT}/live-build/functions >> config/functions
cp -af /usr/share/livecd-rootfs/live-build/expand-task config/expand-task cp -af ${LIVECD_ROOTFS_ROOT}/live-build/lb_*_layered config/
cp -af /usr/share/livecd-rootfs/live-build/squashfs-exclude-files config/ cp -af ${LIVECD_ROOTFS_ROOT}/live-build/snap-seed-parse.py config/snap-seed-parse
cp -af ${LIVECD_ROOTFS_ROOT}/live-build/snap-seed-missing-providers.py config/snap-seed-missing-providers
cp -af ${LIVECD_ROOTFS_ROOT}/live-build/expand-task config/expand-task
cp -af ${LIVECD_ROOTFS_ROOT}/live-build/squashfs-exclude-files config/
mkdir -p config/package-lists mkdir -p config/package-lists
@ -391,7 +394,7 @@ if [ -z "${IMAGEFORMAT:-}" ]; then
;; ;;
esac esac
;; ;;
ubuntu-server:live|ubuntu-mini-iso:|ubuntu-core-installer:*) ubuntu-server:live|ubuntu-mini-iso:|ubuntu-test-iso:|ubuntu-core-installer:*)
IMAGEFORMAT=plain IMAGEFORMAT=plain
;; ;;
esac esac
@ -401,15 +404,6 @@ fi
# one also must request disk1-img-xz image format # one also must request disk1-img-xz image format
if [ "$IMAGEFORMAT" = "ext4" ] && [ "$PROJECT" = "ubuntu-cpc" ]; then if [ "$IMAGEFORMAT" = "ext4" ] && [ "$PROJECT" = "ubuntu-cpc" ]; then
case $ARCH:$SUBARCH in case $ARCH:$SUBARCH in
riscv64:icicle | \
riscv64:jh7110 | \
riscv64:licheerv | \
riscv64:milkvmars | \
riscv64:nezha | \
riscv64:pic64gx | \
riscv64:unmatched | \
riscv64:visionfive | \
riscv64:visionfive2 | \
*:generic) *:generic)
IMAGE_HAS_HARDCODED_PASSWORD=1 IMAGE_HAS_HARDCODED_PASSWORD=1
if [ -z "${IMAGE_TARGETS:-}" ]; then if [ -z "${IMAGE_TARGETS:-}" ]; then
@ -436,7 +430,7 @@ case $IMAGEFORMAT in
ubuntu-server:live|ubuntu-core-installer:*) ubuntu-server:live|ubuntu-core-installer:*)
touch config/universe-enabled touch config/universe-enabled
;; ;;
ubuntu-mini-iso:) ubuntu-mini-iso:|ubuntu-test-iso:)
fs=none fs=none
;; ;;
*) *)
@ -646,7 +640,7 @@ case $PROJECT in
esac esac
case $PROJECT in case $PROJECT in
ubuntu-server|ubuntu-mini-iso) ubuntu-mini-iso|ubuntu-test-iso)
COMPONENTS='main' COMPONENTS='main'
;; ;;
edubuntu|ubuntu-budgie|ubuntucinnamon|ubuntukylin) edubuntu|ubuntu-budgie|ubuntucinnamon|ubuntukylin)
@ -663,6 +657,13 @@ case $SUBPROJECT in
;; ;;
esac esac
case $PROJECT in
ubuntu-test-iso)
# ubuntu-test-iso uses only add_package (not add_task) and has no
# pool, so germinate output is never needed.
touch config/germinate-output/structure
;;
*)
if ! [ -e config/germinate-output/structure ]; then if ! [ -e config/germinate-output/structure ]; then
echo "Running germinate..." echo "Running germinate..."
if [ -n "$COMPONENTS" ]; then if [ -n "$COMPONENTS" ]; then
@ -670,13 +671,31 @@ if ! [ -e config/germinate-output/structure ]; then
fi fi
(cd config/germinate-output && germinate --no-rdepends --no-installer \ (cd config/germinate-output && germinate --no-rdepends --no-installer \
-S $SEEDMIRROR -m $MIRROR -d $SUITE,$SUITE-updates \ -S $SEEDMIRROR -m $MIRROR -d $SUITE,$SUITE-updates \
-s $FLAVOUR.$SUITE $GERMINATE_ARG -a $ARCH) -s $FLAVOUR.$SUITE $GERMINATE_ARG -a ${ARCH_VARIANT:-$ARCH})
fi fi
;;
esac
# ISO build configuration. These defaults are overridden per-project below.
#
# MAKE_ISO: Set to "yes" to generate an installer ISO at the end of the build.
# This triggers isobuild to run in auto/build.
MAKE_ISO=no
# POOL_SEED_NAME: The germinate output file defining packages for the ISO's
# package pool (repository). Different flavors use different seeds:
# - "ship-live" for most desktop images
# - "server-ship-live" for Ubuntu Server (includes server-specific packages)
# - "" (empty) for images without a pool, like Ubuntu Core Installer
POOL_SEED_NAME=ship-live
# SQUASHFS_COMP: compression algorithm for squashfs images. lz4 is ~10x
# faster than xz and useful for test builds that don't need small images.
SQUASHFS_COMP=xz
# Common functionality for layered desktop images # Common functionality for layered desktop images
common_layered_desktop_image() { common_layered_desktop_image() {
touch config/universe-enabled touch config/universe-enabled
PASSES_TO_LAYERS="true" PASSES_TO_LAYERS="true"
MAKE_ISO=yes
if [ -n "$HAS_MINIMAL" ]; then if [ -n "$HAS_MINIMAL" ]; then
if [ -z "$MINIMAL_TASKS" ]; then if [ -z "$MINIMAL_TASKS" ]; then
@ -801,7 +820,7 @@ do_layered_desktop_image() {
DEFAULT_KERNEL="linux-$KERNEL_FLAVOURS" DEFAULT_KERNEL="linux-$KERNEL_FLAVOURS"
if [ "$LOCALE_SUPPORT" != none ]; then if [ "$LOCALE_SUPPORT" != none ]; then
/usr/share/livecd-rootfs/checkout-translations-branch \ ${LIVECD_ROOTFS_ROOT}/checkout-translations-branch \
https://git.launchpad.net/subiquity po \ https://git.launchpad.net/subiquity po \
config/catalog-translations config/catalog-translations
fi fi
@ -907,6 +926,7 @@ case $PROJECT in
add_task install minimal standard add_task install minimal standard
add_task install kubuntu-desktop add_task install kubuntu-desktop
LIVE_TASK='kubuntu-live' LIVE_TASK='kubuntu-live'
MAKE_ISO=yes
add_chroot_hook remove-gnome-icon-cache add_chroot_hook remove-gnome-icon-cache
;; ;;
@ -917,6 +937,7 @@ case $PROJECT in
MINIMAL_DESC="A minimal installation of the Edubuntu Desktop." MINIMAL_DESC="A minimal installation of the Edubuntu Desktop."
STANDARD_TASKS=edubuntu-desktop-gnome STANDARD_TASKS=edubuntu-desktop-gnome
KERNEL_FLAVOURS=generic KERNEL_FLAVOURS=generic
NEEDS_DRACUT=yes
do_layered_desktop_image do_layered_desktop_image
;; ;;
@ -932,6 +953,7 @@ case $PROJECT in
ubuntu-unity) ubuntu-unity)
add_task install minimal standard ${PROJECT}-desktop add_task install minimal standard ${PROJECT}-desktop
LIVE_TASK=${PROJECT}-live LIVE_TASK=${PROJECT}-live
MAKE_ISO=yes
;; ;;
lubuntu) lubuntu)
@ -1006,6 +1028,8 @@ case $PROJECT in
live) live)
OPTS="${OPTS:+$OPTS }--bootstrap-flavour=minimal" OPTS="${OPTS:+$OPTS }--bootstrap-flavour=minimal"
PASSES_TO_LAYERS=true PASSES_TO_LAYERS=true
MAKE_ISO=yes
POOL_SEED_NAME=server-ship-live
add_task ubuntu-server-minimal server-minimal add_task ubuntu-server-minimal server-minimal
add_package ubuntu-server-minimal lxd-installer add_package ubuntu-server-minimal lxd-installer
add_task ubuntu-server-minimal.ubuntu-server minimal standard server add_task ubuntu-server-minimal.ubuntu-server minimal standard server
@ -1106,6 +1130,9 @@ case $PROJECT in
arm64) arm64)
add_package ubuntu-server-minimal.ubuntu-server.installer.$flavor.netboot shim-signed add_package ubuntu-server-minimal.ubuntu-server.installer.$flavor.netboot shim-signed
;; ;;
riscv64)
add_package ubuntu-server-minimal.ubuntu-server.installer.$flavor.netboot grub-efi-riscv64 grub-efi-riscv64-unsigned
;;
*) *)
add_package ubuntu-server-minimal.ubuntu-server.installer.$flavor.netboot add_package ubuntu-server-minimal.ubuntu-server.installer.$flavor.netboot
;; ;;
@ -1113,7 +1140,7 @@ case $PROJECT in
NO_SQUASHFS_PASSES=ubuntu-server-minimal.ubuntu-server.installer.$flavor.netboot NO_SQUASHFS_PASSES=ubuntu-server-minimal.ubuntu-server.installer.$flavor.netboot
DEFAULT_KERNEL="$kernel_metapkg" DEFAULT_KERNEL="$kernel_metapkg"
/usr/share/livecd-rootfs/checkout-translations-branch \ ${LIVECD_ROOTFS_ROOT}/checkout-translations-branch \
https://git.launchpad.net/subiquity po config/catalog-translations https://git.launchpad.net/subiquity po config/catalog-translations
;; ;;
*) *)
@ -1131,10 +1158,12 @@ case $PROJECT in
# created in ubuntu-core-installer/hooks/05-prepare-image.binary, which # created in ubuntu-core-installer/hooks/05-prepare-image.binary, which
# subiquity knows how to install. # subiquity knows how to install.
if [ ${SUBPROJECT} == "desktop" ]; then if [ ${SUBPROJECT} == "desktop" ]; then
cp /usr/share/livecd-rootfs/live-build/${PROJECT}/ubuntu-core-desktop-24-amd64.model-assertion config/ cp ${LIVECD_ROOTFS_ROOT}/live-build/${PROJECT}/ubuntu-core-desktop-24-amd64.model-assertion config/
fi fi
OPTS="${OPTS:+$OPTS }--bootstrap-flavour=minimal" OPTS="${OPTS:+$OPTS }--bootstrap-flavour=minimal"
PASSES_TO_LAYERS=true PASSES_TO_LAYERS=true
MAKE_ISO=yes
POOL_SEED_NAME=
add_task base server-minimal server add_task base server-minimal server
add_task base.live server-live add_task base.live server-live
add_package base.live linux-image-generic add_package base.live linux-image-generic
@ -1143,7 +1172,7 @@ case $PROJECT in
USE_BRIDGE_KERNEL=false USE_BRIDGE_KERNEL=false
DEFAULT_KERNEL="snap:pc-kernel" DEFAULT_KERNEL="snap:pc-kernel"
/usr/share/livecd-rootfs/checkout-translations-branch \ ${LIVECD_ROOTFS_ROOT}/checkout-translations-branch \
https://git.launchpad.net/subiquity po config/catalog-translations https://git.launchpad.net/subiquity po config/catalog-translations
;; ;;
@ -1153,6 +1182,7 @@ case $PROJECT in
OPTS="${OPTS:+$OPTS }--linux-packages=none --initramfs=none" OPTS="${OPTS:+$OPTS }--linux-packages=none --initramfs=none"
KERNEL_FLAVOURS=none KERNEL_FLAVOURS=none
BINARY_REMOVE_LINUX=false BINARY_REMOVE_LINUX=false
MAKE_ISO=yes
add_package install mini-iso-tools linux-generic add_package install mini-iso-tools linux-generic
case $ARCH in case $ARCH in
@ -1165,6 +1195,22 @@ case $PROJECT in
esac esac
;; ;;
ubuntu-test-iso)
OPTS="${OPTS:+$OPTS }--bootstrap-flavour=minimal"
KERNEL_FLAVOURS=virtual
BINARY_REMOVE_LINUX=false
MAKE_ISO=yes
POOL_SEED_NAME=
SQUASHFS_COMP=lz4
PASSES_TO_LAYERS=true
add_package base linux-$KERNEL_FLAVOURS
add_package base.live casper
case $ARCH in
amd64) ;;
*) echo "ubuntu-test-iso only supports amd64"; exit 1 ;;
esac
;;
ubuntu-base|ubuntu-oci) ubuntu-base|ubuntu-oci)
OPTS="${OPTS:+$OPTS }--bootstrap-flavour=minimal" OPTS="${OPTS:+$OPTS }--bootstrap-flavour=minimal"
;; ;;
@ -1218,14 +1264,7 @@ case $PROJECT in
;; ;;
riscv64*) riscv64*)
if [ -n "$SUBARCH" ]; then if [ -n "$SUBARCH" ]; then
case "${SUBARCH:-}" in
visionfive)
KERNEL_FLAVOURS=starfive
;;
*)
KERNEL_FLAVOURS=generic KERNEL_FLAVOURS=generic
;;
esac
fi fi
;; ;;
esac esac
@ -1271,7 +1310,7 @@ case $SUBPROJECT in
# and a variety of things fail without it. # and a variety of things fail without it.
add_package install tzdata add_package install tzdata
cp -af /usr/share/livecd-rootfs/live-build/make-lxd-metadata.py config/make-lxd-metadata cp -af ${LIVECD_ROOTFS_ROOT}/live-build/make-lxd-metadata.py config/make-lxd-metadata
;; ;;
esac esac
@ -1378,6 +1417,7 @@ lb config noauto \
--checksums none \ --checksums none \
--cache false \ --cache false \
${BOOTAPPEND_LIVE:+--bootappend-live "$BOOTAPPEND_LIVE"} \ ${BOOTAPPEND_LIVE:+--bootappend-live "$BOOTAPPEND_LIVE"} \
${ARCH_VARIANT:+--bootstrap-arch "$ARCH_VARIANT"} \
$OPTS \ $OPTS \
"$@" "$@"
@ -1389,13 +1429,19 @@ if [ -n "$PASSES" ] && [ -z "$LIVE_PASSES" ]; then
"Either set \$LIVE_PASSES or add a pass ending with '.live'." "Either set \$LIVE_PASSES or add a pass ending with '.live'."
fi fi
echo "DEBOOTSTRAP_OPTIONS=\"--keyring=/usr/share/keyrings/ubuntu-archive-keyring.gpg\"" >> config/bootstrap
echo "LB_CHROOT_HOOKS=\"$CHROOT_HOOKS\"" >> config/chroot echo "LB_CHROOT_HOOKS=\"$CHROOT_HOOKS\"" >> config/chroot
echo "SUBPROJECT=\"${SUBPROJECT:-}\"" >> config/chroot echo "SUBPROJECT=\"${SUBPROJECT:-}\"" >> config/chroot
echo "LB_DISTRIBUTION=\"$SUITE\"" >> config/chroot echo "LB_DISTRIBUTION=\"$SUITE\"" >> config/chroot
echo "IMAGEFORMAT=\"$IMAGEFORMAT\"" >> config/chroot echo "IMAGEFORMAT=\"$IMAGEFORMAT\"" >> config/chroot
echo "LIVECD_ROOTFS_ROOT=\"$LIVECD_ROOTFS_ROOT\"" >> config/common
if [ -n "$PASSES" ]; then if [ -n "$PASSES" ]; then
echo "PASSES=\"$PASSES\"" >> config/common echo "PASSES=\"$PASSES\"" >> config/common
fi fi
echo "MAKE_ISO=\"$MAKE_ISO\"" >> config/common
echo "POOL_SEED_NAME=\"$POOL_SEED_NAME\"" >> config/common
echo "SQUASHFS_COMP=\"$SQUASHFS_COMP\"" >> config/common
if [ -n "$NO_SQUASHFS_PASSES" ]; then if [ -n "$NO_SQUASHFS_PASSES" ]; then
echo "NO_SQUASHFS_PASSES=\"$NO_SQUASHFS_PASSES\"" >> config/common echo "NO_SQUASHFS_PASSES=\"$NO_SQUASHFS_PASSES\"" >> config/common
fi fi
@ -1431,7 +1477,7 @@ rm -fv /etc/ssl/private/ssl-cert-snakeoil.key \
EOF EOF
case $PROJECT in case $PROJECT in
ubuntu-cpc|ubuntu-core|ubuntu-base|ubuntu-oci|ubuntu-wsl|ubuntu-mini-iso) ubuntu-cpc|ubuntu-core|ubuntu-base|ubuntu-oci|ubuntu-wsl|ubuntu-mini-iso|ubuntu-test-iso)
# ubuntu-cpc gets this added in 025-create-groups.chroot, and we do # ubuntu-cpc gets this added in 025-create-groups.chroot, and we do
# not want this group in projects that are effectively just chroots # not want this group in projects that are effectively just chroots
;; ;;
@ -1519,11 +1565,11 @@ fi
case $PROJECT:${SUBPROJECT:-} in case $PROJECT:${SUBPROJECT:-} in
ubuntu-cpc:*|ubuntu-server:live|ubuntu:desktop-preinstalled| \ ubuntu-cpc:*|ubuntu-server:live|ubuntu:desktop-preinstalled| \
ubuntu-wsl:*|ubuntu-mini-iso:*|ubuntu:|ubuntu-oem:*| \ ubuntu-wsl:*|ubuntu-mini-iso:*|ubuntu-test-iso:*|ubuntu:|ubuntu:dangerous|ubuntu-oem:*| \
ubuntustudio:*|edubuntu:*|ubuntu-budgie:*|ubuntucinnamon:*|xubuntu:*| \ ubuntustudio:*|edubuntu:*|ubuntu-budgie:*|ubuntucinnamon:*|xubuntu:*| \
ubuntukylin:*|ubuntu-mate:*|ubuntu-core-installer:*|lubuntu:*) ubuntukylin:*|ubuntu-mate:*|ubuntu-core-installer:*|lubuntu:*)
# Ensure that most things e.g. includes.chroot are copied as is # Ensure that most things e.g. includes.chroot are copied as is
for entry in /usr/share/livecd-rootfs/live-build/${PROJECT}/*; do for entry in ${LIVECD_ROOTFS_ROOT}/live-build/${PROJECT}/*; do
case $entry in case $entry in
*hooks*) *hooks*)
# But hooks are shared across the projects with symlinks # But hooks are shared across the projects with symlinks
@ -1558,11 +1604,11 @@ esac
case $PROJECT in case $PROJECT in
ubuntu-oem|ubuntustudio|edubuntu|ubuntu-budgie|ubuntucinnamon| \ ubuntu-oem|ubuntustudio|edubuntu|ubuntu-budgie|ubuntucinnamon| \
xubuntu|ubuntukylin|ubuntu-mate|lubuntu) xubuntu|ubuntukylin|ubuntu-mate|lubuntu)
cp -af /usr/share/livecd-rootfs/live-build/ubuntu/includes.chroot \ cp -af ${LIVECD_ROOTFS_ROOT}/live-build/ubuntu/includes.chroot \
config/includes.chroot config/includes.chroot
LIVE_LAYER=${LIVE_PREFIX}live LIVE_LAYER=${LIVE_PREFIX}live
cp -af /usr/share/livecd-rootfs/live-build/ubuntu/includes.chroot.minimal.standard.live \ cp -af ${LIVECD_ROOTFS_ROOT}/live-build/ubuntu/includes.chroot.minimal.standard.live \
config/includes.chroot.$LIVE_LAYER config/includes.chroot.$LIVE_LAYER
if [ $PROJECT != ubuntu-oem ]; then if [ $PROJECT != ubuntu-oem ]; then
@ -1578,7 +1624,7 @@ esac
case $SUBPROJECT in case $SUBPROJECT in
buildd) buildd)
cp -af /usr/share/livecd-rootfs/live-build/buildd/* config/ cp -af ${LIVECD_ROOTFS_ROOT}/live-build/buildd/* config/
;; ;;
esac esac
@ -1602,7 +1648,7 @@ if [ "$EXTRA_PPAS" ]; then
extra_ppa=${extra_ppa%:*} extra_ppa=${extra_ppa%:*}
;; ;;
esac esac
extra_ppa_fingerprint="$(/usr/share/livecd-rootfs/get-ppa-fingerprint "$extra_ppa")" extra_ppa_fingerprint="$(${LIVECD_ROOTFS_ROOT}/get-ppa-fingerprint "$extra_ppa")"
cat >> config/archives/extra-ppas.list.chroot <<EOF cat >> config/archives/extra-ppas.list.chroot <<EOF
deb https://ppa.launchpadcontent.net/$extra_ppa/ubuntu @DISTRIBUTION@ main deb https://ppa.launchpadcontent.net/$extra_ppa/ubuntu @DISTRIBUTION@ main
@ -1689,3 +1735,22 @@ apt-get -y download $PREINSTALL_POOL
EOF EOF
fi fi
fi fi
if [ "${MAKE_ISO}" = "yes" ]; then
# XXX should pass --build-type here.
${LIVECD_ROOTFS_ROOT}/live-build/gen-iso-ids \
--project $PROJECT ${SUBPROJECT:+--subproject $SUBPROJECT} \
--arch $ARCH ${SUBARCH:+--subarch $SUBARCH} ${NOW+--serial $NOW} \
--output-dir config/iso-ids/
fi
if [ -n "$http_proxy" ]; then
mkdir -p /etc/systemd/system/snapd.service.d/
cat > /etc/systemd/system/snapd.service.d/snap_proxy.conf <<EOF
[Service]
Environment="HTTP_PROXY=${http_proxy}"
Environment="HTTPS_PROXY=${http_proxy}"
EOF
systemctl daemon-reload
systemctl restart snapd.service
fi

218
live-build/build-livefs Executable file
View File

@ -0,0 +1,218 @@
#!/usr/bin/python3
import configparser
import os
import pathlib
import platform
import subprocess
import click
_CONFIG_FILE = pathlib.Path.home() / ".config" / "livecd-rootfs" / "build-livefs.conf"
def _read_config() -> dict[str, str]:
"""Read default values from the user config file if it exists.
The config file uses INI format with a [defaults] section, e.g.:
[defaults]
http-proxy = http://squid.internal:3128/
mirror = http://ftpmaster.internal/ubuntu/
"""
cp = configparser.ConfigParser()
cp.read(_CONFIG_FILE)
return dict(cp["defaults"]) if "defaults" in cp else {}
_MACHINE_TO_ARCH = {
"x86_64": "amd64",
"aarch64": "arm64",
"ppc64le": "ppc64el",
"s390x": "s390x",
"riscv64": "riscv64",
"armv7l": "armhf",
}
def _default_arch():
machine = platform.machine()
try:
return _MACHINE_TO_ARCH[machine]
except KeyError:
raise click.UsageError(
f"Cannot determine default arch for machine {machine!r}; use --arch"
)
@click.command()
@click.option(
"--work-dir",
default=".",
type=click.Path(file_okay=False, path_type=pathlib.Path),
help="Working directory for the build (default: current directory)",
)
@click.option("--project", required=True, help="Project name (e.g. ubuntu, ubuntu-cpc)")
@click.option("--suite", required=True, help="Ubuntu suite/series (e.g. noble)")
@click.option("--arch", default=None, help="Target architecture (default: host arch)")
@click.option("--arch-variant", default=None, help="Architecture variant")
@click.option("--subproject", default=None, help="Subproject")
@click.option("--subarch", default=None, help="Sub-architecture")
@click.option("--channel", default=None, help="Channel")
@click.option(
"--image-target",
"image_targets",
multiple=True,
help="Image target (may be repeated)",
)
@click.option("--repo-snapshot-stamp", default=None, help="Repository snapshot stamp")
@click.option(
"--snapshot-service-timestamp", default=None, help="Snapshot service timestamp"
)
@click.option("--cohort-key", default=None, help="Cohort key")
@click.option("--datestamp", default=None, help="Datestamp (sets NOW)")
@click.option("--image-format", default=None, help="Image format (sets IMAGEFORMAT)")
@click.option(
"--proposed",
is_flag=True,
default=False,
help="Enable proposed pocket (sets PROPOSED=1)",
)
@click.option(
"--extra-ppa", "extra_ppas", multiple=True, help="Extra PPA (may be repeated)"
)
@click.option(
"--extra-snap", "extra_snaps", multiple=True, help="Extra snap (may be repeated)"
)
@click.option("--build-type", default=None, help="Build type")
@click.option(
"--http-proxy",
default=None,
help="HTTP proxy (sets http_proxy, HTTP_PROXY, LB_APT_HTTP_PROXY)",
)
@click.option(
"--mirror",
default=None,
help="Ubuntu archive mirror URL (sets MIRROR)",
)
@click.option(
"--debug", is_flag=True, default=False, help="Enable debug mode (set -x in lb scripts)"
)
def main(
work_dir,
project,
suite,
arch,
arch_variant,
subproject,
subarch,
channel,
image_targets,
repo_snapshot_stamp,
snapshot_service_timestamp,
cohort_key,
datestamp,
image_format,
proposed,
extra_ppas,
extra_snaps,
build_type,
http_proxy,
mirror,
debug,
):
cfg = _read_config()
if http_proxy is None:
http_proxy = cfg.get("http-proxy")
if mirror is None:
mirror = cfg.get("mirror")
if arch is None:
arch = _default_arch()
# Locate auto/ scripts relative to this script, following symlinks.
# Works for: git checkout, installed deb, and /usr/bin/build-livefs symlink.
live_build_dir = pathlib.Path(__file__).resolve().parent
auto_source = live_build_dir / "auto"
# base_env is passed to both lb config and lb build
base_env = {
"PROJECT": project,
"ARCH": arch,
"LIVECD_ROOTFS_ROOT": str(live_build_dir.parent),
}
if arch_variant is not None:
base_env["ARCH_VARIANT"] = arch_variant
if subproject is not None:
base_env["SUBPROJECT"] = subproject
if subarch is not None:
base_env["SUBARCH"] = subarch
if channel is not None:
base_env["CHANNEL"] = channel
if image_targets:
base_env["IMAGE_TARGETS"] = " ".join(image_targets)
if repo_snapshot_stamp is not None:
base_env["REPO_SNAPSHOT_STAMP"] = repo_snapshot_stamp
if snapshot_service_timestamp is not None:
base_env["SNAPSHOT_SERVICE_TIMESTAMP"] = snapshot_service_timestamp
if cohort_key is not None:
base_env["COHORT_KEY"] = cohort_key
if http_proxy is not None:
base_env["http_proxy"] = http_proxy
base_env["HTTP_PROXY"] = http_proxy
base_env["LB_APT_HTTP_PROXY"] = http_proxy
# config_env adds lb-config-only vars on top of base_env
config_env = {
**base_env,
"SUITE": suite,
}
if datestamp is not None:
config_env["NOW"] = datestamp
if image_format is not None:
config_env["IMAGEFORMAT"] = image_format
if proposed:
config_env["PROPOSED"] = "1"
if extra_ppas:
config_env["EXTRA_PPAS"] = " ".join(extra_ppas)
if extra_snaps:
config_env["EXTRA_SNAPS"] = " ".join(extra_snaps)
if build_type is not None:
config_env["BUILD_TYPE"] = build_type
if mirror is not None:
config_env["MIRROR"] = mirror
work_dir = work_dir.resolve()
work_dir.mkdir(parents=True, exist_ok=True)
# Create/replace auto/ symlinks
auto_dir = work_dir / "auto"
auto_dir.mkdir(exist_ok=True)
for script in ("config", "build", "clean"):
link = auto_dir / script
if link.is_symlink() or link.exists():
link.unlink()
link.symlink_to(auto_source / script)
# Write debug.sh if requested
if debug:
debug_dir = work_dir / "local" / "functions"
debug_dir.mkdir(parents=True, exist_ok=True)
(debug_dir / "debug.sh").write_text("set -x\n")
def run(cmd, env_extra):
env = os.environ.copy()
env.update(env_extra)
if os.getuid() != 0:
env_args = [f"{k}={v}" for k, v in env_extra.items()]
cmd = ["sudo", "env"] + env_args + cmd
subprocess.run(cmd, cwd=work_dir, env=env, check=True)
run(["lb", "clean", "--purge"], base_env)
run(["lb", "config"], config_env)
run(["lb", "build"], base_env)
if __name__ == "__main__":
main()

150
live-build/build-livefs-lxd Executable file
View File

@ -0,0 +1,150 @@
#!/usr/bin/env python3
import configparser
import pathlib
import subprocess
import time
import click
_CONFIG_FILE = pathlib.Path.home() / ".config" / "livecd-rootfs" / "build-livefs.conf"
def _read_config() -> dict[str, str]:
cp = configparser.ConfigParser()
cp.read(_CONFIG_FILE)
return dict(cp["defaults"]) if "defaults" in cp else {}
@click.command(
context_settings={"ignore_unknown_options": True, "allow_extra_args": True}
)
@click.option("--suite", required=True, help="Ubuntu suite/series (e.g. noble)")
@click.option(
"--vm-name",
default=None,
help="LXD VM name (default: livefs-builder-{suite})",
)
@click.option(
"--http-proxy",
default=None,
help="HTTP proxy URL for apt inside the VM (also read from build-livefs.conf)",
)
@click.argument("extra_args", nargs=-1, type=click.UNPROCESSED)
def main(suite, vm_name, http_proxy, extra_args):
livecd_rootfs_root = pathlib.Path(__file__).resolve().parent.parent
vm_name = vm_name or f"livefs-builder-{suite}"
host_conf = (
pathlib.Path.home() / ".config" / "livecd-rootfs" / "build-livefs.conf"
)
if http_proxy is None:
http_proxy = _read_config().get("http-proxy")
result = subprocess.run(["lxc", "info", vm_name], capture_output=True)
if result.returncode != 0:
launch_cmd = [
"lxc", "launch", f"ubuntu-daily:{suite}", vm_name, "--vm",
"--config", "limits.cpu=4",
"--config", "limits.memory=8GiB",
"--device", "root,size=100GiB",
]
user_data = "#cloud-config\npackage_update: true\n"
if http_proxy is not None:
user_data += (
"apt:\n"
f" http_proxy: {http_proxy}\n"
f" https_proxy: {http_proxy}\n"
)
launch_cmd += ["--config", f"user.user-data={user_data}"]
subprocess.run(launch_cmd, check=True)
device_info = subprocess.run(
["lxc", "config", "device", "show", vm_name],
capture_output=True,
text=True,
check=True,
).stdout
if "livecd-rootfs" not in device_info:
subprocess.run(
[
"lxc",
"config",
"device",
"add",
vm_name,
"livecd-rootfs",
"disk",
f"source={livecd_rootfs_root}",
"path=/srv/livecd-rootfs",
],
check=True,
)
info = subprocess.run(
["lxc", "info", vm_name], capture_output=True, text=True, check=True
).stdout
if "Status: STOPPED" in info:
subprocess.run(["lxc", "start", vm_name], check=True)
for _ in range(30):
result = subprocess.run(
["lxc", "exec", vm_name, "--", "true"], capture_output=True
)
if result.returncode == 0:
break
time.sleep(2)
else:
raise click.ClickException(f"VM {vm_name!r} did not become ready in time")
subprocess.run(
["lxc", "exec", vm_name, "--", "cloud-init", "status", "--wait"], check=True
)
subprocess.run(
["lxc", "exec", vm_name, "--", "apt-get", "install", "-y", "livecd-rootfs"],
check=True,
)
if host_conf.exists():
subprocess.run(
[
"lxc",
"exec",
vm_name,
"--",
"mkdir",
"-p",
"/root/.config/livecd-rootfs",
],
check=True,
)
subprocess.run(
[
"lxc",
"file",
"push",
str(host_conf),
f"{vm_name}/root/.config/livecd-rootfs/build-livefs.conf",
],
check=True,
)
subprocess.run(
[
"lxc",
"exec",
vm_name,
"--",
"/srv/livecd-rootfs/live-build/build-livefs",
"--suite",
suite,
*extra_args,
],
check=True,
)
if __name__ == "__main__":
main()

View File

@ -137,7 +137,9 @@ EOF
disk_image=binary/boot/disk-uefi.ext4 disk_image=binary/boot/disk-uefi.ext4
create_empty_disk_image "${disk_image}" # Adjust the image size to accomodate increasing package sizes
size=$(( imagesize*115/100 ))
create_empty_disk_image "${disk_image}" "${size}"
create_partitions "${disk_image}" create_partitions "${disk_image}"
mount_image "${disk_image}" 1 mount_image "${disk_image}" 1

View File

@ -1,6 +1,7 @@
# vi: ts=4 expandtab syntax=sh # vi: ts=4 expandtab syntax=sh
# default imagesize = 2252*1024**2 = 2.2G (the current size we ship) # default imagesize = 2252*1024**2 = 2.2G (the current size we ship)
# However this value may be overridden in individual hooks/binaries (like in buildd and ubuntu-cpc)
imagesize=${IMAGE_SIZE:-2361393152} imagesize=${IMAGE_SIZE:-2361393152}
fs_label="${FS_LABEL:-rootfs}" fs_label="${FS_LABEL:-rootfs}"
@ -33,7 +34,8 @@ clean_loops() {
create_empty_disk_image() { create_empty_disk_image() {
# Prepare an empty disk image # Prepare an empty disk image
dd if=/dev/zero of="$1" bs=1 count=0 seek="${imagesize}" size=${2:-$imagesize}
dd if=/dev/zero of="$1" bs=1 count=0 seek="${size}"
} }
create_manifest() { create_manifest() {
@ -42,6 +44,7 @@ create_manifest() {
local base_default_sbom_name="ubuntu-cloud-image-$(grep "VERSION_ID" $chroot_root/etc/os-release | cut --delimiter "=" --field 2 | tr -d '"')-${ARCH}-$(date +%Y%m%dT%H:%M:%S)" local base_default_sbom_name="ubuntu-cloud-image-$(grep "VERSION_ID" $chroot_root/etc/os-release | cut --delimiter "=" --field 2 | tr -d '"')-${ARCH}-$(date +%Y%m%dT%H:%M:%S)"
local sbom_file_name=${3:-"${base_default_sbom_name}.spdx"} local sbom_file_name=${3:-"${base_default_sbom_name}.spdx"}
local sbom_document_name=${4:-"${base_default_sbom_name}"} local sbom_document_name=${4:-"${base_default_sbom_name}"}
local should_include_sbom=${5:-"true"}
local sbom_log=${sbom_document_name}.log local sbom_log=${sbom_document_name}.log
echo "create_manifest chroot_root: ${chroot_root}" echo "create_manifest chroot_root: ${chroot_root}"
dpkg-query --show --admindir="${chroot_root}/var/lib/dpkg" > ${target_file} dpkg-query --show --admindir="${chroot_root}/var/lib/dpkg" > ${target_file}
@ -52,6 +55,7 @@ create_manifest() {
echo "create_manifest creating file listing." echo "create_manifest creating file listing."
local target_filelist=${2%.manifest}.filelist local target_filelist=${2%.manifest}.filelist
(cd "${chroot_root}" && find -xdev) | sort > "${target_filelist}" (cd "${chroot_root}" && find -xdev) | sort > "${target_filelist}"
if [ "$should_include_sbom" = "true" ]; then
# only creating sboms for CPC project at this time # only creating sboms for CPC project at this time
if [[ ! $(which cpc-sbom) ]]; then if [[ ! $(which cpc-sbom) ]]; then
# ensure the tool is installed # ensure the tool is installed
@ -68,6 +72,9 @@ create_manifest() {
else else
echo "SBOM generation succeeded. see ${sbom_log} for details" echo "SBOM generation succeeded. see ${sbom_log} for details"
fi fi
else
echo "SBOM generation skipped"
fi
fi fi
echo "create_manifest finished" echo "create_manifest finished"
} }
@ -181,8 +188,8 @@ setup_mountpoint() {
mount sysfs-live -t sysfs "$mountpoint/sys" mount sysfs-live -t sysfs "$mountpoint/sys"
mount securityfs -t securityfs "$mountpoint/sys/kernel/security" mount securityfs -t securityfs "$mountpoint/sys/kernel/security"
# Provide more up to date apparmor features, matching target kernel # Provide more up to date apparmor features, matching target kernel
mount -o bind /usr/share/livecd-rootfs/live-build/apparmor/generic "$mountpoint/sys/kernel/security/apparmor/features/" mount -o bind ${LIVECD_ROOTFS_ROOT}/live-build/apparmor/generic "$mountpoint/sys/kernel/security/apparmor/features/"
mount -o bind /usr/share/livecd-rootfs/live-build/seccomp/generic.actions_avail "$mountpoint/proc/sys/kernel/seccomp/actions_avail" mount -o bind ${LIVECD_ROOTFS_ROOT}/live-build/seccomp/generic.actions_avail "$mountpoint/proc/sys/kernel/seccomp/actions_avail"
# cgroup2 mount for LP: 1944004 # cgroup2 mount for LP: 1944004
mount -t cgroup2 none "$mountpoint/sys/fs/cgroup" mount -t cgroup2 none "$mountpoint/sys/fs/cgroup"
mount -t tmpfs none "$mountpoint/tmp" mount -t tmpfs none "$mountpoint/tmp"
@ -401,7 +408,7 @@ create_squashfs() {
squashfs_file="$2" squashfs_file="$2"
config_dir="$PWD/config" config_dir="$PWD/config"
(cd $rootfs_dir && (cd $rootfs_dir &&
mksquashfs . $squashfs_file -no-progress -xattrs -comp xz \ mksquashfs . $squashfs_file -no-progress -xattrs -comp "${SQUASHFS_COMP:-xz}" \
-ef "$config_dir/squashfs-exclude-files") -ef "$config_dir/squashfs-exclude-files")
} }
@ -566,8 +573,8 @@ _snap_post_process() {
# If the 'core' snap is not present, assume we are coreXX-only and # If the 'core' snap is not present, assume we are coreXX-only and
# install the snapd snap. # install the snapd snap.
channel=stable channel=stable
if [ $SUBPROJECT = "dangerous" ]; then if [ "$SUBPROJECT" = "dangerous" ]; then
channel=$CHANNEL channel=edge
fi fi
if [ ! -f ${snaps_dir}/core_[0-9]*.snap ]; then if [ ! -f ${snaps_dir}/core_[0-9]*.snap ]; then
_snap_preseed $CHROOT_ROOT snapd "$channel" _snap_preseed $CHROOT_ROOT snapd "$channel"
@ -666,7 +673,12 @@ _snap_preseed() {
fi fi
fi fi
_snap_preseed $CHROOT_ROOT $core_snap stable local core_channel=stable
if [ "$SUBPROJECT" = "dangerous" ]; then
core_channel=edge
fi
_snap_preseed $CHROOT_ROOT $core_snap $core_channel
fi fi
;; ;;
esac esac
@ -795,6 +807,14 @@ snap_preseed() {
esac esac
fi fi
if [ "$SUBPROJECT" = "dangerous" ]; then
# For the dangerous ISOs we want to include edge versions of all the
# snaps. Many snaps have a channel like "1/stable/ubuntu-X.Y" in the
# seed but the ubuntu-X.Y branches don't usually exist in the edge
# channel so strip that off as well.
CHANNEL=$(echo $CHANNEL | sed -e s/stable.*/edge/)
fi
# At this point: # At this point:
# SNAP_NAME is just the snap name # SNAP_NAME is just the snap name
# SNAP is either $SNAP_NAME or $SNAP_NAME/classic for classic confined # SNAP is either $SNAP_NAME or $SNAP_NAME/classic for classic confined
@ -840,7 +860,7 @@ snap_validate_seed() {
fi fi
if [ ${boot_filename} != undefined ]; then # we have a known boot file so we can proceed with checking for features to mount if [ ${boot_filename} != undefined ]; then # we have a known boot file so we can proceed with checking for features to mount
kern_major_min=$(readlink --canonicalize --no-newline ${CHROOT_ROOT}/boot/${boot_filename} | grep --extended-regexp --only-matching --max-count 1 '[0-9]+\.[0-9]+') kern_major_min=$(readlink --canonicalize --no-newline ${CHROOT_ROOT}/boot/${boot_filename} | grep --extended-regexp --only-matching --max-count 1 '[0-9]+\.[0-9]+')
if [ -d /usr/share/livecd-rootfs/live-build/apparmor/${kern_major_min} ]; then if [ -d ${LIVECD_ROOTFS_ROOT}/live-build/apparmor/${kern_major_min} ]; then
# if an Ubuntu version has different kernel apparmor features between LTS and HWE kernels # if an Ubuntu version has different kernel apparmor features between LTS and HWE kernels
# a snap pre-seeding issue can occur, where the incorrect apparmor features are reported # a snap pre-seeding issue can occur, where the incorrect apparmor features are reported
# basic copy of a directory structure overriding the "generic" feature set # basic copy of a directory structure overriding the "generic" feature set
@ -848,11 +868,22 @@ snap_validate_seed() {
# Bind kernel apparmor directory to feature directory for snap preseeding # Bind kernel apparmor directory to feature directory for snap preseeding
umount "${CHROOT_ROOT}/sys/kernel/security/apparmor/features/" umount "${CHROOT_ROOT}/sys/kernel/security/apparmor/features/"
mount --bind /usr/share/livecd-rootfs/live-build/apparmor/${kern_major_min} "${CHROOT_ROOT}/sys/kernel/security/apparmor/features/" mount --bind ${LIVECD_ROOTFS_ROOT}/live-build/apparmor/${kern_major_min} "${CHROOT_ROOT}/sys/kernel/security/apparmor/features/"
fi fi
fi fi
if [ -e "${CHROOT_ROOT}/var/lib/snapd/seed/seed.yaml" ]; then if [ -e "${CHROOT_ROOT}/var/lib/snapd/seed/seed.yaml" ]; then
if [ "${SUBPROJECT}" = "dangerous" ]; then
# When we include a snap from edge instead of stable, it may have
# require different content provider snaps to be installed and it
# is not reasonable to have the seed contain this. So run this
# script which figures out which content provider snaps are
# missing and include them.
./config/snap-seed-missing-providers "${CHROOT_ROOT}/var/lib/snapd/seed/" >> config/missing-providers
while read snap; do
_snap_preseed chroot "${snap}" edge
done < config/missing-providers
fi
snap debug validate-seed "${CHROOT_ROOT}/var/lib/snapd/seed/seed.yaml" snap debug validate-seed "${CHROOT_ROOT}/var/lib/snapd/seed/seed.yaml"
/usr/lib/snapd/snap-preseed --reset $(realpath "${CHROOT_ROOT}") /usr/lib/snapd/snap-preseed --reset $(realpath "${CHROOT_ROOT}")
/usr/lib/snapd/snap-preseed $(realpath "${CHROOT_ROOT}") /usr/lib/snapd/snap-preseed $(realpath "${CHROOT_ROOT}")
@ -863,7 +894,7 @@ snap_validate_seed() {
# mount generic apparmor feature again (cleanup) # mount generic apparmor feature again (cleanup)
if [ -d /build/config/hooks.d/extra/apparmor/${kern_major_min} ]; then if [ -d /build/config/hooks.d/extra/apparmor/${kern_major_min} ]; then
umount "${CHROOT_ROOT}/sys/kernel/security/apparmor/features/" umount "${CHROOT_ROOT}/sys/kernel/security/apparmor/features/"
mount -o bind /usr/share/livecd-rootfs/live-build/apparmor/generic "${CHROOT_ROOT}/sys/kernel/security/apparmor/features/" mount -o bind ${LIVECD_ROOTFS_ROOT}/live-build/apparmor/generic "${CHROOT_ROOT}/sys/kernel/security/apparmor/features/"
fi fi
} }
@ -1057,6 +1088,7 @@ network:
version: 2 version: 2
renderer: NetworkManager renderer: NetworkManager
EOF EOF
chmod 600 chroot/etc/netplan/01-network-manager-all.yaml
# Do not limit cloud-init renderers to network-manager as suggested # Do not limit cloud-init renderers to network-manager as suggested
# in LP: #1982855 because subiquity needs to render full networking # in LP: #1982855 because subiquity needs to render full networking
# in ephemeral boot time when autoinstall.network is provided. # in ephemeral boot time when autoinstall.network is provided.
@ -1085,11 +1117,11 @@ EOF
mkdir -p chroot/etc/systemd/system/ mkdir -p chroot/etc/systemd/system/
cat <<EOF > chroot/etc/systemd/system/cloud-init-network.service cat <<EOF > chroot/etc/systemd/system/cloud-init-network.service
${AUTOMATION_HEADER} ${AUTOMATION_HEADER}
# Based on cloud-init 24.3 for Desktop LiveCD # Based on cloud-init 25.3 for Desktop LiveCD (LP: #2128887)
# Redact sysinit.target from Before, add After=NetworkManager*.service # Redact sysinit.target from Before, add After=NetworkManager*.service
# (LP: #2008952) # (LP: #2008952)
[Unit] [Unit]
# https://cloudinit.readthedocs.io/en/latest/explanation/boot.html # https://docs.cloud-init.io/en/latest/explanation/boot.html
Description=Cloud-init: Network Stage Description=Cloud-init: Network Stage
DefaultDependencies=no DefaultDependencies=no
Wants=cloud-init-local.service Wants=cloud-init-local.service
@ -1119,7 +1151,7 @@ Type=oneshot
# process has completed this stage. The output from the return socket is piped # process has completed this stage. The output from the return socket is piped
# into a shell so that the process can send a completion message (defaults to # into a shell so that the process can send a completion message (defaults to
# "done", otherwise includes an error message) and an exit code to systemd. # "done", otherwise includes an error message) and an exit code to systemd.
ExecStart=sh -c 'echo "start" | netcat -Uu -W1 /run/cloud-init/share/network.sock -s /run/cloud-init/share/network-return.sock | sh' ExecStart=sh -c 'echo "start" | nc -U /run/cloud-init/share/network.sock | sh'
RemainAfterExit=yes RemainAfterExit=yes
TimeoutSec=0 TimeoutSec=0
@ -1222,7 +1254,7 @@ setup_cidata() {
local mountpoint=$(mktemp -d) local mountpoint=$(mktemp -d)
mkfs.vfat -F 32 -n CIDATA ${cidata_dev} mkfs.vfat -F 32 -n CIDATA ${cidata_dev}
mount ${cidata_dev} ${mountpoint} mount ${cidata_dev} ${mountpoint}
cp /usr/share/livecd-rootfs/live-build/cidata/* ${mountpoint} cp ${LIVECD_ROOTFS_ROOT}/live-build/cidata/* ${mountpoint}
cat >>${mountpoint}/meta-data.sample <<END cat >>${mountpoint}/meta-data.sample <<END
#instance-id: iid-$(openssl rand -hex 8) #instance-id: iid-$(openssl rand -hex 8)
@ -1377,3 +1409,50 @@ EOF
EOF EOF
fi fi
} }
# Determine the appropriate partition type UUID for the root filesystem
# based on the architecture.
# Please see https://uapi-group.org/specifications/specs/discoverable_partitions_specification/
# for where the GUIDs come from.
gpt_root_partition_uuid() {
local ARCH="$1"
if [ -z "${ARCH:-}" ]; then
echo "usage: gpt_root_partition_uuid <arch>"
exit 1
fi
case "${ARCH}" in
"amd64")
ROOTFS_PARTITION_TYPE="4f68bce3-e8cd-4db1-96e7-fbcaf984b709"
;;
"arm64")
ROOTFS_PARTITION_TYPE="b921b045-1df0-41c3-af44-4c6f280d3fae"
;;
"armhf")
ROOTFS_PARTITION_TYPE="69dad710-2ce4-4e3c-b16c-21a1d49abed3"
;;
"riscv64")
ROOTFS_PARTITION_TYPE="72ec70a6-cf74-40e6-bd49-4bda08e8f224"
;;
"ppc64el")
ROOTFS_PARTITION_TYPE="c31c45e6-3f39-412e-80fb-4809c4980599"
;;
"s390x")
ROOTFS_PARTITION_TYPE="5eead9a9-fe09-4a1e-a1d7-520d00531306"
;;
*)
echo "Unsupported architecture: ${ARCH}"
exit 1
;;
esac
echo "${ROOTFS_PARTITION_TYPE}"
}
# Wrapper for the isobuild tool. Sets PYTHONPATH so the isobuilder module
# is importable, and uses config/iso-dir as the standard working directory
# for ISO metadata and intermediate files.
isobuild () {
PYTHONPATH=${LIVECD_ROOTFS_ROOT}/live-build/ ${LIVECD_ROOTFS_ROOT}/live-build/isobuild --workdir config/iso-dir "$@"
}

198
live-build/gen-iso-ids Executable file
View File

@ -0,0 +1,198 @@
#!/usr/bin/python3
# Compute various slightly obscure IDs and labels used by ISO builds.
#
# * ISO9660 images have a "volume id".
# * Our ISOs contain a ".disk/info" file that is read by various
# other things (casper, the installer) and is generally used as a
# record of where an installation came from.
# * The code that sets up grub for the ISO needs a "capitalized
# project name" or capproject.
#
# All of these are derived from other build parameters (and/or
# information in etc/os-release) in slightly non-obvious ways so the
# logic to do so is confined to this file to avoid it cluttering
# anywhere else.
import pathlib
import platform
import time
import click
# Be careful about the values here. They end up in .disk/info, which is read by
# casper to create the live session user, so if there is a space in the
# capproject things go a bit wonky.
#
# It will also be used by make_vol_id to construct an ISO9660 volume ID as
#
# "$(CAPPROJECT) $(DEBVERSION) $(ARCH)",
#
# e.g. "Ubuntu 14.10 amd64". The volume ID is limited to 32 characters. This
# therefore imposes a limit on the length of project_map values of 25 - (length
# of longest relevant architecture name).
project_to_capproject_map = {
"edubuntu": "Edubuntu",
"kubuntu": "Kubuntu",
"lubuntu": "Lubuntu",
"ubuntu": "Ubuntu",
"ubuntu-base": "Ubuntu-Base",
"ubuntu-budgie": "Ubuntu-Budgie",
"ubuntu-core-installer": "Ubuntu-Core-Installer",
"ubuntu-mate": "Ubuntu-MATE",
"ubuntu-mini-iso": "Ubuntu-Mini-ISO",
"ubuntu-test-iso": "Ubuntu-Test-ISO",
"ubuntu-oem": "Ubuntu OEM",
"ubuntu-server": "Ubuntu-Server",
"ubuntu-unity": "Ubuntu-Unity",
"ubuntu-wsl": "Ubuntu WSL",
"ubuntucinnamon": "Ubuntu-Cinnamon",
"ubuntukylin": "Ubuntu-Kylin",
"ubuntustudio": "Ubuntu-Studio",
"xubuntu": "Xubuntu",
}
def make_disk_info(
os_release: dict[str, str],
arch: str,
subarch: str,
capproject: str,
subproject: str,
build_type: str,
serial: str,
) -> str:
# os-release VERSION is _almost_ what goes into .disk/info...
# it can be
# VERSION="24.04.3 LTS (Noble Numbat)"
# or
# VERSION="25.10 (Questing Quokka)"
# We want the Adjective Animal to be in quotes, not parentheses, e.g.
# 'Ubuntu 24.04.3 LTS "Noble Numbat"'. This format is expected by casper
# (which parses .disk/info to set up the live session) and the installer.
version = os_release["VERSION"]
version = version.replace("(", '"')
version = version.replace(")", '"')
capsubproject = ""
if subproject == "minimal":
capsubproject = " Minimal"
fullarch = arch
if subarch:
fullarch += "+" + subarch
return f"{capproject}{capsubproject} {version} - {build_type} {fullarch} ({serial})"
def make_vol_id(os_release: dict[str, str], arch: str, capproject: str) -> str:
# ISO9660 volume IDs are limited to 32 characters. The volume ID format is
# "CAPPROJECT VERSION ARCH", e.g. "Ubuntu 24.04.3 LTS amd64". Longer arch
# names like ppc64el and riscv64 can push us over the limit, so we shorten
# them here. This is why capproject names are also kept short (see the
# comment above project_to_capproject_map).
arch_for_volid_map = {
"ppc64el": "ppc64",
"riscv64": "riscv",
}
arch_for_volid = arch_for_volid_map.get(arch, arch)
# from
# VERSION="24.04.3 LTS (Noble Numbat)"
# or
# VERSION="25.10 (Questing Quokka)"
# we want "24.04.3 LTS" or "25.10", i.e. everything up to the first "(" (apart
# from the whitespace).
version = os_release["VERSION"].split("(")[0].strip()
volid = f"{capproject} {version} {arch_for_volid}"
# If still over 32 characters (e.g. long capproject + LTS version), fall
# back to shorter forms. amd64 gets "x64" since it's widely recognized and
# fits; other architectures just drop the arch entirely since multi-arch
# ISOs are less common for non-amd64 platforms.
if len(volid) > 32:
if arch == "amd64":
volid = f"{capproject} {version} x64"
else:
volid = f"{capproject} {version}"
return volid
@click.command()
@click.option(
"--project",
type=str,
required=True,
)
@click.option(
"--subproject",
type=str,
default=None,
)
@click.option(
"--arch",
type=str,
required=True,
)
@click.option(
"--subarch",
type=str,
default=None,
)
@click.option(
"--serial",
type=str,
default=time.strftime("%Y%m%d"),
)
@click.option(
"--build-type",
type=str,
default="Daily",
)
@click.option(
"--output-dir",
type=click.Path(file_okay=False, resolve_path=True, path_type=pathlib.Path),
required=True,
help="working directory",
)
def main(
project: str,
subproject: str,
arch: str,
subarch: str,
serial: str,
build_type: str,
output_dir: pathlib.Path,
):
output_dir.mkdir(exist_ok=True)
capproject = project_to_capproject_map[project]
os_release = platform.freedesktop_os_release()
with output_dir.joinpath("disk-info").open("w") as fp:
disk_info = make_disk_info(
os_release,
arch,
subarch,
capproject,
subproject,
build_type,
serial,
)
print(f"disk_info: {disk_info!r}")
fp.write(disk_info)
with output_dir.joinpath("vol-id").open("w") as fp:
vol_id = make_vol_id(os_release, arch, capproject)
print(f"vol_id: {vol_id!r} {len(vol_id)}")
fp.write(vol_id)
with output_dir.joinpath("capproject").open("w") as fp:
print(f"capproject: {capproject!r}")
fp.write(capproject)
if __name__ == "__main__":
main()

221
live-build/isobuild Executable file
View File

@ -0,0 +1,221 @@
#!/usr/bin/python3
# Building an ISO requires knowing:
#
# * The architecture and series we are building for
# * The address of the mirror to pull packages from the pool from and the
# components of that mirror to use
# * The list of packages to include in the pool
# * Where the squashfs files that contain the rootfs and other metadata layers
# are
# * Where to put the final ISO
# * All the bits of information that end up in .disk/info on the ISO and in the
# "volume ID" for the ISO
#
# It's not completely trivial to come up with a nice feeling interface between
# livecd-rootfs and this tool. There are about 13 parameters that are needed to
# build the ISO and having a tool take 13 arguments seems a bit overwhelming. In
# addition some steps need to run before the layers are made into squashfs files
# and some after. It felt nicer to have a tool with a few subcommands (7, in the
# end) and taking arguments relevant to each step:
#
# $ isobuild --work-dir "" init --disk-id "" --series "" --arch ""
#
# Set up the work-dir for later steps. Create the skeleton file layout of the
# ISO, populate .disk/info etc, create the gpg key referred to above. Store
# series and arch somewhere that later steps can refer to.
#
# $ isobuild --work-dir "" setup-apt --chroot ""
#
# Set up aptfor use by later steps, using the configuration from the passed
# chroot.
#
# $ isobuild --work-dir "" generate-pool --package-list-file ""
#
# Create the pool from the passed germinate output file.
#
# $ isobuild --work-dir "" generate-sources --mountpoint ""
#
# Generate an apt deb822 source for the pool, assuming it is mounted at the
# passed mountpoint, and output it on stdout.
#
# $ isobuild --work-dir "" add-live-filesystem --artifact-prefix ""
#
# Copy the relevant artifacts to the casper directory (and extract the uuids
# from the initrds)
#
# $ isobuild --work-dir "" make-bootable --project "" --capitalized-project ""
# --subarch ""
#
# Set up the bootloader etc so that the ISO can boot (for this clones debian-cd
# and run the tools/boot/$series-$arch script but those should be folded into
# isobuild fairly promptly IMO).
#
# $ isobuild --work-dir "" make-iso --vol-id "" --dest ""
#
# Generate the checksum file and run xorriso to build the final ISO.
import pathlib
import shlex
import click
from isobuilder.builder import ISOBuilder
@click.group()
@click.option(
"--workdir",
type=click.Path(file_okay=False, resolve_path=True, path_type=pathlib.Path),
required=True,
help="working directory",
)
@click.pass_context
def main(ctxt, workdir):
ctxt.obj = ISOBuilder(workdir)
cwd = pathlib.Path().cwd()
if workdir.is_relative_to(cwd):
workdir = workdir.relative_to(cwd)
ctxt.obj.logger.log(f"isobuild starting, workdir: {workdir}")
def subcommand(f):
"""Decorator that converts a function into a Click subcommand with logging.
This decorator:
1. Converts function name from snake_case to kebab-case for the CLI
2. Wraps the function to log the subcommand name and all parameters
3. Registers it as a Click command under the main command group
4. Extracts the ISOBuilder instance from the context and passes it as first arg
"""
name = f.__name__.replace("_", "-")
def wrapped(ctxt, **kw):
# Build a log message showing the subcommand and all its parameters.
# We use ctxt.params (Click's resolved parameters) rather than **kw
# because ctxt.params includes path resolution and type conversion.
# Paths are converted to relative form to keep logs readable and avoid
# exposing full filesystem paths in build artifacts.
msg = f"subcommand {name}"
cwd = pathlib.Path().cwd()
for k, v in sorted(ctxt.params.items()):
if isinstance(v, pathlib.Path):
if v.is_relative_to(cwd):
v = v.relative_to(cwd)
v = shlex.quote(str(v))
msg += f" {k}={v}"
with ctxt.obj.logger.logged(msg):
f(ctxt.obj, **kw)
return main.command(name=name)(click.pass_context(wrapped))
@click.option(
"--disk-info",
type=str,
required=True,
help="contents of .disk/info",
)
@click.option(
"--series",
type=str,
required=True,
help="series being built",
)
@click.option(
"--arch",
type=str,
required=True,
help="architecture being built",
)
@subcommand
def init(builder, disk_info, series, arch):
builder.init(disk_info, series, arch)
@click.option(
"--chroot",
type=click.Path(
file_okay=False, resolve_path=True, path_type=pathlib.Path, exists=True
),
required=True,
)
@subcommand
def setup_apt(builder, chroot: pathlib.Path):
builder.setup_apt(chroot)
@click.pass_obj
@click.option(
"--package-list-file",
type=click.Path(
dir_okay=False, exists=True, resolve_path=True, path_type=pathlib.Path
),
required=True,
)
@subcommand
def generate_pool(builder, package_list_file: pathlib.Path):
builder.generate_pool(package_list_file)
@click.option(
"--mountpoint",
type=str,
required=True,
)
@subcommand
def generate_sources(builder, mountpoint: str):
builder.generate_sources(mountpoint)
@click.option(
"--artifact-prefix",
type=click.Path(dir_okay=False, resolve_path=True, path_type=pathlib.Path),
required=True,
)
@subcommand
def add_live_filesystem(builder, artifact_prefix: pathlib.Path):
builder.add_live_filesystem(artifact_prefix)
@click.option(
"--project",
type=str,
required=True,
)
@click.option("--capproject", type=str, required=True)
@click.option(
"--subarch",
type=str,
default="",
)
@subcommand
def make_bootable(builder, project: str, capproject: str | None, subarch: str):
# capproject is the "capitalized project name" used in GRUB menu entries,
# e.g. "Ubuntu" or "Kubuntu". It should come from gen-iso-ids (which uses
# project_to_capproject_map for proper formatting like "Ubuntu-MATE"), but
# we provide a simple .capitalize() fallback for cases where the caller
# doesn't have the pre-computed value.
if capproject is None:
capproject = project.capitalize()
builder.make_bootable(project, capproject, subarch)
@click.option(
"--dest",
type=click.Path(dir_okay=False, resolve_path=True, path_type=pathlib.Path),
required=True,
)
@click.option(
"--volid",
type=str,
default=None,
)
@subcommand
def make_iso(builder, dest: pathlib.Path, volid: str | None):
builder.make_iso(dest, volid)
if __name__ == "__main__":
main()

View File

@ -0,0 +1 @@
#

View File

@ -0,0 +1,117 @@
import dataclasses
import os
import pathlib
import shutil
import subprocess
from typing import Iterator
@dataclasses.dataclass
class PackageInfo:
package: str
filename: str
architecture: str
version: str
@property
def spec(self) -> str:
return f"{self.package}:{self.architecture}={self.version}"
def check_proc(proc, ok_codes=(0,)) -> None:
proc.wait()
if proc.returncode not in ok_codes:
raise Exception(f"{proc} failed")
class AptStateManager:
"""Maintain and use an apt state directory to access package info and debs."""
def __init__(self, logger, series: str, apt_dir: pathlib.Path):
self.logger = logger
self.series = series
self.apt_root = apt_dir.joinpath("root")
self.apt_conf_path = apt_dir.joinpath("apt.conf")
def _apt_env(self) -> dict[str, str]:
return dict(os.environ, APT_CONFIG=str(self.apt_conf_path))
def setup(self, chroot: pathlib.Path):
"""Set up the manager by copying the apt configuration from `chroot`."""
for path in "etc/apt", "var/lib/apt":
tgt = self.apt_root.joinpath(path)
tgt.parent.mkdir(parents=True, exist_ok=True)
shutil.copytree(chroot.joinpath(path), tgt, ignore_dangling_symlinks=True)
self.apt_conf_path.write_text(f'Dir "{self.apt_root}/"; \n')
with self.logger.logged("updating apt indices"):
self.logger.run(["apt-get", "update"], env=self._apt_env())
def show(self, pkgs: list[str]) -> Iterator[PackageInfo]:
"""Return information about the binary packages named by `pkgs`.
Parses apt-cache output, which uses RFC822-like format: field names
followed by ": " and values, with multi-line values indented with
leading whitespace. We skip continuation lines (starting with space)
since PackageInfo only needs single-line fields.
The `fields` set (derived from PackageInfo's dataclass fields) acts as
a filter - we only extract fields we care about, ignoring others like
Description.
"""
proc = subprocess.Popen(
["apt-cache", "-o", "APT::Cache::AllVersions=0", "show"] + pkgs,
stdout=subprocess.PIPE,
encoding="utf-8",
env=self._apt_env(),
)
assert proc.stdout is not None
fields = {f.name for f in dataclasses.fields(PackageInfo)}
params: dict[str, str] = {}
for line in proc.stdout:
if line == "\n":
yield PackageInfo(**params)
params = {}
continue
if line.startswith(" "):
continue
field, value = line.split(": ", 1)
field = field.lower()
if field in fields:
params[field] = value.strip()
check_proc(proc)
if params:
yield PackageInfo(**params)
def download(self, rootdir: pathlib.Path, pkg_info: PackageInfo) -> None:
"""Download the package specified by `pkg_info` under `rootdir`.
The package is saved to the same path under `rootdir` as it is
at in the archive it comes from.
"""
target_dir = rootdir.joinpath(pkg_info.filename).parent
target_dir.mkdir(parents=True, exist_ok=True)
self.download_direct(pkg_info.spec, target_dir)
def download_direct(self, spec: str, target: pathlib.Path) -> None:
"""Download the package specified by spec to target directory.
The package is downloaded using apt-get download and saved directly
in the target directory.
"""
self.logger.run(
["apt-get", "download", spec],
cwd=target,
check=True,
env=self._apt_env(),
)
def in_release_path(self) -> pathlib.Path:
"""Return the path to the InRelease file.
This ignores all but the first path.
Will raise Error if there isn't at least one match.
"""
[path] = self.apt_root.joinpath("var/lib/apt/lists").glob(
f"*ubuntu.com*_dists_{self.series}_InRelease"
)
return path

View File

@ -0,0 +1,49 @@
"""Boot configuration package for ISO builder.
This package contains architecture-specific boot configurators for building
bootable ISOs for different architectures.
"""
import pathlib
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from ..apt_state import AptStateManager
from ..builder import Logger
from .base import BaseBootConfigurator
def make_boot_configurator_for_arch(
arch: str,
logger: "Logger",
apt_state: "AptStateManager",
workdir: pathlib.Path,
iso_root: pathlib.Path,
) -> "BaseBootConfigurator":
"""Factory function to create boot configurator for a specific architecture."""
match arch:
case "amd64":
from .amd64 import AMD64BootConfigurator
return AMD64BootConfigurator(logger, apt_state, workdir, iso_root)
case "arm64":
from .arm64 import ARM64BootConfigurator
return ARM64BootConfigurator(logger, apt_state, workdir, iso_root)
case "ppc64el":
from .ppc64el import PPC64ELBootConfigurator
return PPC64ELBootConfigurator(logger, apt_state, workdir, iso_root)
case "riscv64":
from .riscv64 import RISCV64BootConfigurator
return RISCV64BootConfigurator(logger, apt_state, workdir, iso_root)
case "s390x":
from .s390x import S390XBootConfigurator
return S390XBootConfigurator(logger, apt_state, workdir, iso_root)
case _:
raise ValueError(f"Unsupported architecture: {arch}")
__all__ = ["make_boot_configurator_for_arch"]

View File

@ -0,0 +1,216 @@
"""AMD64/x86_64 architecture boot configuration."""
import pathlib
import shutil
from .base import default_kernel_params
from .grub import copy_grub_modules
from .uefi import UEFIBootConfigurator
CALAMARES_PROJECTS = ["kubuntu", "lubuntu"]
class AMD64BootConfigurator(UEFIBootConfigurator):
"""Boot setup for AMD64/x86_64 architecture."""
efi_suffix = "x64"
grub_target = "x86_64"
arch = "amd64"
def mkisofs_opts(self) -> list[str | pathlib.Path]:
# Boring mkisofs options that should be set somewhere architecture independent.
opts: list[str | pathlib.Path] = ["-J", "-joliet-long", "-l"]
# Generalities on booting
#
# There is a 2x2 matrix of boot modes we care about: legacy or UEFI
# boot modes and having the installer be on a cdrom or a disk. Booting
# from cdrom uses the el torito standard and booting from disk expects
# a MBR or GPT partition table.
#
# https://wiki.osdev.org/El-Torito has a lot more background on this.
# ## Set up the mkisofs options for legacy boot.
# Set the el torito boot image "name", i.e. the path on the ISO
# containing the bootloader for legacy-cdrom boot.
opts.extend(["-b", "boot/grub/i386-pc/eltorito.img"])
# Back in the day, el torito booting worked by emulating a floppy
# drive. This hasn't been a useful way of operating for a long time.
opts.append("-no-emul-boot")
# Misc options to make the legacy-cdrom boot work.
opts.extend(["-boot-load-size", "4", "-boot-info-table", "--grub2-boot-info"])
# The bootloader to write to the MBR for legacy-disk boot.
#
# We use the grub stage1 bootloader, boot_hybrid.img, which then jumps
# to the eltorito image based on the information xorriso provides it
# via the --grub2-boot-info option.
opts.extend(
[
"--grub2-mbr",
self.scratch.joinpath("boot_hybrid.img"),
]
)
# ## Set up the mkisofs options for UEFI boot.
opts.extend(self.get_uefi_mkisofs_opts())
return opts
def extract_files(self) -> None:
with self.logger.logged("extracting AMD64 boot files"):
# Extract UEFI files (common with ARM64)
self.extract_uefi_files()
# AMD64-specific: Add BIOS/legacy boot files
with self.logger.logged("adding BIOS/legacy boot files"):
grub_pc_pkg_dir = self.scratch.joinpath("grub-pc-pkg")
self.download_and_extract_package("grub-pc-bin", grub_pc_pkg_dir)
grub_boot_dir = self.iso_root.joinpath("boot", "grub", "i386-pc")
grub_boot_dir.mkdir(parents=True, exist_ok=True)
src_grub_dir = grub_pc_pkg_dir.joinpath("usr", "lib", "grub", "i386-pc")
shutil.copy(src_grub_dir.joinpath("eltorito.img"), grub_boot_dir)
shutil.copy(src_grub_dir.joinpath("boot_hybrid.img"), self.scratch)
copy_grub_modules(
grub_pc_pkg_dir,
self.iso_root,
"i386-pc",
["*.mod", "*.lst", "*.o"],
)
def generate_grub_config(self) -> str:
"""Generate grub.cfg content for AMD64."""
result = self.grub_header()
if self.project == "ubuntu-mini-iso":
result += """\
menuentry "Choose an Ubuntu version to install" {
set gfxpayload=keep
linux /casper/vmlinuz iso-chooser-menu ip=dhcp ---
initrd /casper/initrd
}
"""
return result
kernel_params = default_kernel_params(self.project)
# Main menu entry
result += f"""\
menuentry "Try or Install {self.humanproject}" {{
set gfxpayload=keep
linux /casper/vmlinuz {kernel_params}
initrd /casper/initrd
}}
"""
# All but server get safe-graphics mode
if self.project != "ubuntu-server":
result += f"""\
menuentry "{self.humanproject} (safe graphics)" {{
set gfxpayload=keep
linux /casper/vmlinuz nomodeset {kernel_params}
initrd /casper/initrd
}}
"""
# ubiquity based projects get OEM mode
if "maybe-ubiquity" in kernel_params:
oem_kernel_params = kernel_params.replace(
"maybe-ubiquity", "only-ubiquity oem-config/enable=true"
)
result += f"""\
menuentry "OEM install (for manufacturers)" {{
set gfxpayload=keep
linux /casper/vmlinuz {oem_kernel_params}
initrd /casper/initrd
}}
"""
# Calamares-based projects get OEM mode
if self.project in CALAMARES_PROJECTS:
result += f"""\
menuentry "OEM install (for manufacturers)" {{
set gfxpayload=keep
linux /casper/vmlinuz {kernel_params} oem-config/enable=true
initrd /casper/initrd
}}
"""
# Currently only server is built with HWE, hence no safe-graphics/OEM
if self.hwe:
result += f"""\
menuentry "{self.humanproject} with the HWE kernel" {{
set gfxpayload=keep
linux /casper/hwe-vmlinuz {kernel_params}
initrd /casper/hwe-initrd
}}
"""
# UEFI Entries (wrapped in grub_platform check for dual BIOS/UEFI support)
uefi_menu_entries = self.uefi_menu_entries()
result += f"""\
grub_platform
if [ "$grub_platform" = "efi" ]; then
{uefi_menu_entries}\
fi
"""
return result
@staticmethod
def generate_loopback_config(grub_content: str) -> str:
"""Derive loopback.cfg from grub.cfg content.
Strips the header (up to menu_color_highlight) and the UEFI
trailer (from grub_platform to end), and adds iso-scan/filename
to linux lines.
"""
lines = grub_content.split("\n")
start_idx = 0
for i, line in enumerate(lines):
if "menu_color_highlight" in line:
start_idx = i + 1
break
end_idx = len(lines)
for i, line in enumerate(lines):
if "grub_platform" in line:
end_idx = i
break
loopback_lines = lines[start_idx:end_idx]
loopback_lines = [
(
line.replace("---", "iso-scan/filename=${iso_path} ---")
if "linux" in line
else line
)
for line in loopback_lines
]
return "\n".join(loopback_lines)
def make_bootable(
self,
project: str,
capproject: str,
subarch: str,
hwe: bool,
) -> None:
"""Make the ISO bootable, including generating loopback.cfg."""
super().make_bootable(project, capproject, subarch, hwe)
grub_cfg = self.iso_root.joinpath("boot", "grub", "grub.cfg")
grub_content = grub_cfg.read_text()
self.iso_root.joinpath("boot", "grub", "loopback.cfg").write_text(
self.generate_loopback_config(grub_content)
)

View File

@ -0,0 +1,76 @@
"""ARM 64-bit architecture boot configuration."""
import pathlib
from .uefi import UEFIBootConfigurator
from .base import default_kernel_params
class ARM64BootConfigurator(UEFIBootConfigurator):
"""Boot setup for ARM 64-bit architecture."""
efi_suffix = "aa64"
grub_target = "arm64"
arch = "arm64"
def mkisofs_opts(self) -> list[str | pathlib.Path]:
"""Return mkisofs options for ARM64."""
opts: list[str | pathlib.Path] = [
"-J",
"-joliet-long",
"-l",
"-c",
"boot/boot.cat",
]
# Add common UEFI options
opts.extend(self.get_uefi_mkisofs_opts())
# ARM64-specific: partition cylinder alignment
opts.extend(["-partition_cyl_align", "all"])
return opts
def extract_files(self) -> None:
"""Download and extract bootloader packages for ARM64."""
with self.logger.logged("extracting ARM64 boot files"):
self.extract_uefi_files()
def generate_grub_config(self) -> str:
"""Generate grub.cfg for ARM64."""
kernel_params = default_kernel_params(self.project)
result = self.grub_header()
# ARM64-specific: Snapdragon workarounds
result += f"""\
set cmdline=
smbios --type 4 --get-string 5 --set proc_version
regexp "Snapdragon.*" "$proc_version"
if [ $? = 0 ]; then
# Work around Snapdragon X firmware bug. cutmem is not allowed in lockdown mode.
if [ $lockdown != "y" ]; then
cutmem 0x8800000000 0x8fffffffff
fi
# arm64.nopauth works around 8cx Gen 3 firmware bug
cmdline="clk_ignore_unused pd_ignore_unused arm64.nopauth"
fi
menuentry "Try or Install {self.humanproject}" {{
set gfxpayload=keep
linux /casper/vmlinuz $cmdline {kernel_params} console=tty0
initrd /casper/initrd
}}
"""
# HWE kernel option if available
result += self.hwe_menu_entry(
"vmlinuz",
f"{kernel_params} console=tty0",
extra_params="$cmdline ",
)
# Note: ARM64 HWE also includes $dtb in the original shell script,
# but it's not actually set anywhere in the grub.cfg, so we omit it here
# UEFI Entries (ARM64 is UEFI-only, no grub_platform check needed)
result += self.uefi_menu_entries()
return result

View File

@ -0,0 +1,98 @@
"""Base classes and helper functions for boot configuration."""
import pathlib
import subprocess
import tempfile
from abc import ABC, abstractmethod
from ..builder import Logger
from ..apt_state import AptStateManager
def default_kernel_params(project: str) -> str:
if project == "ubuntukylin":
return (
"file=/cdrom/preseed/ubuntu.seed locale=zh_CN "
"keyboard-configuration/layoutcode?=cn quiet splash --- "
)
if project == "ubuntu-server":
return " --- "
else:
return " --- quiet splash"
class BaseBootConfigurator(ABC):
"""Abstract base class for architecture-specific boot configurators.
Subclasses must implement:
- extract_files(): Download and extract bootloader packages
- mkisofs_opts(): Return mkisofs command-line options
"""
def __init__(
self,
logger: Logger,
apt_state: AptStateManager,
workdir: pathlib.Path,
iso_root: pathlib.Path,
) -> None:
self.logger = logger
self.apt_state = apt_state
self.scratch = workdir.joinpath("boot-stuff")
self.iso_root = iso_root
def download_and_extract_package(
self, pkg_name: str, target_dir: pathlib.Path
) -> None:
"""Download a Debian package and extract its contents to target directory."""
self.logger.log(f"downloading and extracting {pkg_name}")
target_dir.mkdir(exist_ok=True, parents=True)
with tempfile.TemporaryDirectory() as tdir_str:
tdir = pathlib.Path(tdir_str)
self.apt_state.download_direct(pkg_name, tdir)
[deb] = tdir.glob("*.deb")
dpkg_proc = subprocess.Popen(
["dpkg-deb", "--fsys-tarfile", deb], stdout=subprocess.PIPE
)
tar_proc = subprocess.Popen(
["tar", "xf", "-", "-C", target_dir], stdin=dpkg_proc.stdout
)
assert dpkg_proc.stdout is not None
dpkg_proc.stdout.close()
tar_proc.communicate()
@abstractmethod
def extract_files(self) -> None:
"""Download and extract bootloader packages to the boot tree.
Each architecture must implement this to set up its specific bootloader files.
"""
...
@abstractmethod
def mkisofs_opts(self) -> list[str | pathlib.Path]:
"""Return mkisofs command-line options for this architecture.
Returns:
List of command-line options to pass to mkisofs/xorriso.
"""
...
def post_process_iso(self, iso_path: pathlib.Path) -> None:
"""Post-process the ISO image after xorriso creates it."""
def make_bootable(
self,
project: str,
capproject: str,
subarch: str,
hwe: bool,
) -> None:
"""Make the ISO bootable by extracting bootloader files."""
self.project = project
self.humanproject = capproject.replace("-", " ")
self.subarch = subarch
self.hwe = hwe
self.scratch.mkdir(exist_ok=True)
with self.logger.logged("configuring boot"):
self.extract_files()

View File

@ -0,0 +1,104 @@
"""GRUB boot configuration for multiple architectures."""
import pathlib
import shutil
from abc import abstractmethod
from .base import BaseBootConfigurator
def copy_grub_common_files(grub_pkg_dir: pathlib.Path, iso_root: pathlib.Path) -> None:
fonts_dir = iso_root.joinpath("boot", "grub", "fonts")
fonts_dir.mkdir(parents=True, exist_ok=True)
src = grub_pkg_dir.joinpath("usr", "share", "grub", "unicode.pf2")
dst = fonts_dir.joinpath("unicode.pf2")
shutil.copy(src, dst)
def copy_grub_modules(
grub_pkg_dir: pathlib.Path,
iso_root: pathlib.Path,
grub_target: str,
patterns: list[str],
) -> None:
"""Copy GRUB module files matching given patterns from src to dest."""
src_dir = grub_pkg_dir.joinpath("usr", "lib", "grub", grub_target)
dest_dir = iso_root.joinpath("boot", "grub", grub_target)
dest_dir.mkdir(parents=True, exist_ok=True)
for pat in patterns:
for file in src_dir.glob(pat):
shutil.copy(file, dest_dir)
class GrubBootConfigurator(BaseBootConfigurator):
"""Base class for architectures that use GRUB (all except S390X).
Common GRUB functionality shared across AMD64, ARM64, PPC64EL, and RISC-V64.
Subclasses must implement generate_grub_config().
"""
def grub_header(self, include_loadfont: bool = True) -> str:
"""Return common GRUB config header (timeout, colors).
Args:
include_loadfont: Whether to include 'loadfont unicode'
(not needed for RISC-V)
"""
result = "set timeout=30\n\n"
if include_loadfont:
result += "loadfont unicode\n\n"
result += """\
set menu_color_normal=white/black
set menu_color_highlight=black/light-gray
"""
return result
def hwe_menu_entry(
self,
kernel_name: str,
kernel_params: str,
extra_params: str = "",
) -> str:
"""Return HWE kernel menu entry if HWE is enabled.
Args:
kernel_name: Kernel binary name (vmlinuz or vmlinux)
kernel_params: Kernel parameters to append
extra_params: Additional parameters (e.g., console=tty0, $cmdline)
"""
if not self.hwe:
return ""
return f"""\
menuentry "{self.humanproject} with the HWE kernel" {{
set gfxpayload=keep
linux /casper/hwe-{kernel_name} {extra_params}{kernel_params}
initrd /casper/hwe-initrd
}}
"""
@abstractmethod
def generate_grub_config(self) -> str:
"""Generate grub.cfg content.
Each GRUB-based architecture must implement this to return the
GRUB configuration.
"""
...
def make_bootable(
self,
project: str,
capproject: str,
subarch: str,
hwe: bool,
) -> None:
"""Make the ISO bootable by extracting files and generating GRUB config."""
super().make_bootable(project, capproject, subarch, hwe)
with self.logger.logged("generating grub config"):
content = self.generate_grub_config()
grub_dir = self.iso_root.joinpath("boot", "grub")
grub_dir.mkdir(parents=True, exist_ok=True)
grub_dir.joinpath("grub.cfg").write_text(content)

View File

@ -0,0 +1,74 @@
"""PowerPC 64-bit Little Endian architecture boot configuration."""
import pathlib
import shutil
from .grub import (
copy_grub_common_files,
copy_grub_modules,
GrubBootConfigurator,
)
from .base import default_kernel_params
class PPC64ELBootConfigurator(GrubBootConfigurator):
"""Boot setup for PowerPC 64-bit Little Endian architecture."""
def mkisofs_opts(self) -> list[str | pathlib.Path]:
"""Return mkisofs options for PPC64EL."""
return []
def extract_files(self) -> None:
"""Download and extract bootloader packages for PPC64EL."""
self.logger.log("extracting PPC64EL boot files")
grub_pkg_dir = self.scratch.joinpath("grub-pkg")
# Download and extract bootloader packages
self.download_and_extract_package("grub2-common", grub_pkg_dir)
self.download_and_extract_package("grub-ieee1275-bin", grub_pkg_dir)
# Add common files for GRUB to tree
copy_grub_common_files(grub_pkg_dir, self.iso_root)
# Add IEEE1275 ppc boot files
ppc_dir = self.iso_root.joinpath("ppc")
ppc_dir.mkdir()
src_grub_dir = grub_pkg_dir.joinpath("usr", "lib", "grub", "powerpc-ieee1275")
# Copy bootinfo.txt to ppc directory
shutil.copy(
src_grub_dir.joinpath("bootinfo.txt"), ppc_dir.joinpath("bootinfo.txt")
)
# Copy eltorito.elf to boot/grub as powerpc.elf
shutil.copy(
src_grub_dir.joinpath("eltorito.elf"),
self.iso_root.joinpath("boot", "grub", "powerpc.elf"),
)
# Copy GRUB modules
copy_grub_modules(
grub_pkg_dir, self.iso_root, "powerpc-ieee1275", ["*.mod", "*.lst"]
)
def generate_grub_config(self) -> str:
"""Generate grub.cfg for PPC64EL."""
kernel_params = default_kernel_params(self.project)
result = self.grub_header()
# Main menu entry
result += f"""\
menuentry "Try or Install {self.humanproject}" {{
set gfxpayload=keep
linux /casper/vmlinux quiet {kernel_params}
initrd /casper/initrd
}}
"""
# HWE kernel option if available
result += self.hwe_menu_entry("vmlinux", kernel_params, extra_params="quiet ")
return result

View File

@ -0,0 +1,207 @@
"""RISC-V 64-bit architecture boot configuration."""
import pathlib
import shutil
from .grub import GrubBootConfigurator, copy_grub_common_files, copy_grub_modules
def copy_unsigned_monolithic_grub(
grub_pkg_dir: pathlib.Path,
efi_suffix: str,
grub_target: str,
iso_root: pathlib.Path,
) -> None:
efi_boot_dir = iso_root.joinpath("EFI", "boot")
efi_boot_dir.mkdir(parents=True, exist_ok=True)
shutil.copy(
grub_pkg_dir.joinpath(
"usr",
"lib",
"grub",
grub_target,
"monolithic",
f"gcd{efi_suffix}.efi",
),
efi_boot_dir.joinpath(f"boot{efi_suffix}.efi"),
)
copy_grub_modules(grub_pkg_dir, iso_root, grub_target, ["*.mod", "*.lst"])
class RISCV64BootConfigurator(GrubBootConfigurator):
"""Boot setup for RISC-V 64-bit architecture."""
def mkisofs_opts(self) -> list[str | pathlib.Path]:
"""Return mkisofs options for RISC-V64."""
efi_img = self.scratch.joinpath("efi.img")
return [
"-joliet",
"on",
"-compliance",
"joliet_long_names",
"--append_partition",
"2",
"0xef",
efi_img,
"-boot_image",
"any",
"partition_offset=10240",
"-boot_image",
"any",
"partition_cyl_align=all",
"-boot_image",
"any",
"efi_path=--interval:appended_partition_2:all::",
"-boot_image",
"any",
"appended_part_as=gpt",
"-boot_image",
"any",
"cat_path=/boot/boot.cat",
"-fs",
"64m",
]
def extract_files(self) -> None:
"""Download and extract bootloader packages for RISC-V64."""
self.logger.log("extracting RISC-V64 boot files")
u_boot_dir = self.scratch.joinpath("u-boot-sifive")
grub_pkg_dir = self.scratch.joinpath("grub-pkg")
# Download and extract bootloader packages
self.download_and_extract_package("grub2-common", grub_pkg_dir)
self.download_and_extract_package("grub-efi-riscv64-bin", grub_pkg_dir)
self.download_and_extract_package("grub-efi-riscv64-unsigned", grub_pkg_dir)
self.download_and_extract_package("u-boot-sifive", u_boot_dir)
# Add GRUB to tree
copy_grub_common_files(grub_pkg_dir, self.iso_root)
copy_unsigned_monolithic_grub(
grub_pkg_dir, "riscv64", "riscv64-efi", self.iso_root
)
# Extract DTBs to tree
self.logger.log("extracting device tree files")
kernel_layer = self.scratch.joinpath("kernel-layer")
squashfs_path = self.iso_root.joinpath(
"casper", "ubuntu-server-minimal.squashfs"
)
# Extract device tree firmware from squashfs
self.logger.run(
[
"unsquashfs",
"-no-xattrs",
"-d",
kernel_layer,
squashfs_path,
"usr/lib/firmware",
],
check=True,
)
# Copy DTBs if they exist
dtb_dir = self.iso_root.joinpath("dtb")
dtb_dir.mkdir(parents=True, exist_ok=True)
firmware_dir = kernel_layer.joinpath("usr", "lib", "firmware")
for dtb_file in firmware_dir.glob("*/device-tree/*"):
if dtb_file.is_file():
shutil.copy(dtb_file, dtb_dir)
# Create ESP image with GRUB and dtbs
efi_img = self.scratch.joinpath("efi.img")
self.logger.run(
["mkfs.msdos", "-n", "ESP", "-C", "-v", efi_img, "32768"], check=True
)
# Add EFI files to ESP
efi_dir = self.iso_root.joinpath("EFI")
self.logger.run(["mcopy", "-s", "-i", efi_img, efi_dir, "::/."], check=True)
# Add DTBs to ESP
self.logger.run(["mcopy", "-s", "-i", efi_img, dtb_dir, "::/."], check=True)
def generate_grub_config(self) -> str:
"""Generate grub.cfg for RISC-V64."""
result = self.grub_header(include_loadfont=False)
# Main menu entry
result += f"""\
menuentry "Try or Install {self.humanproject}" {{
set gfxpayload=keep
linux /casper/vmlinux efi=debug sysctl.kernel.watchdog_thresh=60 ---
initrd /casper/initrd
}}
"""
# HWE kernel option if available
result += self.hwe_menu_entry(
"vmlinux",
"---",
extra_params="efi=debug sysctl.kernel.watchdog_thresh=60 ",
)
return result
def post_process_iso(self, iso_path: pathlib.Path) -> None:
"""Add GPT partitions with U-Boot for SiFive Unmatched board.
The SiFive Unmatched board needs a GPT table containing U-Boot in
order to boot. U-Boot does not currently support booting from a CD,
so the GPT table also contains an entry pointing to the ESP so that
U-Boot can find it.
"""
u_boot_dir = self.scratch.joinpath(
"u-boot-sifive", "usr", "lib", "u-boot", "sifive_unmatched"
)
self.logger.run(
[
"sgdisk",
iso_path,
"--set-alignment=2",
"-d",
"1",
"-n",
"1:2082:10273",
"-c",
"1:loader2",
"-t",
"1:2E54B353-1271-4842-806F-E436D6AF6985",
"-n",
"3:10274:12321",
"-c",
"3:loader1",
"-t",
"3:5B193300-FC78-40CD-8002-E86C45580B47",
"-c",
"2:ESP",
"-r=2:3",
],
)
self.logger.run(
[
"dd",
f"if={u_boot_dir / 'u-boot.itb'}",
f"of={iso_path}",
"bs=512",
"seek=2082",
"conv=notrunc",
],
)
self.logger.run(
[
"dd",
f"if={u_boot_dir / 'u-boot-spl.bin'}",
f"of={iso_path}",
"bs=512",
"seek=10274",
"conv=notrunc",
],
)

View File

@ -0,0 +1,206 @@
"""IBM S/390 architecture boot configuration."""
import pathlib
import shutil
import struct
from .base import BaseBootConfigurator
README_dot_boot = """\
About the S/390 installation CD
===============================
It is possible to "boot" the installation system off this CD using
the files provided in the /boot directory.
Although you can boot the installer from this CD, the installation
itself is *not* actually done from the CD. Once the initrd is loaded,
the installer will ask you to configure your network connection and
uses the network-console component to allow you to continue the
installation over SSH. The rest of the installation is done over the
network: all installer components and Debian packages are retrieved
from a mirror.
Instead of SSH, one can also use the ASCII terminal available in HMC.
Exporting full .iso contents (including the hidden .disk directory)
allows one to use the result as a valid mirror for installation.
"""
ubuntu_dot_exec = """\
/* REXX EXEC TO IPL Ubuntu for */
/* z Systems FROM THE VM READER. */
/* */
'CP CLOSE RDR'
'PURGE RDR ALL'
'SPOOL PUNCH * RDR'
'PUNCH KERNEL UBUNTU * (NOHEADER'
'PUNCH PARMFILE UBUNTU * (NOHEADER'
'PUNCH INITRD UBUNTU * (NOHEADER'
'CHANGE RDR ALL KEEP NOHOLD'
'CP IPL 000C CLEAR'
"""
ubuntu_dot_ins = """\
* Ubuntu for IBM Z (default kernel)
kernel.ubuntu 0x00000000
initrd.off 0x0001040c
initrd.siz 0x00010414
parmfile.ubuntu 0x00010480
initrd.ubuntu 0x01000000
"""
def gen_s390_cd_kernel(
kernel: pathlib.Path, initrd: pathlib.Path, cmdline: str, outfile: pathlib.Path
) -> None:
"""Generate a bootable S390X CD kernel image.
This is a Python translation of gen-s390-cd-kernel.pl from debian-cd.
It creates a bootable image for S/390 architecture by combining kernel,
initrd, and boot parameters in a specific format.
"""
# Calculate sizes
initrd_size = initrd.stat().st_size
# The initrd is placed at a fixed offset of 16 MiB
initrd_offset = 0x1000000
# Calculate total boot image size (rounded up to 4K blocks)
boot_size = ((initrd_offset + initrd_size) >> 12) + 1
boot_size = boot_size << 12
# Validate cmdline length (max 896 bytes)
if len(cmdline) >= 896:
raise ValueError(f"Kernel commandline too long ({len(cmdline)} bytes)")
# Create output file and fill with zeros
with outfile.open("wb") as out_fh:
# Fill entire file with zeros
out_fh.write(b"\x00" * boot_size)
# Copy kernel to offset 0
out_fh.seek(0)
with kernel.open("rb") as kernel_fh:
out_fh.write(kernel_fh.read())
# Copy initrd to offset 0x1000000 (16 MiB)
out_fh.seek(initrd_offset)
with initrd.open("rb") as initrd_fh:
out_fh.write(initrd_fh.read())
# Write boot loader control value at offset 4
# This tells the S/390 boot loader where to find the kernel
out_fh.seek(4)
out_fh.write(struct.pack("!I", 0x80010000))
# Write kernel command line at offset 0x10480
out_fh.seek(0x10480)
out_fh.write(cmdline.encode("utf-8"))
# Write initrd parameters
# Initrd offset at 0x1040C
out_fh.seek(0x1040C)
out_fh.write(struct.pack("!I", initrd_offset))
# Initrd size at 0x10414
out_fh.seek(0x10414)
out_fh.write(struct.pack("!I", initrd_size))
class S390XBootConfigurator(BaseBootConfigurator):
"""Boot setup for IBM S/390 architecture."""
def mkisofs_opts(self) -> list[str | pathlib.Path]:
"""Return mkisofs options for S390X."""
return [
"-J",
"-no-emul-boot",
"-b",
"boot/ubuntu.ikr",
]
def extract_files(self) -> None:
"""Set up boot files for S390X."""
self.logger.log("extracting S390X boot files")
boot_dir = self.iso_root.joinpath("boot")
boot_dir.mkdir(parents=True, exist_ok=True)
# Copy static .ins & exec scripts, docs from data directory
self.iso_root.joinpath("README.boot").write_text(README_dot_boot)
boot_dir.joinpath("ubuntu.exec").write_text(ubuntu_dot_exec)
boot_dir.joinpath("ubuntu.ins").write_text(ubuntu_dot_ins)
# Move kernel image to the name used in .ins & exec scripts
kernel_src = self.iso_root.joinpath("casper", "vmlinuz")
kernel_dst = boot_dir.joinpath("kernel.ubuntu")
kernel_src.replace(kernel_dst)
# Move initrd to the name used in .ins & exec scripts
initrd_src = self.iso_root.joinpath("casper", "initrd")
initrd_dst = boot_dir.joinpath("initrd.ubuntu")
initrd_src.replace(initrd_dst)
# Compute initrd offset & size, store in files used by .ins & exec scripts
# Offset is always 0x1000000 (16 MiB)
initrd_offset_file = boot_dir.joinpath("initrd.off")
with initrd_offset_file.open("wb") as f:
f.write(struct.pack("!I", 0x1000000))
# Size is the actual size of the initrd
initrd_size = initrd_dst.stat().st_size
initrd_size_file = boot_dir.joinpath("initrd.siz")
with initrd_size_file.open("wb") as f:
f.write(struct.pack("!I", initrd_size))
# Compute cmdline, store in parmfile used by .ins & exec scripts
parmfile = boot_dir.joinpath("parmfile.ubuntu")
with parmfile.open("w") as f:
f.write(" --- ")
# Generate secondary top-level ubuntu.ins file
# This transforms lines not starting with * by prepending "boot/"
ubuntu_ins_src = boot_dir.joinpath("ubuntu.ins")
ubuntu_ins_dst = self.iso_root.joinpath("ubuntu.ins")
if ubuntu_ins_src.exists():
self.logger.run(
["sed", "-e", "s,^[^*],boot/&,g", ubuntu_ins_src],
stdout=ubuntu_ins_dst.open("w"),
check=True,
)
# Generate QEMU-KVM boot image using gen_s390_cd_kernel
cmdline = parmfile.read_text().strip()
ikr_file = boot_dir.joinpath("ubuntu.ikr")
gen_s390_cd_kernel(kernel_dst, initrd_dst, cmdline, ikr_file)
# Extract bootloader signing certificate
installed_pem = pathlib.Path("/usr/lib/s390-tools/stage3.pem")
squashfs_root = self.iso_root.joinpath("squashfs-root")
squashfs_path = self.iso_root.joinpath(
"casper", "ubuntu-server-minimal.squashfs"
)
if squashfs_path.exists():
self.logger.run(
[
"unsquashfs",
"-no-xattrs",
"-i",
"-d",
squashfs_root,
squashfs_path,
installed_pem,
],
check=True,
)
# Move certificate to iso root
cert_src = squashfs_root.joinpath(str(installed_pem).lstrip("/"))
cert_dst = self.iso_root.joinpath("ubuntu.pem")
if cert_src.exists():
cert_src.replace(cert_dst)
# Clean up squashfs extraction
shutil.rmtree(squashfs_root)

View File

@ -0,0 +1,168 @@
"""UEFI boot configuration for AMD64 and ARM64 architectures."""
import pathlib
import shutil
from ..builder import Logger
from .grub import copy_grub_common_files, GrubBootConfigurator
def copy_signed_shim_grub(
shim_pkg_dir: pathlib.Path,
grub_pkg_dir: pathlib.Path,
efi_suffix: str,
grub_target: str,
iso_root: pathlib.Path,
) -> None:
efi_boot_dir = iso_root.joinpath("EFI", "boot")
efi_boot_dir.mkdir(parents=True, exist_ok=True)
shutil.copy(
shim_pkg_dir.joinpath(
"usr", "lib", "shim", f"shim{efi_suffix}.efi.signed.latest"
),
efi_boot_dir.joinpath(f"boot{efi_suffix}.efi"),
)
shutil.copy(
shim_pkg_dir.joinpath("usr", "lib", "shim", f"mm{efi_suffix}.efi"),
efi_boot_dir.joinpath(f"mm{efi_suffix}.efi"),
)
shutil.copy(
grub_pkg_dir.joinpath(
"usr",
"lib",
"grub",
f"{grub_target}-efi-signed",
f"gcd{efi_suffix}.efi.signed",
),
efi_boot_dir.joinpath(f"grub{efi_suffix}.efi"),
)
grub_boot_dir = iso_root.joinpath("boot", "grub", f"{grub_target}-efi")
grub_boot_dir.mkdir(parents=True, exist_ok=True)
src_grub_dir = grub_pkg_dir.joinpath("usr", "lib", "grub", f"{grub_target}-efi")
for mod_file in src_grub_dir.glob("*.mod"):
shutil.copy(mod_file, grub_boot_dir)
for lst_file in src_grub_dir.glob("*.lst"):
shutil.copy(lst_file, grub_boot_dir)
def create_eltorito_esp_image(
logger: Logger, iso_root: pathlib.Path, target_file: pathlib.Path
) -> None:
logger.log("creating El Torito ESP image")
efi_dir = iso_root.joinpath("EFI")
# Calculate size: du -s --apparent-size --block-size=1024 + 1024
result = logger.run(
["du", "-s", "--apparent-size", "--block-size=1024", efi_dir],
capture_output=True,
text=True,
check=True,
)
size_kb = int(result.stdout.split()[0]) + 1024
# Create filesystem: mkfs.msdos -n ESP -C -v
logger.run(
["mkfs.msdos", "-n", "ESP", "-C", "-v", target_file, str(size_kb)],
check=True,
)
# Copy files: mcopy -s -i target_file EFI ::/.
logger.run(["mcopy", "-s", "-i", target_file, efi_dir, "::/."], check=True)
class UEFIBootConfigurator(GrubBootConfigurator):
"""Base class for UEFI-based architectures (AMD64, ARM64).
Subclasses should set:
- efi_suffix: EFI binary suffix (e.g., "x64", "aa64")
- grub_target: GRUB target name (e.g., "x86_64", "arm64")
"""
# Subclasses must override these
efi_suffix: str = ""
grub_target: str = ""
arch: str = ""
def get_uefi_grub_packages(self) -> list[str]:
"""Return list of UEFI GRUB packages to download."""
return [
"grub2-common",
f"grub-efi-{self.arch}-bin",
f"grub-efi-{self.arch}-signed",
]
def extract_uefi_files(self) -> None:
"""Extract common UEFI files to boot tree."""
shim_pkg_dir = self.scratch.joinpath("shim-pkg")
grub_pkg_dir = self.scratch.joinpath("grub-pkg")
# Download UEFI packages
self.download_and_extract_package("shim-signed", shim_pkg_dir)
for pkg in self.get_uefi_grub_packages():
self.download_and_extract_package(pkg, grub_pkg_dir)
# Add common files for GRUB to tree
copy_grub_common_files(grub_pkg_dir, self.iso_root)
# Add EFI GRUB to tree
copy_signed_shim_grub(
shim_pkg_dir,
grub_pkg_dir,
self.efi_suffix,
self.grub_target,
self.iso_root,
)
# Create ESP image for El-Torito catalog and hybrid boot
create_eltorito_esp_image(
self.logger, self.iso_root, self.scratch.joinpath("cd-boot-efi.img")
)
def uefi_menu_entries(self) -> str:
"""Return UEFI firmware menu entries."""
return """\
menuentry 'Boot from next volume' {
exit 1
}
menuentry 'UEFI Firmware Settings' {
fwsetup
}
"""
def get_uefi_mkisofs_opts(self) -> list[str | pathlib.Path]:
"""Return common UEFI mkisofs options."""
# To make our ESP / El-Torito image compliant with MBR/GPT standards,
# we first append it as a partition and then point the El Torito at
# it. See https://lists.debian.org/debian-cd/2019/07/msg00007.html
opts: list[str | pathlib.Path] = [
"-append_partition",
"2",
"0xef",
self.scratch.joinpath("cd-boot-efi.img"),
"-appended_part_as_gpt",
]
# Some BIOSes ignore removable disks with no partitions marked bootable
# in the MBR. Make sure our protective MBR partition is marked bootable.
opts.append("--mbr-force-bootable")
# Start a new entry in the el torito boot catalog
opts.append("-eltorito-alt-boot")
# Specify where the el torito UEFI boot image "name". We use a special
# syntax available in latest xorriso to point at our newly-created
# partition.
opts.extend(["-e", "--interval:appended_partition_2:all::"])
# Whether to emulate a floppy or not is a per-boot-catalog-entry
# thing, so we need to say it again.
opts.append("-no-emul-boot")
# Create a partition table entry that covers the iso9660 filesystem
opts.extend(["-partition_offset", "16"])
return opts

View File

@ -0,0 +1,368 @@
import contextlib
import json
import pathlib
import shlex
import shutil
import subprocess
import sys
from isobuilder.apt_state import AptStateManager
from isobuilder.boot import make_boot_configurator_for_arch
from isobuilder.gpg_key import EphemeralGPGKey
from isobuilder.pool_builder import PoolBuilder
# Constants
PACKAGE_BATCH_SIZE = 200
MAX_CMD_DISPLAY_LENGTH = 80
def package_list_packages(package_list_file: pathlib.Path) -> list[str]:
# Parse germinate output to extract package names. Germinate is Ubuntu's
# package dependency resolver that outputs dependency trees for seeds (like
# "ship-live" or "server-ship-live").
#
# Germinate output format has 2 header lines at the start and 2 footer lines
# at the end (showing statistics), so we skip them with [2:-2].
# Each data line starts with the package name followed by whitespace and
# dependency info. This format is stable but if germinate ever changes its
# header/footer count, this will break silently.
lines = package_list_file.read_text().splitlines()[2:-2]
return [line.split(None, 1)[0] for line in lines]
def make_sources_text(
series: str, gpg_key: EphemeralGPGKey, components: list[str], mountpoint: str
) -> str:
"""Generate a deb822-format apt source file for the ISO's package pool.
deb822 is the modern apt sources format (see sources.list(5) and deb822(5)).
It uses RFC822-style fields where multi-line values must be indented with a
leading space, and empty lines within a value are represented as " ."
(space-dot). This format is required for inline GPG keys in the Signed-By
field.
"""
key = gpg_key.export_public()
quoted_key = []
for line in key.splitlines():
if not line:
quoted_key.append(" .")
else:
quoted_key.append(" " + line)
return f"""\
Types: deb
URIs: file://{mountpoint}
Suites: {series}
Components: {" ".join(components)}
Check-Date: no
Signed-By:
""" + "\n".join(
quoted_key
)
class Logger:
def __init__(self):
self._indent = ""
def log(self, msg):
print(self._indent + msg, file=sys.stderr)
@contextlib.contextmanager
def logged(self, msg, done_msg=None):
self.log(msg)
self._indent += " "
try:
yield
finally:
self._indent = self._indent[:-2]
if done_msg is not None:
self.log(done_msg)
def msg_for_cmd(self, cmd, limit_length=True, cwd=None) -> str:
if cwd is None:
_cwd = pathlib.Path().cwd()
else:
_cwd = cwd
fmted_cmd = []
for arg in cmd:
if isinstance(arg, pathlib.Path):
if arg.is_relative_to(_cwd):
arg = arg.relative_to(_cwd)
arg = str(arg)
fmted_cmd.append(shlex.quote(arg))
fmted_cmd_str = " ".join(fmted_cmd)
if len(fmted_cmd_str) > MAX_CMD_DISPLAY_LENGTH and limit_length:
fmted_cmd_str = fmted_cmd_str[:MAX_CMD_DISPLAY_LENGTH] + "..."
msg = f"running `{fmted_cmd_str}`"
if cwd is not None:
msg += f" in {cwd}"
return msg
def run(
self, cmd: list[str | pathlib.Path], *args, limit_length=True, check=True, **kw
):
with self.logged(
self.msg_for_cmd(cmd, cwd=kw.get("cwd"), limit_length=limit_length)
):
return subprocess.run(cmd, *args, check=check, **kw)
class ISOBuilder:
def __init__(self, workdir: pathlib.Path):
self.workdir = workdir
self.logger = Logger()
self.iso_root = workdir.joinpath("iso-root")
self._config: dict | None = None
self._gpg_key = self._apt_state = None
# UTILITY STUFF
def _read_config(self):
with self.workdir.joinpath("config.json").open() as fp:
self._config = json.load(fp)
@property
def config(self):
if self._config is None:
self._read_config()
return self._config
def save_config(self):
with self.workdir.joinpath("config.json").open("w") as fp:
json.dump(self._config, fp)
@property
def arch(self):
return self.config["arch"]
@property
def series(self):
if self._config is None:
self._read_config()
return self._config["series"]
@property
def gpg_key(self):
if self._gpg_key is None:
self._gpg_key = EphemeralGPGKey(
self.logger, self.workdir.joinpath("gpg-home")
)
return self._gpg_key
@property
def apt_state(self):
if self._apt_state is None:
self._apt_state = AptStateManager(
self.logger, self.series, self.workdir.joinpath("apt-state")
)
return self._apt_state
# COMMANDS
def init(self, disk_info: str, series: str, arch: str):
self.logger.log("creating directories")
self.workdir.mkdir(exist_ok=True)
self.iso_root.mkdir()
dot_disk = self.iso_root.joinpath(".disk")
dot_disk.mkdir()
self.logger.log("saving config")
self._config = {"arch": arch, "series": series}
self.save_config()
self.logger.log("populating .disk")
dot_disk.joinpath("base_installable").touch()
dot_disk.joinpath("cd_type").write_text("full_cd/single\n")
dot_disk.joinpath("info").write_text(disk_info)
self.iso_root.joinpath("casper").mkdir()
self.gpg_key.create()
def setup_apt(self, chroot: pathlib.Path):
self.apt_state.setup(chroot)
def generate_pool(self, package_list_file: pathlib.Path):
# do we need any of the symlinks we create here??
self.logger.log("creating pool skeleton")
self.iso_root.joinpath("ubuntu").symlink_to(".")
if self.arch not in ("amd64", "i386"):
self.iso_root.joinpath("ubuntu-ports").symlink_to(".")
self.iso_root.joinpath("dists", self.series).mkdir(parents=True)
builder = PoolBuilder(
self.logger,
series=self.series,
rootdir=self.iso_root,
apt_state=self.apt_state,
)
pkgs = package_list_packages(package_list_file)
# XXX include 32-bit deps of 32-bit packages if needed here
with self.logger.logged("adding packages"):
for i in range(0, len(pkgs), PACKAGE_BATCH_SIZE):
builder.add_packages(
self.apt_state.show(pkgs[i : i + PACKAGE_BATCH_SIZE])
)
builder.make_packages()
release_file = builder.make_release()
self.gpg_key.sign(release_file)
for name in "stable", "unstable":
self.iso_root.joinpath("dists", name).symlink_to(self.series)
def generate_sources(self, mountpoint: str):
components = [p.name for p in self.iso_root.joinpath("pool").iterdir()]
print(
make_sources_text(
self.series, self.gpg_key, mountpoint=mountpoint, components=components
)
)
def _extract_casper_uuids(self):
# Extract UUID files from initrd images for casper (the live boot system).
# Each initrd contains a conf/uuid.conf with a unique identifier that
# casper uses at boot time to locate the correct root filesystem. These
# UUIDs must be placed in .disk/casper-uuid-<flavor> on the ISO so casper
# can verify it's booting from the right media.
with self.logger.logged("extracting casper uuids"):
casper_dir = self.iso_root.joinpath("casper")
dot_disk = self.iso_root.joinpath(".disk")
for initrd in casper_dir.glob("*initrd"):
initrddir = self.workdir.joinpath("initrd")
with self.logger.logged(
f"unpacking {initrd.name} ...", done_msg="... done"
):
self.logger.run(["unmkinitramfs", initrd, initrddir])
# unmkinitramfs can produce different directory structures:
# - Platforms with early firmware: subdirs like "main/" or "early/"
# containing conf/uuid.conf
# - Other platforms: conf/uuid.conf directly in the root
# Try to find uuid.conf in both locations.
confs = list(initrddir.glob("*/conf/uuid.conf"))
if confs:
[uuid_conf] = confs
elif initrddir.joinpath("conf/uuid.conf").exists():
uuid_conf = initrddir.joinpath("conf/uuid.conf")
else:
raise Exception("uuid.conf not found")
self.logger.log(f"found {uuid_conf.relative_to(initrddir)}")
if initrd.name == "initrd":
suffix = "generic"
elif initrd.name == "hwe-initrd":
suffix = "generic-hwe"
else:
raise Exception(f"unexpected initrd name {initrd.name}")
uuid_conf.rename(dot_disk.joinpath(f"casper-uuid-{suffix}"))
shutil.rmtree(initrddir)
def add_live_filesystem(self, artifact_prefix: pathlib.Path):
casper_dir = self.iso_root.joinpath("casper")
artifact_dir = artifact_prefix.parent
filename_prefix = artifact_prefix.name
def link(src: pathlib.Path, target_name: str):
target = casper_dir.joinpath(target_name)
self.logger.log(
f"creating link from $ISOROOT/casper/{target_name} to $src/{src.name}"
)
target.hardlink_to(src)
kernel_name = "vmlinuz"
if self.arch in ("ppc64el", "riscv64"):
kernel_name = "vmlinux"
with self.logger.logged(
f"linking artifacts from {casper_dir} to {artifact_dir}"
):
for ext in "squashfs", "squashfs.gpg", "size", "manifest", "yaml":
for path in artifact_dir.glob(f"{filename_prefix}*.{ext}"):
newname = path.name[len(filename_prefix) :]
link(path, newname)
for kernel_path in artifact_dir.glob(f"{filename_prefix}kernel*"):
suffix = kernel_path.name[len(filename_prefix) + len("kernel") :]
prefix = "hwe-" if suffix.endswith("-hwe") else ""
link(
artifact_dir.joinpath(f"{filename_prefix}kernel{suffix}"),
f"{prefix}{kernel_name}",
)
link(
artifact_dir.joinpath(f"{filename_prefix}initrd{suffix}"),
f"{prefix}initrd",
)
self._extract_casper_uuids()
def make_bootable(self, project: str, capproject: str, subarch: str):
configurator = make_boot_configurator_for_arch(
self.arch,
self.logger,
self.apt_state,
self.workdir,
self.iso_root,
)
configurator.make_bootable(
project,
capproject,
subarch,
self.iso_root.joinpath("casper/hwe-initrd").exists(),
)
def checksum(self):
# Generate md5sum.txt for ISO integrity verification.
# - Symlinks are excluded because their targets are already checksummed
# - Files are sorted for deterministic, reproducible output across builds
# - Paths use "./" prefix and we run md5sum from iso_root so the output
# matches what users get when they verify with "md5sum -c" from the ISO
all_files = []
for dirpath, dirnames, filenames in self.iso_root.walk():
filepaths = [dirpath.joinpath(filename) for filename in filenames]
all_files.extend(
"./" + str(filepath.relative_to(self.iso_root))
for filepath in filepaths
if not filepath.is_symlink()
)
self.iso_root.joinpath("md5sum.txt").write_bytes(
self.logger.run(
["md5sum"] + sorted(all_files),
cwd=self.iso_root,
stdout=subprocess.PIPE,
).stdout
)
def make_iso(self, dest: pathlib.Path, volid: str | None):
# xorriso with "-as mkisofs" runs in mkisofs compatibility mode.
# -r enables Rock Ridge extensions for Unix metadata (permissions, symlinks).
# -iso-level 3 (amd64 only) allows files >4GB which some amd64 ISOs need.
# mkisofs_opts comes from the boot configurator and contains architecture-
# specific options for boot sectors, EFI images, etc.
self.checksum()
configurator = make_boot_configurator_for_arch(
self.arch,
self.logger,
self.apt_state,
self.workdir,
self.iso_root,
)
mkisofs_opts = configurator.mkisofs_opts()
cmd: list[str | pathlib.Path] = ["xorriso"]
if self.arch == "riscv64":
# For $reasons, xorriso is not run in mkisofs mode on riscv64 only.
cmd.extend(["-rockridge", "on", "-outdev", dest])
if volid:
cmd.extend(["-volid", volid])
cmd.extend(mkisofs_opts)
cmd.extend(["-map", self.iso_root, "/"])
else:
# xorriso with "-as mkisofs" runs in mkisofs compatibility mode on
# other architectures. -r enables Rock Ridge extensions for Unix
# metadata (permissions, symlinks). -iso-level 3 (amd64 only)
# allows files >4GB which some amd64 ISOs need.
cmd.extend(["-as", "mkisofs", "-r"])
if self.arch == "amd64":
cmd.extend(["-iso-level", "3"])
if volid:
cmd.extend(["-V", volid])
cmd.extend(mkisofs_opts + [self.iso_root, "-o", dest])
with self.logger.logged("running xorriso"):
self.logger.run(cmd, cwd=self.workdir, check=True, limit_length=False)
configurator.post_process_iso(dest)

View File

@ -0,0 +1,58 @@
import pathlib
import subprocess
key_conf = """\
%no-protection
Key-Type: eddsa
Key-Curve: Ed25519
Key-Usage: sign
Name-Real: Ubuntu ISO One-Time Signing Key
Name-Email: noone@nowhere.invalid
Expire-Date: 0
"""
class EphemeralGPGKey:
def __init__(self, logger, gpghome):
self.logger = logger
self.gpghome = gpghome
def _run_gpg(self, cmd, **kwargs):
return self.logger.run(
["gpg", "--homedir", self.gpghome] + cmd, check=True, **kwargs
)
def create(self):
with self.logger.logged("creating gpg key ...", done_msg="... done"):
self.gpghome.mkdir(mode=0o700)
self._run_gpg(
["--gen-key", "--batch"],
input=key_conf,
text=True,
)
def sign(self, path: pathlib.Path):
with self.logger.logged(f"signing {path}"):
with path.open("rb") as inp:
with pathlib.Path(str(path) + ".gpg").open("wb") as outp:
self._run_gpg(
[
"--no-options",
"--batch",
"--no-tty",
"--armour",
"--digest-algo",
"SHA512",
"--detach-sign",
],
stdin=inp,
stdout=outp,
)
def export_public(self) -> str:
return self._run_gpg(
["--export", "--armor"],
stdout=subprocess.PIPE,
text=True,
).stdout

View File

@ -0,0 +1,166 @@
import pathlib
import subprocess
import tempfile
from isobuilder.apt_state import AptStateManager, PackageInfo
generate_template = """
Dir::ArchiveDir "{root}";
Dir::CacheDir "{scratch}/apt-ftparchive-db";
TreeDefault::Contents " ";
Tree "dists/{series}" {{
FileList "{scratch}/filelist_$(SECTION)";
Sections "{components}";
Architectures "{arches}";
}}
"""
class PoolBuilder:
def __init__(
self, logger, series: str, apt_state: AptStateManager, rootdir: pathlib.Path
):
self.logger = logger
self.series = series
self.apt_state = apt_state
self.rootdir = rootdir
self.arches: set[str] = set()
self._present_components: set[str] = set()
def add_packages(self, pkglist: list[PackageInfo]):
for pkg_info in pkglist:
if pkg_info.architecture != "all":
self.arches.add(pkg_info.architecture)
self.apt_state.download(self.rootdir, pkg_info)
def make_packages(self) -> None:
with self.logger.logged("making Packages files"):
with tempfile.TemporaryDirectory() as tmpdir:
scratchdir = pathlib.Path(tmpdir)
with self.logger.logged("scanning for packages"):
for component in ["main", "restricted", "universe", "multiverse"]:
if not self.rootdir.joinpath("pool", component).is_dir():
continue
self._present_components.add(component)
for arch in self.arches:
self.rootdir.joinpath(
"dists", self.series, component, f"binary-{arch}"
).mkdir(parents=True)
proc = self.logger.run(
["find", f"pool/{component}"],
stdout=subprocess.PIPE,
cwd=self.rootdir,
encoding="utf-8",
check=True,
)
scratchdir.joinpath(f"filelist_{component}").write_text(
"\n".join(sorted(proc.stdout.splitlines()))
)
with self.logger.logged("writing apt-ftparchive config"):
scratchdir.joinpath("apt-ftparchive-db").mkdir()
generate_path = scratchdir.joinpath("generate-binary")
generate_path.write_text(
generate_template.format(
arches=" ".join(self.arches),
series=self.series,
root=self.rootdir.resolve(),
scratch=scratchdir.resolve(),
components=" ".join(self._present_components),
)
)
with self.logger.logged("running apt-ftparchive generate"):
self.logger.run(
[
"apt-ftparchive",
"--no-contents",
"--no-md5",
"--no-sha1",
"--no-sha512",
"generate",
generate_path,
],
check=True,
)
def make_release(self) -> pathlib.Path:
# Build the Release file by merging metadata from the mirror with
# checksums for our pool. We can't just use apt-ftparchive's Release
# output directly because:
# 1. apt-ftparchive doesn't know about Origin, Label, Suite, Version,
# Codename, etc. - these come from the mirror and maintain package
# provenance
# 2. We keep the mirror's Date (when packages were released) rather than
# apt-ftparchive's Date (when we ran the command)
# 3. We need to override Architectures/Components to match our pool
#
# There may be a cleaner way (apt-get indextargets?) but this works.
with self.logger.logged("making Release file"):
in_release = self.apt_state.in_release_path()
cp_mirror_release = self.logger.run(
["gpg", "--verify", "--output", "-", in_release],
stdout=subprocess.PIPE,
encoding="utf-8",
check=False,
)
if cp_mirror_release.returncode not in (0, 2):
# gpg returns code 2 when the public key the InRelease is
# signed with is not available, which is most of the time.
raise Exception("gpg failed")
mirror_release_lines = cp_mirror_release.stdout.splitlines()
release_dir = self.rootdir.joinpath("dists", self.series)
af_release_lines = self.logger.run(
[
"apt-ftparchive",
"--no-contents",
"--no-md5",
"--no-sha1",
"--no-sha512",
"release",
".",
],
stdout=subprocess.PIPE,
encoding="utf-8",
cwd=release_dir,
check=True,
).stdout.splitlines()
# Build the final Release file by merging mirror metadata with pool
# checksums.
# Strategy:
# 1. Take metadata fields (Suite, Origin, etc.) from the mirror's InRelease
# 2. Override Architectures and Components to match what's actually in our
# pool
# 3. Skip the mirror's checksum sections (MD5Sum, SHA256, etc.) because they
# don't apply to our pool
# 4. Skip Acquire-By-Hash since we don't use it
# 5. Append checksums from apt-ftparchive (but not the Date field)
release_lines = []
skipping = False
for line in mirror_release_lines:
if line.startswith("Architectures:"):
line = "Architectures: " + " ".join(sorted(self.arches))
elif line.startswith("Components:"):
line = "Components: " + " ".join(sorted(self._present_components))
elif line.startswith("MD5") or line.startswith("SHA"):
# Start of a checksum section - skip this and indented lines below
# it
skipping = True
elif not line.startswith(" "):
# Non-indented line means we've left the checksum section if we were
# in one.
skipping = False
if line.startswith("Acquire-By-Hash"):
continue
if not skipping:
release_lines.append(line)
# Append checksums from apt-ftparchive, but skip its Date field
# (we want to keep the Date from the mirror release)
for line in af_release_lines:
if not line.startswith("Date"):
release_lines.append(line)
release_path = release_dir.joinpath("Release")
release_path.write_text("\n".join(release_lines))
return release_path

View File

@ -180,10 +180,22 @@ build_layered_squashfs () {
# Operate on the upperdir directly, so that we are only # Operate on the upperdir directly, so that we are only
# modifying mtime on files that are actually changed in # modifying mtime on files that are actually changed in
# this layer. LP: #2107332 # this layer. LP: #2107332
/usr/share/livecd-rootfs/sync-mtime chroot "$overlay_dir" ${LIVECD_ROOTFS_ROOT}/sync-mtime chroot "$overlay_dir"
fi fi
create_squashfs "${overlay_dir}" ${squashfs_f} create_squashfs "${overlay_dir}" ${squashfs_f}
# Create a "for-iso" variant of the squashfs for ISO builds. For
# the root layer (the base system) when building with a pool, we
# need to include cdrom.sources so casper can access the ISO's
# package repository. This requires regenerating the squashfs with
# that file included, then removing it (so it doesn't pollute the
# regular squashfs). Non-root layers (desktop environment, etc.)
# and builds without pools can just hardlink to the regular squashfs.
if [ -n "${POOL_SEED_NAME}" ] && $(is_root_layer $pass); then
isobuild generate-sources --mountpoint=/cdrom > ${overlay_dir}/etc/apt/sources.list.d/cdrom.sources
create_squashfs "${overlay_dir}" ${PWD}/for-iso.${pass}.squashfs
rm ${overlay_dir}/etc/apt/sources.list.d/cdrom.sources
fi
if [ -f config/$pass.catalog-in.yaml ]; then if [ -f config/$pass.catalog-in.yaml ]; then
echo "Expanding catalog entry template for $pass" echo "Expanding catalog entry template for $pass"
@ -194,7 +206,7 @@ build_layered_squashfs () {
if [ -f config/seeded-languages ]; then if [ -f config/seeded-languages ]; then
usc_opts="$usc_opts --langs $(cat config/seeded-languages)" usc_opts="$usc_opts --langs $(cat config/seeded-languages)"
fi fi
/usr/share/livecd-rootfs/update-source-catalog source $usc_opts ${LIVECD_ROOTFS_ROOT}/update-source-catalog source $usc_opts
else else
echo "No catalog entry template for $pass" echo "No catalog entry template for $pass"
fi fi
@ -215,7 +227,7 @@ done
if [ -n "$DEFAULT_KERNEL" -a -f livecd.${PROJECT_FULL}.install-sources.yaml ]; then if [ -n "$DEFAULT_KERNEL" -a -f livecd.${PROJECT_FULL}.install-sources.yaml ]; then
write_kernel_yaml "$DEFAULT_KERNEL" "$BRIDGE_KERNEL_REASONS" write_kernel_yaml "$DEFAULT_KERNEL" "$BRIDGE_KERNEL_REASONS"
/usr/share/livecd-rootfs/update-source-catalog merge \ ${LIVECD_ROOTFS_ROOT}/update-source-catalog merge \
--output livecd.${PROJECT_FULL}.install-sources.yaml \ --output livecd.${PROJECT_FULL}.install-sources.yaml \
--template config/kernel.yaml --template config/kernel.yaml
fi fi
@ -227,3 +239,11 @@ if [ -n "$(ls livecd.${PROJECT_FULL}.*install.live.manifest.full 2>/dev/null)" ]
fi fi
chmod 644 *.squashfs *.manifest* *.size chmod 644 *.squashfs *.manifest* *.size
prefix=livecd.${PROJECT_FULL}
for artifact in ${prefix}.*; do
for_iso_path=for-iso${artifact#${prefix}}
if [ ! -f $for_iso_path ]; then
ln -v $artifact $for_iso_path
fi
done

View File

@ -237,7 +237,7 @@ create_chroot_pass () {
lb chroot_interactive ${*} lb chroot_interactive ${*}
# Misc ubuntu cleanup and post-layer configuration # Misc ubuntu cleanup and post-layer configuration
/usr/share/livecd-rootfs/minimize-manual chroot ${LIVECD_ROOTFS_ROOT}/minimize-manual chroot
clean_debian_chroot clean_debian_chroot
Chroot chroot "dpkg-query -W" > chroot.packages.${pass} Chroot chroot "dpkg-query -W" > chroot.packages.${pass}

View File

@ -0,0 +1,53 @@
#!/usr/bin/python3
"""
Usage: snap-seed-add-providers $SEED_DIR
Check the snaps referenced by the $SEED_DIR/seed.yaml and print any
missing providers.
"""
import contextlib
import pathlib
import subprocess
import sys
import tempfile
import yaml
@contextlib.contextmanager
def mounted(thing, target):
subprocess.run(["mount", "-r", thing, target], check=True)
try:
yield
finally:
subprocess.run(["umount", target], check=True)
def main(argv):
seed_dir = pathlib.Path(argv[0])
assert seed_dir.is_dir()
with seed_dir.joinpath("seed.yaml").open() as fp:
seed = yaml.safe_load(fp)
snap_files = set()
snap_names = set()
for snap_info in seed["snaps"]:
snap_files.add(snap_info["file"])
snap_names.add(snap_info["name"])
tempdir = pathlib.Path(tempfile.mkdtemp())
for snap_file in snap_files:
snap = seed_dir.joinpath("snaps", snap_file)
with mounted(snap, tempdir):
with tempdir.joinpath("meta/snap.yaml").open() as fp:
metadata = yaml.safe_load(fp)
for plug_name, plug_info in metadata.get("plugs", {}).items():
if plug_info.get("interface") == "content":
default_provider = plug_info.get("default-provider")
if default_provider and default_provider not in snap_names:
print(default_provider)
snap_names.add(default_provider)
if __name__ == "__main__":
main(sys.argv[1:])

View File

@ -11,6 +11,7 @@ case ${PASS:-} in
esac esac
. config/binary . config/binary
. config/common
. config/functions . config/functions
case ${SUBPROJECT} in case ${SUBPROJECT} in
@ -56,4 +57,4 @@ PROJECT_FULL=$PROJECT${SUBARCH:+-$SUBARCH}
usc_opts="--output livecd.${PROJECT_FULL}.install-sources.yaml \ usc_opts="--output livecd.${PROJECT_FULL}.install-sources.yaml \
--template config/edge.catalog-in.yaml \ --template config/edge.catalog-in.yaml \
--size 0" --size 0"
/usr/share/livecd-rootfs/update-source-catalog source $usc_opts ${LIVECD_ROOTFS_ROOT}/update-source-catalog source $usc_opts

View File

@ -24,7 +24,10 @@ create_partitions() {
sgdisk "${disk_image}" \ sgdisk "${disk_image}" \
--new=2::+8M \ --new=2::+8M \
--new=1: --new=1:
sgdisk "${disk_image}" -t 2:4100 sgdisk "${disk_image}" \
--change-name=1:"$FS_LABEL" \
-t 2:4100 \
-t 1:"$(gpt_root_partition_uuid $ARCH)"
sgdisk "${disk_image}" \ sgdisk "${disk_image}" \
--print --print
} }

View File

@ -30,7 +30,7 @@ if [ "$ARCH" = "riscv64" ]; then
fi fi
case ${PROJECT:-}:${SUBPROJECT:-} in case ${PROJECT:-}:${SUBPROJECT:-} in
ubuntu:) ubuntu:|ubuntu:dangerous)
echo "We don't create EFI images for Ubuntu Desktop." echo "We don't create EFI images for Ubuntu Desktop."
exit 0 exit 0
;; ;;
@ -70,105 +70,12 @@ create_partitions() {
fi fi
;; ;;
riscv64) riscv64)
# same as arm64/armhf, but set bit 2 legacy bios bootable
# on the first partition for uboot
# and have two loader partitions of uboot SPL & real one
# and have CIDATA partition for preinstalled image
if [ -z "${SUBARCH:-}" ]; then
# cloud-image
sgdisk "${disk_image}" \ sgdisk "${disk_image}" \
--set-alignment=2 \
--new=15::+106M \
--typecode=15:ef00 \
--new=1:: \
--attributes=1:set:2
elif [ "${SUBARCH:-}" = "nezha" ] || [ "${SUBARCH:-}" = "licheerv" ]; then
# Nezha/LicheeRV D1 boards
sgdisk "${disk_image}" \
--set-alignment=2 \
--new=13:256:25575 \
--change-name=13:loader1 \
--typecode=13:B161E8AB-7D4B-4DB4-821C-4120A0554A35 \
--attributes=13:set:0 \
--new=16:25576:32799 \
--change-name=16:loader2b \
--typecode=16:F79E76D9-AC98-418B-8F31-E17EA24FF07C \
--attributes=16:set:0 \
--new=14:32800:43007 \
--change-name=14:loader2 \
--typecode=14:F4FA3898-3478-4941-887D-FCEC4E9E3C05 \
--attributes=14:set:0 \
--new=15::+106M \
--typecode=15:ef00 \
--change-name=15:ESP \
--new=12::+4M \
--change-name=12:CIDATA \
--new=1:: \
--attributes=1:set:2
elif [ "${SUBARCH:-}" = "icicle" ] || [ "${SUBARCH:-}" = "pic64gx" ]; then
# Microchip Icicle Kit
sgdisk "${disk_image}" \
--set-alignment=2 \
--new=13:256:25575 \
--change-name=13:loader \
--typecode=13:ef02 \
--attributes=13:set:0 \
--new=15::+106M \
--typecode=15:ef00 \
--change-name=15:ESP \
--new=12::+4M \
--change-name=12:CIDATA \
--new=1:: \
--attributes=1:set:2
elif [ "${SUBARCH:-}" = "visionfive" ]; then
# VisionFive
sgdisk "${disk_image}" \
--set-alignment=2 \
--new=15::+106M \
--typecode=15:ef00 \
--change-name=15:ESP \
--new=12::+4M \
--change-name=12:CIDATA \
--new=3::+1M \
--change-name=3:uEnv \
--new=1:: \
--attributes=1:set:2
elif [ "${SUBARCH:-}" = "visionfive2" ] || [ "${SUBARCH:-}" = "milkvmars" ] || [ "${SUBARCH:-}" = "jh7110" ]; then
# JH7110 chips: VisionFive 2, Milk-V Mars
sgdisk "${disk_image}" \
--set-alignment=4096 \
--new=13:4096:8191 \
--typecode=13:2E54B353-1271-4842-806F-E436D6AF6985 \
--change-name=13:loader1 \
--new=2:8192:40959 \
--typecode=2:7a097280-70d2-44bc-886c-ff5ffbb7b098 \
--change-name=2:loader2 \
--new=12:40960:49151 \
--change-name=12:CIDATA \
--new=15:49152:253951 \
--typecode=15:ef00 \
--change-name=15:ESP \
--new=1:253952: \
--attributes=1:set:2
else
# preinstalled server, currently FU540
# FU740 too in the future
sgdisk "${disk_image}" \
--set-alignment=2 \
--new=13:34:2081 \
--change-name=13:loader1 \
--typecode=13:5B193300-FC78-40CD-8002-E86C45580B47 \
--attributes=13:set:0 \
--new=14:2082:10239 \
--change-name=14:loader2 \
--typecode=14:2E54B353-1271-4842-806F-E436D6AF6985 \
--attributes=14:set:0 \
--new=15::+106M \ --new=15::+106M \
--typecode=15:ef00 \ --typecode=15:ef00 \
--new=12::+4M \ --new=12::+4M \
--change-name=12:CIDATA \ --change-name=12:CIDATA \
--new=1:: \ --new=1:: \
--attributes=1:set:2
fi fi
;; ;;
amd64) amd64)
@ -265,100 +172,12 @@ install_grub() {
fi fi
;; ;;
riscv64) riscv64)
if [ -n "${SUBARCH:-}" ]; then
# Per-device images # Per-device images
local my_d=$(dirname $(readlink -f ${0})) local my_d=$(dirname $(readlink -f ${0}))
echo "Adjusting GRUB defaults for ${ARCH}" echo "Adjusting GRUB defaults for ${ARCH}/${SUBARCH}"
mkdir -p mountpoint/etc/default/grub.d/ mkdir -p mountpoint/etc/default/grub.d/
rm -rf mountpoint/etc/default/grub.d/*
cp ${my_d}/riscv64/grub/10_cmdline.cfg mountpoint/etc/default/grub.d/ cp ${my_d}/riscv64/grub/10_cmdline.cfg mountpoint/etc/default/grub.d/
echo "Installing GRUB for ${SUBARCH} board"
case "${SUBARCH}" in
"icicle")
cp ${my_d}/riscv64/grub/90_watchdog-thresh.cfg mountpoint/etc/default/grub.d/
# The real U-Boot
chroot mountpoint apt-get install -qqy u-boot-microchip
loader="${loop_device}p13"
dd if=mountpoint/usr/lib/u-boot/microchip_icicle/u-boot.payload of=$loader
;;
"nezha"|"licheerv")
echo "Reducing initramfs size for ${SUBARCH} board"
mkdir -p mountpoint/etc/initramfs-tools/conf.d/
cp ${my_d}/riscv64/initramfs-tools/modules_list.conf mountpoint/etc/initramfs-tools/conf.d/
cat ${my_d}/riscv64/initramfs-tools/allwinner >> mountpoint/etc/initramfs-tools/modules
chroot mountpoint update-initramfs -c -v -k all
echo "Installing U-Boot for ${SUBARCH} board"
if [ "$SUBARCH" = "licheerv" ]; then
# cryptsetup-initramfs is a large contributor of the initrd size: we have to
# remove it for the LicheeRV board, otherwise it fails to boot. cryptsetup-initramfs
# needs to embed plymouth (and then the drm/gpu stuff) for interacting with the user
# to decrypt the rootfs (passphrase key).
chroot mountpoint bash -c "apt remove -qqy cryptsetup-initramfs"
fi
# u-boot-nezha supports both the LicheeRV and the Nezha D1.
chroot mountpoint apt-get install -qqy u-boot-nezha
# Since version 2022.10 U-Boot SPL and U-Boot are installed onto the same partition.
# Package nezha-boot0 is not needed anymore.
loader1="${loop_device}p13"
dd if=mountpoint/usr/lib/u-boot/${SUBARCH}/u-boot-sunxi-with-spl.bin of=$loader1
;;
"pic64gx")
cp ${my_d}/riscv64/grub/90_watchdog-thresh.cfg mountpoint/etc/default/grub.d/
# u-boot-pic64gx contains the vendor U-Boot
chroot mountpoint apt-get install -qqy u-boot-pic64gx
loader="${loop_device}p13"
dd if=mountpoint/usr/lib/u-boot-pic64gx/u-boot.payload of=$loader
;;
"visionfive")
# factory u-boot requires a p3 partition with /boot/uEnv.txt file
uenv_dev="${loop_device}p3"
mkfs.ext4 "${uenv_dev}"
uenv_mnt_dir=`mktemp -d uenvXXX`
mount "${uenv_dev}" "${uenv_mnt_dir}"
mkdir -p "${uenv_mnt_dir}"/boot
cat <<'EOF' >${uenv_mnt_dir}/boot/uEnv.txt
scriptaddr=0x88100000
script_offset_f=0x1fff000
script_size_f=0x1000
kernel_addr_r=0x84000000
kernel_comp_addr_r=0x90000000
kernel_comp_size=0x10000000
fdt_addr_r=0x88000000
ramdisk_addr_r=0x88300000
bootcmd=load mmc 0:f ${kernel_addr_r} /EFI/ubuntu/grubriscv64.efi; bootefi ${kernel_addr_r}
bootcmd_mmc0=devnum=0; run mmc_boot
ipaddr=192.168.120.200
netmask=255.255.255.0
EOF
umount "${uenv_mnt_dir}"
rmdir "${uenv_mnt_dir}"
;;
"visionfive2"|"milkvmars"|"jh7110")
cp ${my_d}/riscv64/grub/90_watchdog-thresh.cfg mountpoint/etc/default/grub.d/
chroot mountpoint apt-get install -qqy u-boot-starfive
# U-Boot SPL
loader1="${loop_device}p13"
# Main U-Boot
loader2="${loop_device}p2"
dd if=mountpoint/usr/lib/u-boot/starfive_visionfive2/u-boot-spl.bin.normal.out of=$loader1
dd if=mountpoint/usr/lib/u-boot/starfive_visionfive2/u-boot.itb of=$loader2
;;
unmatched)
cp ${my_d}/riscv64/grub/90_watchdog-thresh.cfg mountpoint/etc/default/grub.d/
chroot mountpoint apt-get install -qqy u-boot-sifive
# U-Boot SPL
loader1="${loop_device}p13"
# Main U-Boot
loader2="${loop_device}p14"
dd if=mountpoint/usr/lib/u-boot/sifive_unmatched/u-boot-spl.bin of=$loader1
dd if=mountpoint/usr/lib/u-boot/sifive_unmatched/u-boot.itb of=$loader2
;;
esac
echo "Copying device trees" echo "Copying device trees"
kver=$(ls mountpoint/lib/modules | sort -V | tail -n 1) kver=$(ls mountpoint/lib/modules | sort -V | tail -n 1)
dtb_src_dirs=( dtb_src_dirs=(
@ -378,13 +197,6 @@ EOF
# Provide stock nocloud datasource # Provide stock nocloud datasource
# Allow interactive login without a cloud datasource. # Allow interactive login without a cloud datasource.
setup_cinocloud mountpoint setup_cinocloud mountpoint
else
# Other images e.g. cloud images
chroot mountpoint apt-get install -qqy u-boot-menu grub-efi-riscv64
efi_target=riscv64-efi
chroot mountpoint u-boot-update
fi
;; ;;
esac esac

View File

@ -41,7 +41,9 @@ create_partitions() {
--typecode=15:ef00 \ --typecode=15:ef00 \
--new=13::1G \ --new=13::1G \
--typecode=13:ea00 \ --typecode=13:ea00 \
--new=1: --new=1: \
--change-name=1:"$FS_LABEL" \
--typecode=1:"$(gpt_root_partition_uuid $ARCH)"
;; ;;
riscv64) riscv64)
sgdisk "${disk_image}" \ sgdisk "${disk_image}" \
@ -51,6 +53,8 @@ create_partitions() {
--new=15::+106M \ --new=15::+106M \
--typecode=15:ef00 \ --typecode=15:ef00 \
--new=1:: \ --new=1:: \
--change-name=1:"$FS_LABEL" \
--typecode=1:"$(gpt_root_partition_uuid $ARCH)" \
--attributes=1:set:2 --attributes=1:set:2
;; ;;
amd64) amd64)
@ -61,8 +65,10 @@ create_partitions() {
--new=15::+106M \ --new=15::+106M \
--new=1:: --new=1::
sgdisk "${disk_image}" \ sgdisk "${disk_image}" \
--change-name=1:"$FS_LABEL" \
-t 14:ef02 \ -t 14:ef02 \
-t 15:ef00 -t 15:ef00 \
-t 1:"$(gpt_root_partition_uuid $ARCH)"
;; ;;
esac esac
sgdisk "${disk_image}" \ sgdisk "${disk_image}" \

View File

@ -14,6 +14,10 @@ ROOTPART_START=1
my_d=$(dirname $(readlink -f ${0})) my_d=$(dirname $(readlink -f ${0}))
# NEW CPC default imagesize is 2.5G [2684354560 bytes] to avail of the slow increase in pkg sizes
# This value will be passed into live-build/functions as an override
imagesize=2684354560
case $ARCH:$SUBARCH in case $ARCH:$SUBARCH in
ppc64el:*|powerpc:*) ppc64el:*|powerpc:*)
echo "POWER disk images are handled separately" echo "POWER disk images are handled separately"
@ -95,6 +99,7 @@ if [ "${should_install_grub}" -eq 1 ]; then
echo "(hd0) ${loop_device}" > mountpoint/tmp/device.map echo "(hd0) ${loop_device}" > mountpoint/tmp/device.map
# install the required package to get the grub-install command # install the required package to get the grub-install command
chroot mountpoint apt-get -qqy install --no-install-recommends grub-pc grub2-common chroot mountpoint apt-get -qqy install --no-install-recommends grub-pc grub2-common
chroot mountpoint df --all --human-readable --print-type
chroot mountpoint grub-install ${loop_device} chroot mountpoint grub-install ${loop_device}
chroot mountpoint grub-bios-setup \ chroot mountpoint grub-bios-setup \
--boot-image=i386-pc/boot.img \ --boot-image=i386-pc/boot.img \

View File

@ -0,0 +1 @@
datasource_list: [ OpenStack, None ]

View File

@ -0,0 +1,2 @@
dsmode: local
instance_id: ubuntu-server

View File

@ -0,0 +1,104 @@
name: ubuntu-minimal
version: "0.1"
base: bare
build-base: devel
summary: Minimal Ubuntu image for CPC
description: A minimal Ubuntu image to be built using livecd-rootfs by CPC
platforms:
amd64:
volumes:
pc:
schema: gpt
structure:
# 1. BIOS Boot
- name: bios-boot
type: 21686148-6449-6E6F-744E-656564454649
role: system-boot
filesystem: vfat
size: 4M
partition-number: 14
# 2. EFI System Partition
- name: efi
type: C12A7328-F81F-11D2-BA4B-00A0C93EC93B
filesystem: vfat
filesystem-label: UEFI
role: system-boot
size: 106M
partition-number: 15
# 3. Linux Extended Boot
- name: boot
type: 0FC63DAF-8483-4772-8E79-3D69D8477DE4
filesystem: ext4
filesystem-label: BOOT
role: system-data
size: 1G
partition-number: 13
# 4. Root Filesystem
- name: rootfs
type: 0FC63DAF-8483-4772-8E79-3D69D8477DE4
filesystem: ext4
filesystem-label: cloudimg-rootfs
role: system-data
size: 3G
partition-number: 1
filesystems:
default:
- mount: "/"
device: "(volume/pc/rootfs)"
- mount: "/boot"
device: "(volume/pc/boot)"
- mount: "/boot/efi"
device: "(volume/pc/efi)"
parts:
rootfs:
plugin: nil
build-packages: ["mmdebstrap"]
override-build: |
mmdebstrap --arch $CRAFT_ARCH_BUILD_FOR \
--mode=sudo \
--format=dir \
--variant=minbase \
--include=apt \
resolute \
$CRAFT_PART_INSTALL/ \
http://archive.ubuntu.com/ubuntu/
rm -r $CRAFT_PART_INSTALL/dev/*
mkdir $CRAFT_PART_INSTALL/boot/efi
organize:
'*': (overlay)/
packages:
plugin: nil
overlay-packages:
- ubuntu-server-minimal
- grub2-common
- grub-pc
- shim-signed
- linux-image-generic
overlay-script: |
rm $CRAFT_OVERLAY/etc/cloud/cloud.cfg.d/90_dpkg.cfg
snaps:
plugin: nil
after: [packages]
overlay-script: |
env SNAPPY_STORE_NO_CDN=1 snap prepare-image --classic \
--arch=amd64 --snap snapd --snap core24 "" $CRAFT_OVERLAY
fstab:
plugin: nil
after: [snaps]
overlay-script: |
cat << EOF > $CRAFT_OVERLAY/etc/fstab
LABEL=cloudimg-rootfs / ext4 discard,errors=remount-ro 0 1
LABEL=BOOT /boot ext4 defaults 0 2
LABEL=UEFI /boot/efi vfat umask=0077 0 1
EOF
cloud-init:
plugin: dump
source: cloud-init/

View File

@ -0,0 +1,81 @@
#!/bin/bash -eux
. config/functions
ARCH="${ARCH:-}"
SUBPROJECT="${SUBPROJECT:-}"
# We want to start off imagecraft builds with just amd64 support right now
case $ARCH in
amd64)
;;
*)
echo "imagecraft build is currently not implemented for ARCH=${ARCH:-unset}."
exit 0
;;
esac
case ${SUBPROJECT} in
minimized)
;;
*)
echo "imagecraft build is currently not implemented for SUBPROJECT=${SUBPROJECT:-unset}."
exit 0
;;
esac
_src_d=$(dirname $(readlink -f ${0}))
snap install imagecraft --classic --channel latest/edge
cp -r "$_src_d"/imagecraft-configs/* .
CRAFT_BUILD_ENVIRONMENT=host imagecraft --verbosity debug pack
# We are using this function instead of mount_disk_image from functions
# because imagecraft doesn't currently support XBOOTLDR's GUID and
# mount_disk_image has an explicit check for the XBOOTLDR GUID
# TODO: Use mount_disk_image once imagecraft supports XBOOTLDR's GUID
mount_image_partitions() {
mount_image "${disk_image}" "$ROOT_PARTITION"
# Making sure that the loop device is ready
partprobe "${loop_device}"
udevadm settle
mount_partition "${rootfs_dev_mapper}" "$mountpoint"
mount "${loop_device}p13" "$mountpoint/boot"
mount "${loop_device}p15" "$mountpoint/boot/efi"
}
install_grub_on_image() {
divert_grub "$mountpoint"
chroot "$mountpoint" grub-install --target=i386-pc "${loop_device}"
chroot "$mountpoint" update-grub
undivert_grub "$mountpoint"
echo "GRUB for BIOS boot installed successfully."
}
unmount_image_partitions() {
umount "$mountpoint/boot/efi"
umount "$mountpoint/boot"
umount_partition "$mountpoint"
rmdir "$mountpoint"
}
disk_image="pc.img"
ROOT_PARTITION=1
mountpoint=$(mktemp -d)
mount_image_partitions
install_grub_on_image
create_manifest "$mountpoint/" "$PWD/livecd.ubuntu-cpc.imagecraft.manifest" "$PWD/livecd.ubuntu-cpc.imagecraft.spdx" "cloud-image-$ARCH-$(date +%Y%m%dT%H:%M:%S)" "false"
unmount_image_partitions
clean_loops
trap - EXIT
qemu-img convert -f raw -O qcow2 "${disk_image}" livecd.ubuntu-cpc.imagecraft.img

View File

@ -2,3 +2,4 @@
# For minimum output use # For minimum output use
# GRUB_CMDLINE_LINUX_DEFAULT="quiet" # GRUB_CMDLINE_LINUX_DEFAULT="quiet"
GRUB_CMDLINE_LINUX_DEFAULT="efi=debug earlycon=sbi" GRUB_CMDLINE_LINUX_DEFAULT="efi=debug earlycon=sbi"
GRUB_TERMINAL=console

View File

@ -1,3 +0,0 @@
# When booting with Radeon GPUs a soft lockup was observed. Increase the
# watchdog threshhold.
GRUB_CMDLINE_LINUX_DEFAULT="$GRUB_CMDLINE_LINUX_DEFAULT sysctl.kernel.watchdog_thresh=60"

View File

@ -1,3 +0,0 @@
# Required drivers to boot off MMC
mmc-block
sunxi-mmc

Some files were not shown because too many files have changed in this diff Show More