Compare commits

...

101 Commits

Author SHA1 Message Date
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
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
93 changed files with 2249 additions and 343 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

236
README.parameters Normal file
View File

@ -0,0 +1,236 @@
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)
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))
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.
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).

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

208
debian/changelog vendored
View File

@ -1,3 +1,211 @@
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],

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
@ -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 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

@ -9,9 +9,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*)
;; ;;
@ -52,6 +50,7 @@ mkdir -p config
cp -af /usr/share/livecd-rootfs/live-build/functions config/functions cp -af /usr/share/livecd-rootfs/live-build/functions config/functions
cp -af /usr/share/livecd-rootfs/live-build/lb_*_layered config/ cp -af /usr/share/livecd-rootfs/live-build/lb_*_layered config/
cp -af /usr/share/livecd-rootfs/live-build/snap-seed-parse.py config/snap-seed-parse cp -af /usr/share/livecd-rootfs/live-build/snap-seed-parse.py config/snap-seed-parse
cp -af /usr/share/livecd-rootfs/live-build/snap-seed-missing-providers.py config/snap-seed-missing-providers
cp -af /usr/share/livecd-rootfs/live-build/expand-task config/expand-task cp -af /usr/share/livecd-rootfs/live-build/expand-task config/expand-task
cp -af /usr/share/livecd-rootfs/live-build/squashfs-exclude-files config/ cp -af /usr/share/livecd-rootfs/live-build/squashfs-exclude-files config/
@ -401,15 +400,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
@ -646,7 +636,7 @@ case $PROJECT in
esac esac
case $PROJECT in case $PROJECT in
ubuntu-server|ubuntu-mini-iso) ubuntu-mini-iso)
COMPONENTS='main' COMPONENTS='main'
;; ;;
edubuntu|ubuntu-budgie|ubuntucinnamon|ubuntukylin) edubuntu|ubuntu-budgie|ubuntucinnamon|ubuntukylin)
@ -670,13 +660,26 @@ 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
# 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
# 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
@ -907,6 +910,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 +921,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 +937,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 +1012,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 +1114,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
;; ;;
@ -1135,6 +1146,8 @@ case $PROJECT in
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
@ -1218,14 +1231,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
@ -1378,6 +1384,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 \
"$@" "$@"
@ -1396,6 +1403,8 @@ echo "IMAGEFORMAT=\"$IMAGEFORMAT\"" >> config/chroot
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
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
@ -1519,7 +1528,7 @@ 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:|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
@ -1689,3 +1698,11 @@ apt-get -y download $PREINSTALL_POOL
EOF EOF
fi fi
fi fi
if [ "${MAKE_ISO}" = "yes" ]; then
# XXX should pass --build-type here.
/usr/share/livecd-rootfs/live-build/gen-iso-ids \
--project $PROJECT ${SUBPROJECT:+--subproject $SUBPROJECT} \
--arch $ARCH ${SUBARCH:+--subarch $SUBARCH} ${NOW+--serial $NOW} \
--output-dir config/iso-ids/
fi

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() {
@ -566,8 +568,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 [ "$PROJECT" = "ubuntu" -o "$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 +668,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 +802,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
@ -853,6 +868,17 @@ snap_validate_seed() {
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}")
@ -1057,6 +1083,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 +1112,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 +1146,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
@ -1377,3 +1404,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=/usr/share/livecd-rootfs/live-build/ /usr/share/livecd-rootfs/live-build/isobuild --workdir config/iso-dir "$@"
}

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

@ -0,0 +1,197 @@
#!/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-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,109 @@
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)
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):
"""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.logger.run(
["apt-get", "download", pkg_info.spec],
cwd=target_dir,
check=True,
env=self._apt_env(),
)
def in_release_path(self) -> pathlib.Path:
"""Return the path to the InRelease file.
This assumes exactly one InRelease file matches the pattern.
Will raise ValueError if there are 0 or multiple matches.
"""
[path] = self.apt_root.joinpath("var/lib/apt/lists").glob(
f"*_dists_{self.series}_InRelease"
)
return path

View File

@ -0,0 +1,386 @@
import contextlib
import json
import os
import pathlib
import shlex
import shutil
import subprocess
import sys
from isobuilder.apt_state import AptStateManager
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._series = self._arch = self._gpg_key = self._apt_state = None
# UTILITY STUFF
def _read_config(self):
with self.workdir.joinpath("config.json").open() as fp:
data = json.load(fp)
self._series = data["series"]
self._arch = data["arch"]
@property
def arch(self):
if self._arch is None:
self._read_config()
return self._arch
@property
def series(self):
if self._series is None:
self._read_config()
return self._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")
with self.workdir.joinpath("config.json").open("w") as fp:
json.dump({"arch": arch, "series": series}, fp)
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")
prefix = "filesystem.initrd-"
dot_disk = self.iso_root.joinpath(".disk")
for initrd in casper_dir.glob(f"{prefix}*"):
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. The [uuid_conf] = confs
# unpacking asserts exactly one match; multiple matches would
# indicate an unexpected initrd structure.
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)}")
uuid_conf.rename(
dot_disk.joinpath("casper-uuid-" + initrd.name[len(prefix) :])
)
shutil.rmtree(initrddir)
def add_live_filesystem(self, artifact_prefix: pathlib.Path):
# Link build artifacts into the ISO's casper directory. We use hardlinks
# (not copies) for filesystem efficiency - they reference the same inode.
#
# Artifacts come from the layered build with names like "for-iso.base.squashfs"
# and need to be renamed for casper. The prefix is stripped, so:
# for-iso.base.squashfs -> base.squashfs
# for-iso.kernel-generic -> filesystem.kernel-generic
#
# Kernel and initrd get the extra "filesystem." prefix because debian-cd
# expects names like filesystem.kernel-* and filesystem.initrd-*.
casper_dir = self.iso_root.joinpath("casper")
artifact_dir = artifact_prefix.parent
filename_prefix = artifact_prefix.name
def link(src, target_name):
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)
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 item in "kernel", "initrd":
for path in artifact_dir.glob(f"{filename_prefix}{item}-*"):
newname = "filesystem." + path.name[len(filename_prefix) :]
link(path, newname)
self._extract_casper_uuids()
def make_bootable(self, project: str, capproject: str, subarch: str):
# debian-cd is Ubuntu's CD/ISO image build system. It contains
# architecture and series-specific boot configuration scripts that set up
# GRUB, syslinux, EFI boot, etc. The tools/boot/$series/boot-$arch script
# knows how to make an ISO bootable for each architecture.
#
# TODO: The boot configuration logic should eventually be ported directly
# into isobuilder to avoid this external dependency and git clone.
debian_cd_dir = self.workdir.joinpath("debian-cd")
with self.logger.logged("cloning debian-cd"):
self.logger.run(
[
"git",
"clone",
"--depth=1",
"https://git.launchpad.net/~ubuntu-cdimage/debian-cd/+git/ubuntu",
debian_cd_dir,
],
)
# Override apt-selection to use our ISO's apt configuration instead of
# debian-cd's default. This ensures the boot scripts get packages from
# the correct repository when installing boot packages.
apt_selection = debian_cd_dir.joinpath("tools/apt-selection")
with self.logger.logged("overwriting apt-selection"):
apt_selection.write_text(
"#!/bin/sh\n" f"APT_CONFIG={self.apt_state.apt_conf_path} apt-get $@\n"
)
env = dict(
os.environ,
BASEDIR=str(debian_cd_dir),
DIST=self.series,
PROJECT=project,
CAPPROJECT=capproject,
SUBARCH=subarch,
)
tool_name = f"tools/boot/{self.series}/boot-{self.arch}"
with self.logger.logged(f"running {tool_name} ...", done_msg="... done"):
self.logger.run(
[
debian_cd_dir.joinpath(tool_name),
"1",
self.iso_root,
],
env=env,
)
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 casper-md5check expects.
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):
# 1.mkisofs_opts is generated by debian-cd's make_bootable step. The "1"
# refers to "pass 1" of the build (a legacy naming convention). It contains
# architecture-specific xorriso options for boot sectors, EFI images, etc.
mkisofs_opts = shlex.split(self.workdir.joinpath("1.mkisofs_opts").read_text())
self.checksum()
# 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.
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)
if self.arch == "riscv64":
debian_cd_dir = self.workdir.joinpath("debian-cd")
add_riscv_gpt = debian_cd_dir.joinpath("tools/add_riscv_gpt")
self.logger.run([add_riscv_gpt, dest], cwd=self.workdir)

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

@ -184,6 +184,18 @@ build_layered_squashfs () {
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"
@ -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

@ -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

@ -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

@ -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

View File

@ -1 +0,0 @@
U_BOOT_PARAMETERS="ro efi=debug earlycon=sbi"

View File

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

View File

@ -1,5 +1,18 @@
#!/bin/sh #!/bin/sh
generate_grub_config ()
{
# Generate the grub config file for netboot tarballs
# $1 Generated file path
cat > "${1}" <<EOF
menuentry "Install Ubuntu Server" {
set gfxpayload=keep
linux linux iso-url=#ISOURL# ip=dhcp ---
initrd initrd
}
EOF
}
case $PASS in case $PASS in
ubuntu-server-minimal.ubuntu-server.installer.generic*.netboot) ubuntu-server-minimal.ubuntu-server.installer.generic*.netboot)
;; ;;
@ -46,13 +59,7 @@ case $ARCH in
mv chroot/usr/lib/grub/x86_64-efi-signed/grubnetx64.efi.signed tarball/amd64/grubx64.efi mv chroot/usr/lib/grub/x86_64-efi-signed/grubnetx64.efi.signed tarball/amd64/grubx64.efi
mkdir tarball/amd64/grub tarball/amd64/pxelinux.cfg mkdir tarball/amd64/grub tarball/amd64/pxelinux.cfg
cat > tarball/amd64/grub/grub.cfg.in <<EOF generate_grub_config tarball/amd64/grub/grub.cfg.in
menuentry "Install Ubuntu Server" {
set gfxpayload=keep
linux linux iso-url=#ISOURL# ip=dhcp ---
initrd initrd
}
EOF
cat > tarball/amd64/pxelinux.cfg/default.in <<EOF cat > tarball/amd64/pxelinux.cfg/default.in <<EOF
DEFAULT install DEFAULT install
LABEL install LABEL install
@ -72,13 +79,7 @@ EOF
mv chroot/usr/lib/grub/arm64-efi-signed/grubnetaa64.efi.signed tarball/arm64/grubaa64.efi mv chroot/usr/lib/grub/arm64-efi-signed/grubnetaa64.efi.signed tarball/arm64/grubaa64.efi
mkdir tarball/arm64/grub mkdir tarball/arm64/grub
cat > tarball/arm64/grub/grub.cfg.in <<EOF generate_grub_config tarball/arm64/grub/grub.cfg.in
menuentry "Install Ubuntu Server" {
set gfxpayload=keep
linux linux iso-url=#ISOURL# ip=dhcp ---
initrd initrd
}
EOF
;; ;;
s390x) s390x)
@ -125,6 +126,13 @@ LABEL install
append=ip=dhcp iso-url=#ISOURL# --- append=ip=dhcp iso-url=#ISOURL# ---
EOF EOF
;; ;;
riscv64)
mv chroot/usr/lib/grub/riscv64-efi/monolithic/grubnetriscv64.efi tarball/riscv64/grubriscv64.efi
mkdir tarball/riscv64/grub
generate_grub_config tarball/riscv64/grub/grub.cfg.in
;;
esac esac
tar -C tarball -czf livecd.${PROJECT}.netboot.tar.gz . tar -C tarball -czf livecd.${PROJECT}.netboot.tar.gz .

View File

@ -0,0 +1,16 @@
# AppArmor restrictions of unprivileged user namespaces
# Allows to restrict the use of unprivileged user namespaces to applications
# which have an AppArmor profile loaded which specifies the userns
# permission. All other applications (whether confined by AppArmor or not) will
# be denied the use of unprivileged user namespaces.
#
# See
# https://gitlab.com/apparmor/apparmor/-/wikis/unprivileged_userns_restriction
# https://gitlab.com/apparmor/apparmor/-/wikis/unprivileged_unconfined
#
# If it is desired to disable this restriction, it is preferable to create an
# additional file named /etc/sysctl.d/20-apparmor.conf which will override this
# current file and sets this value to 0 rather than editing this current file
kernel.apparmor_restrict_unprivileged_userns = 0
kernel.apparmor_restrict_unprivileged_unconfined = 1

View File

@ -15,6 +15,25 @@ cat <<EOF > /etc/initramfs-tools/conf.d/default-layer.conf
LAYERFS_PATH=${PASS}.squashfs LAYERFS_PATH=${PASS}.squashfs
EOF EOF
cat <<EOF > /etc/sysctl.d/20-apparmor.conf
# AppArmor restrictions of unprivileged user namespaces
# Allows to restrict the use of unprivileged user namespaces to applications
# which have an AppArmor profile loaded which specifies the userns
# permission. All other applications (whether confined by AppArmor or not) will
# be denied the use of unprivileged user namespaces.
#
# See
# https://gitlab.com/apparmor/apparmor/-/wikis/unprivileged_userns_restriction
# https://gitlab.com/apparmor/apparmor/-/wikis/unprivileged_unconfined
#
# If it is desired to disable this restriction, it is preferable to create an
# additional file named /etc/sysctl.d/20-apparmor.conf which will override this
# current file and sets this value to 0 rather than editing this current file
kernel.apparmor_restrict_unprivileged_userns = 0
kernel.apparmor_restrict_unprivileged_unconfined = 1
EOF
if which glib-compile-schemas >/dev/null 2>&1; then if which glib-compile-schemas >/dev/null 2>&1; then
glib-compile-schemas /usr/share/glib-2.0/schemas/ glib-compile-schemas /usr/share/glib-2.0/schemas/
fi fi

View File

@ -12,14 +12,103 @@ case ${PASS:-} in
;; ;;
esac esac
if [ -n "${SUBPROJECT:-}" ]; then
echo "We don't run Ubuntu Desktop hooks for this project."
exit 0
fi
. config/binary . config/binary
. config/functions . config/functions
# Naive conversion from YAML to JSON. This is needed because yq is in universe
# (but jq is not).
yaml_to_json()
{
python3 -c '
import json
import sys
import yaml
json.dump(yaml.safe_load(sys.stdin), sys.stdout, default=str)
'
}
# Use jq to retrieve a list of --snap options from a given *signed* model.
get_snaps_args_excluding()
{
local model=$1
local jq_filter='
# Find all snaps that are not filtered out.
# The filtered out snaps are passed as positional arguments so they end up in
# the $ARGS.positional array.
.snaps[] | select(.name | IN($ARGS.positional[]) | not)
# Then forge the --snap option.
| "--snap=" + .name + "=" + .["default-channel"]'
shift
# The model is signed and is not valid YAML unless we get rid of the
# signature. Here we assume the only blank line is before the signature.
sed '/^$/,$d' -- "$model" \
| yaml_to_json \
| jq --raw-output "$jq_filter" --args "$@"
}
# Use jq to retrieve a list of --snap options from a given *signed* model.
get_snaps_args()
{
local model=$1
get_snaps_args_excluding "$model"
}
_get_components_filtered()
{
local excluded=$1
local model=$2
local jq_filter='
# Find all snaps that are either filtered in or filtered out
# The filtered in (or out) snaps are passed as positional arguments so they end up in
# the $ARGS.positional array. The excluded variable is passed separately and
# tells if we want to filter in (i.e., excluded=false) or filter out (i.e.,
# excluded=true).
.snaps[] | select(.name | IN($ARGS.positional[]) | if $excluded then not else . end)
# and have components
| select(.components)
# Then save the name of each snap in a variable
| .name as $snap
# Then for each entry that has "optional"
| .components | to_entries | map(select(.value.presence == "optional"))
# Output its name with the snap name prepended
| "\($snap)" + "+" + .[].key'
shift 2
sed '/^$/,$d' -- "$model" \
| yaml_to_json \
| jq --raw-output "$jq_filter" --argjson excluded "$excluded" --args "$@"
}
# Get list of all components for all snaps
get_all_components()
{
local model=$1
# Provide an exclusion list but empty
_get_components_filtered true "$model"
}
# Get list of all components for all snaps except the ones specified.
get_components_excluding()
{
local model=$1
shift
_get_components_filtered true "$model" "$@"
}
# Get list of all components for the snaps specified.
get_components()
{
local model=$1
shift
_get_components_filtered false "$model" "$@"
}
# Generation of the model: # Generation of the model:
# * At https://github.com/canonical/models one can find a repo of raw, # * At https://github.com/canonical/models one can find a repo of raw,
# unsigned, input .json files, and their signed .model equivalents. # unsigned, input .json files, and their signed .model equivalents.
@ -36,12 +125,60 @@ fi
# live-build/${PROJECT}/ubuntu-classic-amd64.model # live-build/${PROJECT}/ubuntu-classic-amd64.model
# env SNAPPY_STORE_NO_CDN=1 snap known --remote model series=16 brand-id=canonical model=ubuntu-classic-2410-amd64 > config/classic-model.model # env SNAPPY_STORE_NO_CDN=1 snap known --remote model series=16 brand-id=canonical model=ubuntu-classic-2410-amd64 > config/classic-model.model
model=/usr/share/livecd-rootfs/live-build/${PROJECT}/ubuntu-classic-amd64.model #
dangerous_model=/usr/share/livecd-rootfs/live-build/${PROJECT}/ubuntu-classic-amd64-dangerous.model
stable_model=/usr/share/livecd-rootfs/live-build/${PROJECT}/ubuntu-classic-amd64.model
prepare_args=()
components=()
# for the dangerous subproject, we need the dangerous model! # for the dangerous subproject, we need the dangerous model!
if [ $SUBPROJECT = "dangerous" ]; then if [ "$SUBPROJECT" = "dangerous" ]; then
model=/usr/share/livecd-rootfs/live-build/${PROJECT}/ubuntu-classic-amd64-dangerous.model # As with the "classically" seeded snaps, snaps from the edge channel may
# require different content snaps to be installed, so they must be
# included in the system as well. We just use the same list as was
# computed in snap_validate_seed.
model="${dangerous_model}"
while read snap; do
prepare_args+=("--snap=${snap}=edge")
done < config/missing-providers
for comp in $(get_all_components "$model"); do
components+=("$comp")
done
else
model="${stable_model}"
# If we need to override anything from the model, we need grade: dangerous.
# And if so, uncomment the below to use the dangerous model and set the
# snaps_from_dangerous and snaps_from_beta variables to still use snaps
# from the stable model.
#model="${dangerous_model}"
snaps_from_dangerous=()
# For these snaps, we ignore the model entirely.
snaps_from_beta=()
for snap in "${snaps_from_beta[@]}"; do
prepare_args+=("--snap=$snap=beta")
done
# snaps that we are special casing.
_exclude=("${snaps_from_dangerous[@]}" "${snaps_from_beta[@]}")
if [ "$model" = "$dangerous_model" ]; then
for snap_arg in $(get_snaps_args_excluding "$stable_model" "${_exclude[@]}"); do
prepare_args+=("$snap_arg")
done
fi fi
for comp in $(get_components_excluding "$stable_model" "${_exclude[@]}"); do
components+=("$comp")
done
for comp in $(get_components "$dangerous_model" "${snaps_from_dangerous[@]}"); do
components+=("$comp")
done
fi
for comp in "${components[@]}"; do
prepare_args+=(--comp "$comp")
done
channel="" channel=""
if [ -n "${CHANNEL:-}" ]; then if [ -n "${CHANNEL:-}" ]; then
@ -52,7 +189,7 @@ fi
# snap versions regardless of phasing status # snap versions regardless of phasing status
env SNAPPY_STORE_NO_CDN=1 UBUNTU_STORE_COHORT_KEY="+" snap prepare-image \ env SNAPPY_STORE_NO_CDN=1 UBUNTU_STORE_COHORT_KEY="+" snap prepare-image \
--classic $model $channel chroot --classic $model $channel "${prepare_args[@]}" chroot
mv chroot/system-seed/systems/* chroot/system-seed/systems/enhanced-secureboot-desktop mv chroot/system-seed/systems/* chroot/system-seed/systems/enhanced-secureboot-desktop
rsync -av chroot/system-seed/{systems,snaps} chroot/var/lib/snapd/seed rsync -av chroot/system-seed/{systems,snaps} chroot/var/lib/snapd/seed

View File

@ -3,7 +3,7 @@
echo "Creating Hyper-V image with Desktop..." echo "Creating Hyper-V image with Desktop..."
case ${SUBPROJECT:-} in case ${SUBPROJECT:-} in
minimized|"") minimized|dangerous|"")
echo "We don't create minimized images for $0." echo "We don't create minimized images for $0."
exit 0 exit 0
;; ;;

View File

@ -0,0 +1,8 @@
# When booting the live ISO, snapd seeding takes a while to complete, which
# can cause GDM to start before the Ubuntu installer is seeded and ready to be
# launched. This leads to a confusing delay between the user leaving Plymouth
# and seeing the desktop wallpaper and the installer launching.
# This drop-in delays display-manager.service until snapd seeding completes, so
# the installer launches within seconds of Plymouth disappearing.
[Unit]
After=snapd.seeded.service

View File

@ -0,0 +1,6 @@
# force reexecuting the snapd snap version on the live system
# while developping features that only lands on edge, even if the
# deb version is higher.
# This allows automated tests to always run whats next.
#[Service]
#Environment="SNAP_REEXEC=force"

View File

@ -2,7 +2,7 @@ type: model
authority-id: canonical authority-id: canonical
series: 16 series: 16
brand-id: canonical brand-id: canonical
model: ubuntu-classic-2510-amd64-dangerous model: ubuntu-classic-2604-amd64-dangerous
architecture: amd64 architecture: amd64
base: core24 base: core24
classic: true classic: true
@ -10,12 +10,17 @@ distribution: ubuntu
grade: dangerous grade: dangerous
snaps: snaps:
- -
default-channel: classic-25.10/edge default-channel: classic-26.04/edge
id: UqFziVZDHLSyO3TqSWgNBoAdHbLI4dAH id: UqFziVZDHLSyO3TqSWgNBoAdHbLI4dAH
name: pc name: pc
type: gadget type: gadget
- -
default-channel: 25.10/beta components:
nvidia-580-uda-ko:
presence: optional
nvidia-580-uda-user:
presence: optional
default-channel: 26.04/beta
id: pYVQrBcKmBa0mZ4CCN7ExT6jH8rY1hza id: pYVQrBcKmBa0mZ4CCN7ExT6jH8rY1hza
name: pc-kernel name: pc-kernel
type: kernel type: kernel
@ -29,6 +34,11 @@ snaps:
id: dwTAh7MZZ01zyriOZErqd1JynQLiOGvM id: dwTAh7MZZ01zyriOZErqd1JynQLiOGvM
name: core24 name: core24
type: base type: base
-
default-channel: latest/edge
id: cUqM61hRuZAJYmIS898Ux66VY61gBbZf
name: core26
type: base
- -
default-channel: latest/edge default-channel: latest/edge
id: PMrrV4ml8uWuEUDBT8dSGnKUYbevVhc4 id: PMrrV4ml8uWuEUDBT8dSGnKUYbevVhc4
@ -40,55 +50,60 @@ snaps:
name: bare name: bare
type: base type: base
- -
default-channel: 1/edge/ubuntu-25.10 default-channel: latest/edge
id: HyhSEBPv3vHsW6uOHkQR384NgI7S6zpj
name: mesa-2404
type: app
-
default-channel: 1/edge
id: EI0D1KHjP8XiwMZKqSjuh6W8zvcowUVP id: EI0D1KHjP8XiwMZKqSjuh6W8zvcowUVP
name: firmware-updater name: firmware-updater
type: app type: app
- -
default-channel: 1/edge/ubuntu-25.10 default-channel: 1/edge
id: FppXWunWzuRT2NUT9CwoBPNJNZBYOCk0 id: FppXWunWzuRT2NUT9CwoBPNJNZBYOCk0
name: desktop-security-center name: desktop-security-center
type: app type: app
- -
default-channel: 1/edge/ubuntu-25.10 default-channel: 1/edge
id: aoc5lfC8aUd2VL8VpvynUJJhGXp5K6Dj id: aoc5lfC8aUd2VL8VpvynUJJhGXp5K6Dj
name: prompting-client name: prompting-client
type: app type: app
- -
default-channel: 2/edge/ubuntu-25.10 default-channel: 2/edge
id: gjf3IPXoRiipCu9K0kVu52f0H56fIksg id: gjf3IPXoRiipCu9K0kVu52f0H56fIksg
name: snap-store name: snap-store
type: app type: app
- -
default-channel: latest/edge/ubuntu-25.10 default-channel: latest/edge
id: jZLfBRzf1cYlYysIjD2bwSzNtngY0qit id: jZLfBRzf1cYlYysIjD2bwSzNtngY0qit
name: gtk-common-themes name: gtk-common-themes
type: app type: app
- -
default-channel: latest/edge/ubuntu-25.10 default-channel: latest/edge
id: 3wdHCAVyZEmYsCMFDE9qt92UV8rC8Wdk id: 3wdHCAVyZEmYsCMFDE9qt92UV8rC8Wdk
name: firefox name: firefox
type: app type: app
- -
default-channel: latest/edge/ubuntu-25.10 default-channel: latest/edge
id: lATO8HzwVvrAPrlZRAWpfyrJKlAJrZS3 id: ew7OxpbRTxfK7ImpIygRR85lkxvU7Pzt
name: gnome-42-2204 name: gnome-46-2404
type: app type: app
- -
default-channel: latest/edge/ubuntu-25.10 default-channel: latest/edge
id: IrwRHakqtzhFRHJOOPxKVPU0Kk7Erhcu id: IrwRHakqtzhFRHJOOPxKVPU0Kk7Erhcu
name: snapd-desktop-integration name: snapd-desktop-integration
type: app type: app
timestamp: 2025-05-01T12:00:00.0Z timestamp: 2025-12-09T12:00:00.0Z
sign-key-sha3-384: 9tydnLa6MTJ-jaQTFUXEwHl1yRx7ZS4K5cyFDhYDcPzhS7uyEkDxdUjg9g08BtNn sign-key-sha3-384: 9tydnLa6MTJ-jaQTFUXEwHl1yRx7ZS4K5cyFDhYDcPzhS7uyEkDxdUjg9g08BtNn
AcLBXAQAAQoABgUCaKKyiQAKCRDgT5vottzAEt+LD/9GTgoaYQg0qYohdnYYQkiWJbtNcZgLRpUf AcLBXAQAAQoABgUCaUFt7QAKCRDgT5vottzAEhdnD/92LBcQm3iw/kPao4KqGE0OhfXDFd7Z6+Qv
gPswZsBzmDLbH0XyLWB9h32gDyZ6Gdt3c9uK2hqCaNTaLJyf5eGs/7zjs9lVmEf3MNSr9FGx6Vek A1Dlzz6Cw0tuj0r5aZH7vJQCx4kC1Eaoi8apg3XhqAyhr74/MsIwMhPPL8qcSNv8ZWruoGwFp/rx
i+NPeOEjrmwjMXzi9FnTDTJzW0cMzCEzYiTHWRF8K5WCVumuzMMUCihZbuHvZCjXoIJ+RrSBMvbE M6NSBKc6hrYqACYfEkBwfq9SgmIDQKFeBVudwswLK2SN58wrDNJjuWz/eJ5hUIIe3ga5ScfzO4Jr
udzmJ0NIEgCGA+r32kEkcauNTrMwXdosdTYhrKy/dcy6SrACaZzvQAwPYXy3UJEDmz6qQQ/94G9s jTWS4kh5lpttCPFX8ouLkMgLUxijQpxFbHoF1trXJndFvavStT0yuC0y5TXzb3wJbbiF/MXZWyjV
5P/bqrOZVoL22H9a/6WnEulHyCNAVlotpbY3Dij1yHp5KEiFCuREP/MauLdONCY+snimwxpItm7L /4U+oQLodO77MhaD01kk2y5bZ62YuQ3MPL0fQGypon12GPHeNNcEcYWRZlFv+JkWAduWlnuefj1D
B4D92BTkhe9noiyRbSU3fLrMJGY//PKCxkQMVyvkx/aUnyqeySRCP6U7U6t09P2/WzmJULT+a+9N dVWV8dQQmSZGZNiGTsIJxkY9+4B+t/OhosGDc6jEmEZcKNVi9fnl0+awkzK6scNNmupZ8NwJl8ZR
pgXrXt3k69zqI0U74R3+JwiU/VrZq6cssdwx8unKYtoOT5O3G0b7q50Lv2RyNHQSjrlSU397HKkQ mJSsfaBcH4paYV1x31y4uTELv+OuDWAJ3D0RoCR8H0djTBxRhsF2/JpSJasxVmSbzWHPSeM3f1aO
Gnnb/w4caV+O/cKlDlXNXijZI/kxfP+tKqRHMSRLmV3I8W+/nh6YE4/NMiXVicej3tTaPtWlBI+9 ChZGwbD6J2SpzsrdogUP/9z6o8YuVnJkOxoBYuXhT1pEYTd93/hE++j3MpOqey/xw8UDbYmq5oJf
hj0Chtlk+wWD+9MjC1suJh4XLHAXbYJczSFdb3qcQq7f64v70sNoLzW4ekGUlSpbazNaX7P+Liov uKaYLOMphqDm5hUCZmxQp8gTzDleZGjxYS2fOS4qFUJlvyVwsSoJMXU+6YfA6tgEQ4Dbh6zp6r78
VBGXSpziQJuF4y/BSU8tCweJExhkibFEBar5SCKbvw== MjEqfWn4lL16xW2Zzr6e8xWwUrM7T3Gp4WTA7/xOeA==

View File

@ -2,7 +2,7 @@ type: model
authority-id: canonical authority-id: canonical
series: 16 series: 16
brand-id: canonical brand-id: canonical
model: ubuntu-classic-2510-amd64 model: ubuntu-classic-2604-amd64
architecture: amd64 architecture: amd64
base: core24 base: core24
classic: true classic: true
@ -10,12 +10,17 @@ distribution: ubuntu
grade: signed grade: signed
snaps: snaps:
- -
default-channel: classic-25.10/stable default-channel: classic-26.04/stable
id: UqFziVZDHLSyO3TqSWgNBoAdHbLI4dAH id: UqFziVZDHLSyO3TqSWgNBoAdHbLI4dAH
name: pc name: pc
type: gadget type: gadget
- -
default-channel: 25.10/stable components:
nvidia-580-uda-ko:
presence: optional
nvidia-580-uda-user:
presence: optional
default-channel: 26.04/stable
id: pYVQrBcKmBa0mZ4CCN7ExT6jH8rY1hza id: pYVQrBcKmBa0mZ4CCN7ExT6jH8rY1hza
name: pc-kernel name: pc-kernel
type: kernel type: kernel
@ -40,55 +45,60 @@ snaps:
name: bare name: bare
type: base type: base
- -
default-channel: 1/stable/ubuntu-25.10 default-channel: latest/stable/ubuntu-26.04
id: HyhSEBPv3vHsW6uOHkQR384NgI7S6zpj
name: mesa-2404
type: app
-
default-channel: 1/stable/ubuntu-26.04
id: EI0D1KHjP8XiwMZKqSjuh6W8zvcowUVP id: EI0D1KHjP8XiwMZKqSjuh6W8zvcowUVP
name: firmware-updater name: firmware-updater
type: app type: app
- -
default-channel: 1/stable/ubuntu-25.10 default-channel: 1/stable/ubuntu-26.04
id: FppXWunWzuRT2NUT9CwoBPNJNZBYOCk0 id: FppXWunWzuRT2NUT9CwoBPNJNZBYOCk0
name: desktop-security-center name: desktop-security-center
type: app type: app
- -
default-channel: 1/stable/ubuntu-25.10 default-channel: 1/stable/ubuntu-26.04
id: aoc5lfC8aUd2VL8VpvynUJJhGXp5K6Dj id: aoc5lfC8aUd2VL8VpvynUJJhGXp5K6Dj
name: prompting-client name: prompting-client
type: app type: app
- -
default-channel: 2/stable/ubuntu-25.10 default-channel: 2/stable/ubuntu-26.04
id: gjf3IPXoRiipCu9K0kVu52f0H56fIksg id: gjf3IPXoRiipCu9K0kVu52f0H56fIksg
name: snap-store name: snap-store
type: app type: app
- -
default-channel: latest/stable/ubuntu-25.10 default-channel: latest/stable/ubuntu-26.04
id: jZLfBRzf1cYlYysIjD2bwSzNtngY0qit id: jZLfBRzf1cYlYysIjD2bwSzNtngY0qit
name: gtk-common-themes name: gtk-common-themes
type: app type: app
- -
default-channel: latest/stable/ubuntu-25.10 default-channel: latest/stable/ubuntu-26.04
id: 3wdHCAVyZEmYsCMFDE9qt92UV8rC8Wdk id: 3wdHCAVyZEmYsCMFDE9qt92UV8rC8Wdk
name: firefox name: firefox
type: app type: app
- -
default-channel: latest/stable/ubuntu-25.10 default-channel: latest/stable/ubuntu-26.04
id: lATO8HzwVvrAPrlZRAWpfyrJKlAJrZS3 id: ew7OxpbRTxfK7ImpIygRR85lkxvU7Pzt
name: gnome-42-2204 name: gnome-46-2404
type: app type: app
- -
default-channel: latest/stable/ubuntu-25.10 default-channel: latest/stable/ubuntu-26.04
id: IrwRHakqtzhFRHJOOPxKVPU0Kk7Erhcu id: IrwRHakqtzhFRHJOOPxKVPU0Kk7Erhcu
name: snapd-desktop-integration name: snapd-desktop-integration
type: app type: app
timestamp: 2025-08-06T12:00:00.0Z timestamp: 2025-12-09T12:00:00.0Z
sign-key-sha3-384: 9tydnLa6MTJ-jaQTFUXEwHl1yRx7ZS4K5cyFDhYDcPzhS7uyEkDxdUjg9g08BtNn sign-key-sha3-384: 9tydnLa6MTJ-jaQTFUXEwHl1yRx7ZS4K5cyFDhYDcPzhS7uyEkDxdUjg9g08BtNn
AcLBXAQAAQoABgUCaJuDnwAKCRDgT5vottzAEqjkD/4+SAjC0APhGmSh73ewaUe57Nbs4qDfrUJZ AcLBXAQAAQoABgUCaYzP9QAKCRDgT5vottzAEus2D/4jJVutpoPmDrLjNQLn2KNf/f1L2zU8ESSe
P8aaq9UO3siLmp1og/3OwbuMhddmqxvurYqe0jby6Qbv7NbCcI7YZvn4wDxnessWeYJnOd1Le8UC VpFjy+9Ff7AxXckALM4eEy/J5mc+UNhHQ/7Thp4XYy2NiH14n9Lv5kVqZCz8udiEfcfLy5gGveio
cOmhckh7WZzQreR2SjIRq9ElGGI4RIft4Ex6H2EYG3A9EpTdwfZ1jmnwUlw6qqPEjXJv/PKtcpm0 oXyGX7J5x9sq3YXV1IHS84aqJS0si80TTLCRQXUN8oUZIVRkgFOGIVVneQkn1ppNs87kNgvBT1ow
qrWetewWMyZF5JmoZUyCWd4xP0y3VpbrTCHvBl5C/Q63xy/KELSGYAesgBfcO1bIP/NbVWsHbvTd nwr9fVvZnt5bTprCxs4R5cEUlWTJMN4l96Eh530Q+wqCjFxbTs6FADUYielsFnBDl/Q1M0fozg4F
qCew5lTQx7tUIu4mnBuvt1bZ6U3jbXUu15g1EgJwsft2ker5bX8GbsJEBnz7TBgdYmrRb3DCthZ7 Ct4gBbvFGWZhp8LXiCbJvTd3PAAV1HYAgtKDKZT0NQp8qaU5DpgTDiUzIjaAJP7feSU5AYDLuVSH
tiZT2XWmOSiwMK++5BNnJkActkkCXFRVzL/f2ofUOJ96bLxk3qlPxh6bkpqUkkSkygC46123MzOZ V3zD8sosg1nmPvVtuSi2q5Z+/zd6gmG+vLn5d16whNqELDnX0O9Hxarc/3DD3ANZrrbXlq/PEJNB
dRehpGuqsZ3VrLZx8CrrwIb0nZZFeR0ZH8y1gL2uXcRPQDSOPFgwRrTLo+NzVLfdUyraRZaU50Y+ Lor5osHLN4utW7CUC5MIEQ5/Z/6cSuav6rQ+bBiAOzQSHRCbhfyCGSMMINX2CE3ePw3moi9gwXeh
xRzh0nT8GSFDY9QeZZwq83UDsUNILZxRBmnD7fidYDFHRNzqLPH+xYAuAakX6zGlA2foOpC1CbIs vKw1iItEOxywEKbeBNEvddnGsvmzoqf9Jg53/X0yrQQVZTHYFsQlTRk9ggajdZnPjJMTqlAqjXnP
LUAhSzFdKJKBGucG3EumAggOCWqggc6RxY3c4eXDbrESE1RG1ndKINScNf+l/cYAwZSa6q9HKx3R QCsgnprvln0akW4IfEzc+IgoF5eiShJd4IidkBbbdNXRRYlHfmOG7ZvR9upJwe1M73Zfu1nQFEvT
w7sGOLj+FzSGWqBaljjQeBd1jk3udS34yaDEtLlcxQ== fly59e2Vw8O50ljOVW3jT5fW36z8h1+ttxkKwVsQJg==