mirror of
https://git.launchpad.net/livecd-rootfs
synced 2026-04-01 14:41:05 +00:00
Compare commits
164 Commits
25.10.18
...
ubuntu/mas
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1975bbd52b | ||
|
|
916b693130 | ||
|
|
a5ed2589f6 | ||
|
|
2a5f5e74dd | ||
|
|
cbb597a46c | ||
|
|
7252138aa9 | ||
|
|
5e4e502c76 | ||
|
|
bf2eb46fc8 | ||
|
|
f45429018c | ||
|
|
dad4a04751 | ||
|
|
6e25a58c56 | ||
|
|
2b2c057acf | ||
|
|
cdd2b7fb01 | ||
|
|
e9d67eecd6 | ||
|
|
6175dfb3ac | ||
|
|
0e292ea3f2 | ||
|
|
7620f798f4 | ||
|
|
04e2aea648 | ||
|
|
9861d393aa | ||
|
|
0b30131aae | ||
|
|
301cf9622c | ||
|
|
a3c8532764 | ||
|
|
92e680cc2c | ||
|
|
b1c61d8bf1 | ||
|
|
9c4ce17909 | ||
|
|
b1ca2ae69b | ||
|
|
31a0c2716c | ||
|
|
a66fbc54b7 | ||
|
|
9819eae23c | ||
|
|
b135edf10c | ||
|
|
112153325c | ||
|
|
39ab3ea113 | ||
|
|
a6ac43fcda | ||
|
|
92c29ecbf2 | ||
|
|
7fdf181234 | ||
|
|
49a0415299 | ||
|
|
96f41a4cf5 | ||
|
|
2579dc30cb | ||
|
|
77db814700 | ||
|
|
a1c8d7f3bd | ||
|
|
f734d8cb8c | ||
|
|
7e3c74afac | ||
|
|
7c4588191c | ||
|
|
42df11d4cc | ||
|
|
6db44c1ea6 | ||
|
|
081981e650 | ||
|
|
a2a166d93d | ||
|
|
a6466ab0a3 | ||
|
|
a5cffa8414 | ||
|
|
ff3addb2f8 | ||
|
|
460037fb4d | ||
|
|
b70abe0d1f | ||
|
|
166e83f5d8 | ||
|
|
5c6a3fba53 | ||
|
|
edc2fa0fc5 | ||
|
|
516d8b8913 | ||
|
|
edf0acbeac | ||
|
|
6a6b00d68b | ||
|
|
e4b17221a0 | ||
|
|
435c4e1895 | ||
|
|
0733a7626a | ||
|
|
f0c7c28618 | ||
|
|
3645bdf230 | ||
|
|
c3671c739d | ||
|
|
733ad14e33 | ||
|
|
01d94b279c | ||
|
|
e26de340e2 | ||
|
|
7f1c505f20 | ||
|
|
6d954c975d | ||
|
|
73035c0b19 | ||
|
|
84760de4da | ||
|
|
2c2f7d5e5c | ||
|
|
45aa1e4550 | ||
|
|
c1edc22c24 | ||
|
|
9add6d4ab8 | ||
|
|
acd63ee3e4 | ||
|
|
ab2b82e3c2 | ||
|
|
9a9ca07a76 | ||
|
|
4d8cfd89b8 | ||
|
|
ce809612c4 | ||
|
|
b3fdc4e615 | ||
|
|
3112c5f175 | ||
|
|
8e26b08f59 | ||
|
|
7cbabf55d5 | ||
|
|
ddbf8bf828 | ||
|
|
74f5986230 | ||
|
|
563d142029 | ||
|
|
755f0b0d15 | ||
|
|
d756afd205 | ||
|
|
9c5d326e56 | ||
|
|
383a1206cc | ||
|
|
2f918331fb | ||
|
|
e6558e2541 | ||
|
|
01c80d8d0a | ||
|
|
72511a0381 | ||
|
|
c147c15291 | ||
|
|
856f14edee | ||
|
|
caf4f1030a | ||
|
|
49e1ab15cd | ||
|
|
5cbea9f677 | ||
|
|
a19f30b9d6 | ||
|
|
44c14b799f | ||
|
|
9fdbaf8d6d | ||
|
|
ae1e5005aa | ||
|
|
c327ab7bd7 | ||
|
|
ab943acf44 | ||
|
|
827d87bd7f | ||
|
|
562e589cd1 | ||
|
|
65dad6ccc0 | ||
|
|
0fc035c8ba | ||
|
|
e5ef47f7dd | ||
|
|
69ee041674 | ||
|
|
e78505a5f3 | ||
|
|
544aa0299e | ||
|
|
98c75ef41b | ||
|
|
5e00e3ecb2 | ||
|
|
d280d58a7d | ||
|
|
e2c8b4b1ad | ||
|
|
b3ddf6a78a | ||
|
|
218ad9af27 | ||
|
|
befd8ddadf | ||
|
|
21372df0f9 | ||
|
|
001aed3b3b | ||
|
|
9164e58d83 | ||
|
|
18c3471930 | ||
|
|
466d9683c5 | ||
|
|
ec74e75597 | ||
|
|
432785a2fe | ||
|
|
596d6d8464 | ||
|
|
1fca197379 | ||
|
|
f6e3d2aedd | ||
|
|
918bc13a3a | ||
|
|
92471dcb76 | ||
|
|
a53da7e27b | ||
|
|
922faa0d12 | ||
|
|
e0f4fd8109 | ||
|
|
78d502951b | ||
|
|
287bf91450 | ||
|
|
ff6b3824d8 | ||
|
|
ddff3faba3 | ||
|
|
9172378dae | ||
|
|
e35eb63edc | ||
|
|
82a239e39c | ||
|
|
b8520530c9 | ||
|
|
7957008902 | ||
|
|
4b4d3de818 | ||
|
|
355f6d5b26 | ||
|
|
39e1066593 | ||
|
|
12545fb878 | ||
|
|
f7e0f39a1d | ||
|
|
f7ed2d271e | ||
|
|
ae0be803f1 | ||
|
|
ab658bce7d | ||
|
|
1501b3776c | ||
|
|
8de7b2eb10 | ||
|
|
2fd6cb1609 | ||
|
|
6dbce04781 | ||
|
|
785c4c53d3 | ||
|
|
762108eaef | ||
|
|
fddd7b7595 | ||
|
|
2aeaaf1815 | ||
|
|
ecaaf04844 | ||
|
|
eda621a927 | ||
|
|
12c92b2cbf |
12
.launchpad.yaml
Normal file
12
.launchpad.yaml
Normal file
@ -0,0 +1,12 @@
|
||||
pipeline:
|
||||
- [lint]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
series: noble
|
||||
architectures: amd64
|
||||
packages:
|
||||
- black
|
||||
- mypy
|
||||
- python3-flake8
|
||||
run: ./check-lint
|
||||
263
README.parameters
Normal file
263
README.parameters
Normal file
@ -0,0 +1,263 @@
|
||||
Understanding the parameters used by livecd-rootfs
|
||||
==================================================
|
||||
|
||||
livecd-rootfs is a confusing codebase. One of the confusing things is
|
||||
how information flows into and around the image build process. There
|
||||
is IMAGEFORMAT and IMAGE_TARGETS and PROJECT and many other
|
||||
variables. It is not obvious when looking at the code if a given
|
||||
variable is something passed as a parameter or something derived from
|
||||
it.
|
||||
|
||||
All (or almost all) production use of livecd-rootfs is via
|
||||
launchpad-buildd so the set of potential parameters is limited by the
|
||||
set of environment variables launchpad-build can set in response to
|
||||
the build request.
|
||||
|
||||
The process from build request to environment live-build is run is a
|
||||
little convoluted. The build request takes:
|
||||
|
||||
an archive -- where to get livecd-rootfs from
|
||||
a distro_arch_series -- the series to get livecd-rootfs and build
|
||||
a pocket -- pocket to get livecd-rootfs from, also influences if proposed is
|
||||
used as a package source for the image being built
|
||||
unique_key -- you cannot have more than one pending livefs build with the same
|
||||
unique_key. does not affect the build at all.
|
||||
version -- optional version string, see below. often a serial like 20250525.1
|
||||
metadata_override -- combined with the metadata on the livefs itself to make
|
||||
the metadata for this build.
|
||||
|
||||
(ref: https://launchpad.net/+apidoc/devel.html#livefs-requestBuild)
|
||||
|
||||
These parameters are stored on the livefsbuild object (ref:
|
||||
https://git.launchpad.net/launchpad/tree/lib/lp/soyuz/model/livefsbuild.py#n372)
|
||||
and converted into a set of args passed to launchpad-build by the
|
||||
LiveFSBuildBehaviour class (ref:
|
||||
https://git.launchpad.net/launchpad/tree/lib/lp/soyuz/model/livefsbuildbehaviour.py#n99).
|
||||
|
||||
Inside launchpad-build, these arguments are inspected by the
|
||||
LiveFilesystemBuildManager.initiate method (ref:
|
||||
https://git.launchpad.net/launchpad-buildd/tree/lpbuildd/livefs.py#n24)
|
||||
which turns them into arguments for the BuildLiveFS lpbuild
|
||||
"operation" which is what creates the environment live-build runs in
|
||||
(ref:
|
||||
https://git.launchpad.net/launchpad-buildd/tree/lpbuildd/target/build_livefs.py#n167).
|
||||
|
||||
These variables can be set for both lb config and lb build:
|
||||
|
||||
PROJECT (mandatory, comes from "project" in the metadata)
|
||||
ARCH (set to the abi tag of the distroarchseries being built for)
|
||||
ARCH_VARIANT (set to the isa tag of the distroarchseries being built for if it is
|
||||
different from the abi tag)
|
||||
SUBPROJECT (optional, comes from "subproject" in the metadata)
|
||||
SUBARCH (optional, comes from "subarch" in the metadata)
|
||||
CHANNEL (optional, comes from "subarch" in the metadata)
|
||||
IMAGE_TARGETS (optional, comes from "image_targets" in the metadata
|
||||
"image_targets" is a list. IMAGE_TARGETS is set to " ".join(image_targets))
|
||||
REPO_SNAPSHOT_STAMP
|
||||
(optional, comes from "repo_snapshot_stamp" in the metadata)
|
||||
SNAPSHOT_SERVICE_TIMESTAMP
|
||||
(optional, comes from "snapshot_snapshot_stamp" in the metadata)
|
||||
COHORT_KEY
|
||||
(optional, comes from "cohort-key" in the metadata)
|
||||
|
||||
launchpad-buildd also contains code to set http_proxy / HTTP_PROXY /
|
||||
LB_APT_HTTP_PROXY but there does not appear to be any way to trigger
|
||||
this when requesting a build.
|
||||
|
||||
In addition the following variables can be set for lb config only (why
|
||||
are some things set for lb config only? no idea):
|
||||
|
||||
SUITE (set to the name of the distroarchseries being built for)
|
||||
NOW (set to value of the 'version' argument to the build request,
|
||||
defaults to strftime("%Y%m%d-%H%M%S"))
|
||||
IMAGEFORMAT (optional, comes from "image_format" in the metadata)
|
||||
PROPOSED (set to "1" if the pocket passed to the build request is proposed)
|
||||
EXTRA_PPAS (optional, comes from "extra_ppas" in the metadata
|
||||
"extra_ppas" is a list. EXTRA_PPAS is set to " ".join(extra_ppas))
|
||||
EXTRA_SNAPS (optional, comes from "extra_snaps" in the metadata
|
||||
"extra_snaps" is a list. EXTRA_SNAPS is set to " ".join(extra_snaps))
|
||||
BUILD_TYPE (optional, the "type" (i.e. Daily or Release) of ISO being built,
|
||||
goes into .disk/info on the ISO, defaults to Daily)
|
||||
|
||||
Here is an opinionated and slightly angry attempt to describe what
|
||||
each of these is for:
|
||||
|
||||
PROJECT
|
||||
-------
|
||||
|
||||
This is the big one, the main variable that defines what is being
|
||||
built. It can be ubuntu, ubuntu-server, xubuntu, ubuntu-mini-iso, that
|
||||
sort of thing. Generally PROJECT determines the set of packages
|
||||
installed but it (unfortunately?) has a bit more impact than that.
|
||||
|
||||
It's unarguable that we need a parameter like this.
|
||||
|
||||
ARCH
|
||||
----
|
||||
|
||||
The architecture being built for. This is always the same as `dpkg
|
||||
--print-architecture` for us, we don't do any cross builds.
|
||||
|
||||
It's kind of redundant but it's not really a problem that this exists.
|
||||
|
||||
ARCH_VARIANT
|
||||
------------
|
||||
|
||||
The "variant" being built for, i.e. the ISA tag of the
|
||||
distroarchseries. Only set if this is different from the ABI tag.
|
||||
|
||||
This is definitely needed to be able to build images for variants.
|
||||
|
||||
SUBPROJECT
|
||||
----------
|
||||
|
||||
This is used for some builds to build a different sort of build of the
|
||||
project. It can be set to:
|
||||
|
||||
* "minimized" for ubuntu-cpc builds to make a minimal cloud image
|
||||
* "minimal" for xubuntu builds to make a smaller ISO
|
||||
* "desktop-preinstalled" for ubuntu builds to make a preinstalled
|
||||
image instead of the parts for an installer.
|
||||
* "buildd" for images to be used as build images by craft tools, and also
|
||||
buildd chroots used on launchpad builders?
|
||||
* "live" for ubuntu-server builds, historically to distinguish d-i
|
||||
style installers from subiquity style installers
|
||||
* "desktop" for ubuntu-core-installer builds, to influence which
|
||||
model is use to build the ubuntu core system that will be
|
||||
installed.
|
||||
|
||||
_This_ parameter is a total mess. The desktop-preinstalled use feels
|
||||
particularly egregious.
|
||||
|
||||
SUBARCH
|
||||
-------
|
||||
|
||||
This identifies the target machine more specifically than ARCH,
|
||||
e.g. "tegra-jetson" or "licheerv". Used mostly but not exclusively for
|
||||
preinstalled builds.
|
||||
|
||||
We probably do need something like this.
|
||||
|
||||
CHANNEL
|
||||
-------
|
||||
|
||||
Influences which channel snaps included in the build are taken from
|
||||
(via a few different mechanisms).
|
||||
|
||||
IMAGE_TARGETS
|
||||
-------------
|
||||
|
||||
Passed for CPC (and ubuntu-oem, for some reason) builds to
|
||||
`config/hooks.d/make-hooks` which uses it to select which binary hooks
|
||||
to run (and so determines which artifacts get produced).
|
||||
|
||||
It is probably reasonable that this exists.
|
||||
|
||||
REPO_SNAPSHOT_STAMP
|
||||
-------------------
|
||||
|
||||
Currently unused.
|
||||
|
||||
SNAPSHOT_SERVICE_TIMESTAMP
|
||||
--------------------------
|
||||
|
||||
Also currently unused, and unclear how it differs from
|
||||
REPO_SNAPSHOT_STAMP.
|
||||
|
||||
COHORT_KEY
|
||||
----------
|
||||
|
||||
Used to make sure that different builds run at the same time don't get
|
||||
different versions of snaps due to phasing differences.
|
||||
|
||||
This is a totally valid thing to need to supply.
|
||||
|
||||
http_proxy / HTTP_PROXY / LB_APT_HTTP_PROXY
|
||||
-------------------------------------------
|
||||
|
||||
Nothing complex here!
|
||||
|
||||
SUITE
|
||||
-----
|
||||
|
||||
This is the series being built (e.g. noble, questing). It is misnamed
|
||||
really -- a suite is usually a combination of a series and a pocket
|
||||
(noble-proposed, questing-security).
|
||||
|
||||
As with ARCH this is sort of redundant as we do builds in a chroot of
|
||||
the series being built but OTOH it is definitely information the
|
||||
build needs to know!
|
||||
|
||||
NOW
|
||||
---
|
||||
|
||||
The serial for the build, e.g. 20250519 or 20240418.4.
|
||||
|
||||
It is a totally reasonable parameter.
|
||||
|
||||
IMAGEFORMAT
|
||||
-----------
|
||||
|
||||
This is one of the more incoherently handled parameters. In rough
|
||||
outline it is the filesystem of the image we produce.
|
||||
|
||||
Installer builds do not produce raw images, so this ends up being set
|
||||
to 'plain' (which causes live-build to just leave the rootfs as a
|
||||
directory tree) or 'none' (which causes live-build to do roughly the
|
||||
same thing but in a different way?).
|
||||
|
||||
Image builds that use ubuntu-image set it to "ubuntu-image". These
|
||||
builds do not call 'lb build' or 'lb binary'.
|
||||
|
||||
Other preinstalled images (mostly cpc images) set it to ext4 (but then
|
||||
use live-build/ubuntu-cpc/hooks.d/remove-implicit-artifacts to remove
|
||||
the output file that this causes live-build to produce...). Some
|
||||
projects rely on this being set via metadata when building the project
|
||||
it seems.
|
||||
|
||||
It can be set when starting an image build, but most builds do not and
|
||||
the behavior when it is not set explicitly is pretty confusing.
|
||||
|
||||
This place is not a place of honor.
|
||||
|
||||
PROPOSED
|
||||
--------
|
||||
|
||||
Should packages from proposed by included?
|
||||
|
||||
This is not really as useful as it used to be for a bunch of reasons
|
||||
but it conceptually makes sense.
|
||||
|
||||
EXTRA_PPAS
|
||||
----------
|
||||
|
||||
Extra archives to get packages from.
|
||||
|
||||
This is a space separated list by the time it gets to
|
||||
livecd-rootfs. Each element of the list is of the form USER/NAME[:PIN]
|
||||
where user is a Launchpad user/team name, NAME is the name of the ppa
|
||||
to add and the optional colon-PIN at the end is the value to pin (in
|
||||
the "man 5 apt_preferences: sense) packages from this PPA at.
|
||||
|
||||
Production builds shouldn't really use this but it's definitely useful
|
||||
for development.
|
||||
|
||||
EXTRA_SNAPS
|
||||
-----------
|
||||
|
||||
Extra snaps to include (but only for ubuntu-image based builds).
|
||||
|
||||
BUILD_TYPE
|
||||
----------
|
||||
|
||||
Before release, the .disk/info on an ISO looks like:
|
||||
|
||||
Ubuntu-Server 26.04 LTS "Resolute Raccoon" - Daily amd64 (20260210)
|
||||
|
||||
after release it looks like:
|
||||
|
||||
Ubuntu-Server 26.04 LTS "Resolute Raccoon" - Release amd64 (20270210)
|
||||
|
||||
We could do a livecd-rootfs upload to change this (it only changes
|
||||
once per cycle), but it's quicker and easier to manage this from the
|
||||
code that triggers the livefs builds.
|
||||
11
check-lint
Executable file
11
check-lint
Executable file
@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -eux
|
||||
|
||||
export MYPYPATH=live-build
|
||||
mypy live-build/isobuilder live-build/isobuild
|
||||
mypy live-build/gen-iso-ids
|
||||
|
||||
black --check live-build/isobuilder live-build/isobuild live-build/gen-iso-ids
|
||||
|
||||
python3 -m flake8 --max-line-length 88 --ignore E203 live-build/isobuilder live-build/isobuild live-build/gen-iso-ids
|
||||
301
debian/changelog
vendored
301
debian/changelog
vendored
@ -1,3 +1,304 @@
|
||||
livecd-rootfs (26.04.26) resolute; urgency=medium
|
||||
|
||||
* Ensure snapd tracks stable and not edge anymore.
|
||||
We did remove it from multiple places, but this one was left and as a
|
||||
consequence, the latest iso was still having snapd edge.
|
||||
|
||||
-- Didier Roche-Tolomelli <didrocks@ubuntu.com> Fri, 27 Mar 2026 15:31:21 +0100
|
||||
|
||||
livecd-rootfs (26.04.25) resolute; urgency=medium
|
||||
|
||||
* bake LIVECD_ROOTFS_ROOT into config/functions, fixing some build failures
|
||||
(for at least ubuntu and some ubuntu-cpc configurations).
|
||||
|
||||
-- Michael Hudson-Doyle <michael.hudson@ubuntu.com> Fri, 20 Mar 2026 06:47:44 +1300
|
||||
|
||||
livecd-rootfs (26.04.24) resolute; urgency=medium
|
||||
|
||||
[ Allen Abraham ]
|
||||
* Added a hook to produce a working minimal Ubuntu image using imagecraft
|
||||
|
||||
[ Michael Hudson-Doyle ]
|
||||
* Various quality of life improvements for hacking on livecd-rootfs:
|
||||
- Add a "ubuntu-test-iso" project that builds a not very useful ISO in 2-5 minutes.
|
||||
- Add a build-livefs script that takes care of copying the auto scripts and
|
||||
invoking lb clean/config/build with the right environment.
|
||||
- Add a build-livefs-lxd script to run the above script in a lxd vm.
|
||||
|
||||
-- Michael Hudson-Doyle <michael.hudson@ubuntu.com> Mon, 16 Mar 2026 11:05:13 +1300
|
||||
|
||||
livecd-rootfs (26.04.23) resolute; urgency=medium
|
||||
|
||||
[ Tobias Heider ]
|
||||
* Fix ISO builds when KERNEL_FLAVOUR != generic.
|
||||
|
||||
-- Michael Hudson-Doyle <michael.hudson@ubuntu.com> Mon, 02 Mar 2026 10:51:47 +1300
|
||||
|
||||
livecd-rootfs (26.04.22) resolute; urgency=medium
|
||||
|
||||
[ Oliver Gayot ]
|
||||
* Pull the model from Launchpad's lp:canonical-models
|
||||
repo, instead of having it uploaded as part of livecd-rootfs. This
|
||||
indirection makes it possible to update the models without requiring a new
|
||||
upload of livecd-rootfs every time.
|
||||
|
||||
[ Michael Hudson-Doyle ]
|
||||
* Fix two more problems with livefs-built ISOs:
|
||||
- Generate the for-iso squashfs in the right place for Kubuntu.
|
||||
- Fix confusion about the kernel path on the ISO on riscv64.
|
||||
|
||||
[ Tobias Heider ]
|
||||
* Fix pool generation when using extra_ppas.
|
||||
|
||||
-- Michael Hudson-Doyle <michael.hudson@ubuntu.com> Thu, 26 Feb 2026 10:56:42 +1300
|
||||
|
||||
livecd-rootfs (26.04.21) resolute; urgency=medium
|
||||
|
||||
[ Dan Bungert ]
|
||||
* Update new signed models to ship latest nvidia drivers for ubuntu hybrid.
|
||||
|
||||
-- Didier Roche-Tolomelli <didrocks@ubuntu.com> Wed, 25 Feb 2026 08:38:32 +0100
|
||||
|
||||
livecd-rootfs (26.04.20) resolute; urgency=medium
|
||||
|
||||
[ Michael Raymond ]
|
||||
* Bug-fix: Only use main archive keyring when building with debootstrap
|
||||
so EOL release signatures can be verified after EOL.
|
||||
|
||||
[ Allen Abraham ]
|
||||
* Make SBOM generation optional in create_manifest function.
|
||||
|
||||
[ Michael Hudson-Doyle ]
|
||||
* 030-ubuntu-live-system-seed.binary: do not run if there is no layer to
|
||||
install the system, in particular on arm64.
|
||||
* Fix some path confusion in the new isobuilder.boot package and refactor
|
||||
grub config generation to be more string based.
|
||||
|
||||
-- Michael Hudson-Doyle <michael.hudson@ubuntu.com> Fri, 20 Feb 2026 12:45:41 +1300
|
||||
|
||||
livecd-rootfs (26.04.19) resolute; urgency=medium
|
||||
|
||||
* Translate the debian-cd tools/boot/$series/boot-$arch scripts to Python
|
||||
and use that to make ISOs bootable rather than cloning debian-cd.
|
||||
|
||||
-- Michael Hudson-Doyle <michael.hudson@ubuntu.com> Tue, 17 Feb 2026 11:16:43 +1300
|
||||
|
||||
livecd-rootfs (26.04.18) resolute; urgency=medium
|
||||
|
||||
[ Michael Hudson-Doyle ]
|
||||
* document ARCH_VARIANT and BUILD_TYPE in README.parameters
|
||||
* isobuilder: pass ignore_dangling_symlinks=True when copying apt config
|
||||
|
||||
-- Utkarsh Gupta <utkarsh@ubuntu.com> Mon, 16 Feb 2026 16:14:03 +0530
|
||||
|
||||
livecd-rootfs (26.04.17) resolute; urgency=medium
|
||||
|
||||
* desktop: build the stable ISO using the stable model - essentially
|
||||
reverting all the hacks.
|
||||
* desktop: update the stable model to the latest. It has:
|
||||
- components defined for the 6.19 kernel (nvidia 580 series)
|
||||
- no core26: for TPM/FDE recovery testing, please install the core26 snap
|
||||
from edge.
|
||||
|
||||
-- Olivier Gayot <olivier.gayot@canonical.com> Thu, 12 Feb 2026 10:25:15 +0100
|
||||
|
||||
livecd-rootfs (26.04.16) resolute; urgency=medium
|
||||
|
||||
* Rename ISO_STATUS to BUILD_TYPE for image builds.
|
||||
|
||||
-- Utkarsh Gupta <utkarsh@debian.org> Thu, 12 Feb 2026 01:41:11 +0530
|
||||
|
||||
livecd-rootfs (26.04.15) resolute; urgency=medium
|
||||
|
||||
[ Matthew Hagemann ]
|
||||
* desktop: delay display manager starting until snapd seeding completes
|
||||
|
||||
[ Michael Hudson-Doyle ]
|
||||
* Make an ISO in the livefs build when building an installer.
|
||||
|
||||
-- Michael Hudson-Doyle <michael.hudson@ubuntu.com> Wed, 11 Feb 2026 10:04:37 +1300
|
||||
|
||||
livecd-rootfs (26.04.14) resolute; urgency=medium
|
||||
|
||||
[ Olivier Gayot ]
|
||||
* desktop: build stable image with snapd from beta. Snapd 2.74 has just been
|
||||
uploaded to beta. Let's stop using the version declared in the dangerous model.
|
||||
|
||||
[ Didier Roche-Tolomelli ]
|
||||
* desktop: add (commented out) config to force reexecution of snapd snap version
|
||||
|
||||
-- Olivier Gayot <olivier.gayot@canonical.com> Thu, 22 Jan 2026 10:13:36 +0100
|
||||
|
||||
livecd-rootfs (26.04.13) resolute; urgency=medium
|
||||
|
||||
* Bootstrap and install variant packages if ARCH_VARIANT is set.
|
||||
|
||||
-- Michael Hudson-Doyle <michael.hudson@ubuntu.com> Tue, 06 Jan 2026 22:03:15 +1300
|
||||
|
||||
livecd-rootfs (26.04.12) resolute; urgency=medium
|
||||
|
||||
* desktop: add variables pointing to the different models (stable & dangerous).
|
||||
* desktop: fix snap components taken from original model when overriding a
|
||||
snap with another model.
|
||||
- if we decide to override the definition of a snap (i.e., by taking in
|
||||
from a different model), we also need to override the definition of its
|
||||
components.
|
||||
* desktop: refactor how we filter the snaps when overriding
|
||||
* desktop: update the dangerous model so that it includes core26 and the 6.17
|
||||
kernel and components.
|
||||
|
||||
-- Olivier Gayot <olivier.gayot@canonical.com> Tue, 16 Dec 2025 14:54:17 +0100
|
||||
|
||||
livecd-rootfs (26.04.11) resolute; urgency=medium
|
||||
|
||||
[ Valentin Haudiquet ]
|
||||
* refactor: added a function to generate grub config for netboot
|
||||
* riscv/server: add grub efi bootloader in netboot tarballs
|
||||
|
||||
[ Olivier Gayot ]
|
||||
* desktop: build with optional components included
|
||||
* desktop: don't build the stable image with pc-kernel from 26.04/beta
|
||||
- This was needed before because there was pc-kernel in 26.04/stable but
|
||||
now there is one and it matches the components definition from the model.
|
||||
|
||||
-- Valentin Haudiquet <valentin.haudiquet@canonical.com> Thu, 11 Dec 2025 09:28:37 +0100
|
||||
|
||||
livecd-rootfs (26.04.10) resolute; urgency=medium
|
||||
|
||||
[ Olivier Gayot ]
|
||||
* desktop: fix a comment typo
|
||||
|
||||
[ Michael Hudson-Doyle ]
|
||||
* Build Ubuntu Server images with the 'restricted' component enabled.
|
||||
|
||||
-- Dan Bungert <daniel.bungert@canonical.com> Tue, 09 Dec 2025 21:07:54 +1300
|
||||
|
||||
livecd-rootfs (26.04.9) resolute; urgency=medium
|
||||
|
||||
* desktop: Add mesa to the hybrid model required by core24 apps.
|
||||
|
||||
-- Didier Roche-Tolomelli <didrocks@ubuntu.com> Wed, 26 Nov 2025 08:51:24 +0100
|
||||
|
||||
livecd-rootfs (26.04.8) resolute; urgency=medium
|
||||
|
||||
* desktop: update TPM/FDE ubuntu model to use a GNOME platform snap
|
||||
compatible with core24.
|
||||
|
||||
-- Didier Roche-Tolomelli <didrocks@ubuntu.com> Tue, 25 Nov 2025 11:38:59 +0100
|
||||
|
||||
livecd-rootfs (26.04.7) resolute; urgency=medium
|
||||
|
||||
* desktop: build both ISOs with snapd, firmware-updater and
|
||||
desktop-security-center from edge for TPM/FDE.
|
||||
|
||||
-- Olivier Gayot <olivier.gayot@canonical.com> Wed, 19 Nov 2025 10:41:17 +0100
|
||||
|
||||
livecd-rootfs (26.04.6) resolute; urgency=medium
|
||||
|
||||
* desktop: build classic ISO with "grade: dangerous" and pull pc-kernel from
|
||||
edge to workaround unavailability of pc-kernel in the stable 26.04 channel.
|
||||
* desktop: refresh models to 26.04
|
||||
|
||||
-- Olivier Gayot <olivier.gayot@canonical.com> Tue, 18 Nov 2025 17:01:47 +0100
|
||||
|
||||
livecd-rootfs (26.04.5) resolute; urgency=medium
|
||||
|
||||
[ Dan Bungert ]
|
||||
* desktop: fix build error when SUBPROJECT is unset
|
||||
|
||||
-- Sebastien Bacher <seb128@ubuntu.com> Mon, 17 Nov 2025 12:05:26 +0100
|
||||
|
||||
livecd-rootfs (26.04.4) resolute; urgency=medium
|
||||
|
||||
* desktop: update dangerous model for 26.04
|
||||
|
||||
-- Dan Bungert <daniel.bungert@canonical.com> Fri, 31 Oct 2025 11:13:53 +0100
|
||||
|
||||
livecd-rootfs (26.04.3) resolute; urgency=medium
|
||||
|
||||
* desktop: use dangerous model for TPMFDE bits until snaps are available on
|
||||
stable channels.
|
||||
* desktop: use snapd from edge.
|
||||
|
||||
-- Dan Bungert <daniel.bungert@canonical.com> Wed, 29 Oct 2025 10:58:00 +0100
|
||||
|
||||
livecd-rootfs (26.04.2) resolute; urgency=medium
|
||||
|
||||
[ Gauthier Jolly ]
|
||||
* ubuntu-cpc:
|
||||
- Use the right specific UUID type for the root filesystem partition.
|
||||
- Set a PARTLABEL (cloudimg-rootfs) on the root filesystem partition.
|
||||
|
||||
-- Gauthier Jolly <gauthier.jolly@canonical.com> Thu, 23 Oct 2025 12:50:07 +1300
|
||||
|
||||
livecd-rootfs (26.04.1) resolute; urgency=medium
|
||||
|
||||
[ Heinrich Schuchardt ]
|
||||
* Remove unused riscv64 SUBARCHs
|
||||
|
||||
[ Chad Smith ]
|
||||
* Refresh cloud-init service override for updated service netcat invocation
|
||||
to cloud-init 25.3. (LP: #2128887)
|
||||
|
||||
-- Chad Smith <chad.smith@canonical.com> Mon, 20 Oct 2025 16:32:36 -0600
|
||||
|
||||
livecd-rootfs (25.10.24) questing; urgency=medium
|
||||
|
||||
[ Chad Smith ]
|
||||
* Limit permissions for /etc/netplan/01-network-manager.yaml to
|
||||
root read-write. (LP: #2119020)
|
||||
|
||||
[ Chloé 'kajiya' Smith ]
|
||||
* Increase CPC disk-image base imagesize to 2.5GB
|
||||
* In the ubuntu-cpc disk-image binary we need to avail of the ever increasing size
|
||||
of packages. 2.2GB is now just a bit too small leading to `No space
|
||||
left on device` errors when the binary hits `grub-install`. This change
|
||||
increases $imagesize to 2.5GB (in the binary as an override, initially
|
||||
implemented in ecaaf0484 by dlalaj). Also now run `df` just after the
|
||||
grub-pc && grub2-common installs to make for easier debugging in the future.
|
||||
(LP: #2115811)
|
||||
|
||||
-- Dan Bungert <daniel.bungert@canonical.com> Fri, 19 Sep 2025 13:47:20 -0600
|
||||
|
||||
livecd-rootfs (25.10.23) questing; urgency=medium
|
||||
|
||||
* Add 6.17 kernel apparmor features' preseeds.
|
||||
|
||||
-- Thomas Bechtold <thomasbechtold@jpberlin.de> Thu, 18 Sep 2025 13:29:42 +0200
|
||||
|
||||
livecd-rootfs (25.10.22) questing; urgency=medium
|
||||
|
||||
* Disable apparmor_restrict_unprivileged_userns in the live layers.
|
||||
(LP: #2122675)
|
||||
|
||||
-- Michael Hudson-Doyle <michael.hudson@ubuntu.com> Tue, 16 Sep 2025 08:51:02 +1200
|
||||
|
||||
livecd-rootfs (25.10.21) questing; urgency=medium
|
||||
|
||||
* Fix daily-dangerous builds:
|
||||
- Copy hooks.
|
||||
- Mangle the channel of seeded snaps to use the edge risk of whichever
|
||||
track they are taken from.
|
||||
- Update the dangerous model to reference tracks that actually exist.
|
||||
- Include providers of content plugs when seeding snaps and creating
|
||||
TPMFDE system.
|
||||
- Do not attempt to build an UEFI boot image or hyperv desktop image for
|
||||
this project/subproject combination.
|
||||
|
||||
-- Michael Hudson-Doyle <michael.hudson@ubuntu.com> Mon, 15 Sep 2025 12:16:08 +1200
|
||||
|
||||
livecd-rootfs (25.10.20) questing; urgency=medium
|
||||
|
||||
* edubuntu: use dracut
|
||||
|
||||
-- Dan Bungert <daniel.bungert@canonical.com> Wed, 10 Sep 2025 17:15:45 -0600
|
||||
|
||||
livecd-rootfs (25.10.19) questing; urgency=medium
|
||||
|
||||
* Increase default image size for buildd.
|
||||
|
||||
-- Denis Lalaj <denis.lalaj@canonical.com> Tue, 09 Sep 2025 16:53:52 -0600
|
||||
|
||||
livecd-rootfs (25.10.18) questing; urgency=medium
|
||||
|
||||
* desktop: use dracut
|
||||
|
||||
2
debian/control
vendored
2
debian/control
vendored
@ -25,6 +25,7 @@ Depends: ${misc:Depends},
|
||||
git,
|
||||
gnupg,
|
||||
grep-dctrl,
|
||||
jq,
|
||||
kpartx,
|
||||
live-build (>= 3.0~a57-1ubuntu31~),
|
||||
lsb-release,
|
||||
@ -36,6 +37,7 @@ Depends: ${misc:Depends},
|
||||
procps,
|
||||
python3,
|
||||
python3-apt,
|
||||
python3-click,
|
||||
python3-launchpadlib [!i386],
|
||||
python3-yaml,
|
||||
qemu-utils [!i386],
|
||||
|
||||
1
debian/livecd-rootfs.links
vendored
Normal file
1
debian/livecd-rootfs.links
vendored
Normal file
@ -0,0 +1 @@
|
||||
usr/share/livecd-rootfs/live-build/build-livefs usr/bin/build-livefs
|
||||
1
live-build/apparmor/6.17/capability
Normal file
1
live-build/apparmor/6.17/capability
Normal file
@ -0,0 +1 @@
|
||||
0xffffff
|
||||
1
live-build/apparmor/6.17/caps/extended
Normal file
1
live-build/apparmor/6.17/caps/extended
Normal file
@ -0,0 +1 @@
|
||||
yes
|
||||
1
live-build/apparmor/6.17/caps/mask
Normal file
1
live-build/apparmor/6.17/caps/mask
Normal 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
|
||||
1
live-build/apparmor/6.17/dbus/mask
Normal file
1
live-build/apparmor/6.17/dbus/mask
Normal file
@ -0,0 +1 @@
|
||||
acquire send receive
|
||||
1
live-build/apparmor/6.17/domain/attach_conditions/xattr
Normal file
1
live-build/apparmor/6.17/domain/attach_conditions/xattr
Normal file
@ -0,0 +1 @@
|
||||
yes
|
||||
1
live-build/apparmor/6.17/domain/change_hat
Normal file
1
live-build/apparmor/6.17/domain/change_hat
Normal file
@ -0,0 +1 @@
|
||||
yes
|
||||
1
live-build/apparmor/6.17/domain/change_hatv
Normal file
1
live-build/apparmor/6.17/domain/change_hatv
Normal file
@ -0,0 +1 @@
|
||||
yes
|
||||
1
live-build/apparmor/6.17/domain/change_onexec
Normal file
1
live-build/apparmor/6.17/domain/change_onexec
Normal file
@ -0,0 +1 @@
|
||||
yes
|
||||
1
live-build/apparmor/6.17/domain/change_profile
Normal file
1
live-build/apparmor/6.17/domain/change_profile
Normal file
@ -0,0 +1 @@
|
||||
yes
|
||||
1
live-build/apparmor/6.17/domain/computed_longest_left
Normal file
1
live-build/apparmor/6.17/domain/computed_longest_left
Normal file
@ -0,0 +1 @@
|
||||
yes
|
||||
1
live-build/apparmor/6.17/domain/disconnected.ipc
Normal file
1
live-build/apparmor/6.17/domain/disconnected.ipc
Normal file
@ -0,0 +1 @@
|
||||
yes
|
||||
1
live-build/apparmor/6.17/domain/disconnected.path
Normal file
1
live-build/apparmor/6.17/domain/disconnected.path
Normal file
@ -0,0 +1 @@
|
||||
yes
|
||||
1
live-build/apparmor/6.17/domain/fix_binfmt_elf_mmap
Normal file
1
live-build/apparmor/6.17/domain/fix_binfmt_elf_mmap
Normal file
@ -0,0 +1 @@
|
||||
yes
|
||||
1
live-build/apparmor/6.17/domain/interruptible
Normal file
1
live-build/apparmor/6.17/domain/interruptible
Normal file
@ -0,0 +1 @@
|
||||
yes
|
||||
1
live-build/apparmor/6.17/domain/kill.signal
Normal file
1
live-build/apparmor/6.17/domain/kill.signal
Normal file
@ -0,0 +1 @@
|
||||
yes
|
||||
1
live-build/apparmor/6.17/domain/post_nnp_subset
Normal file
1
live-build/apparmor/6.17/domain/post_nnp_subset
Normal file
@ -0,0 +1 @@
|
||||
yes
|
||||
1
live-build/apparmor/6.17/domain/stack
Normal file
1
live-build/apparmor/6.17/domain/stack
Normal file
@ -0,0 +1 @@
|
||||
yes
|
||||
@ -0,0 +1 @@
|
||||
yes
|
||||
1
live-build/apparmor/6.17/domain/version
Normal file
1
live-build/apparmor/6.17/domain/version
Normal file
@ -0,0 +1 @@
|
||||
1.2
|
||||
1
live-build/apparmor/6.17/file/mask
Normal file
1
live-build/apparmor/6.17/file/mask
Normal file
@ -0,0 +1 @@
|
||||
create read write exec append mmap_exec link lock
|
||||
1
live-build/apparmor/6.17/io_uring/mask
Normal file
1
live-build/apparmor/6.17/io_uring/mask
Normal file
@ -0,0 +1 @@
|
||||
sqpoll override_creds
|
||||
1
live-build/apparmor/6.17/ipc/posix_mqueue
Normal file
1
live-build/apparmor/6.17/ipc/posix_mqueue
Normal file
@ -0,0 +1 @@
|
||||
create read write open delete setattr getattr label
|
||||
1
live-build/apparmor/6.17/mount/mask
Normal file
1
live-build/apparmor/6.17/mount/mask
Normal file
@ -0,0 +1 @@
|
||||
mount umount pivot_root
|
||||
1
live-build/apparmor/6.17/mount/move_mount
Normal file
1
live-build/apparmor/6.17/mount/move_mount
Normal file
@ -0,0 +1 @@
|
||||
detached
|
||||
1
live-build/apparmor/6.17/namespaces/mask
Normal file
1
live-build/apparmor/6.17/namespaces/mask
Normal file
@ -0,0 +1 @@
|
||||
userns_create
|
||||
1
live-build/apparmor/6.17/namespaces/pivot_root
Normal file
1
live-build/apparmor/6.17/namespaces/pivot_root
Normal file
@ -0,0 +1 @@
|
||||
no
|
||||
1
live-build/apparmor/6.17/namespaces/profile
Normal file
1
live-build/apparmor/6.17/namespaces/profile
Normal file
@ -0,0 +1 @@
|
||||
yes
|
||||
1
live-build/apparmor/6.17/namespaces/userns_create
Normal file
1
live-build/apparmor/6.17/namespaces/userns_create
Normal file
@ -0,0 +1 @@
|
||||
pciu&
|
||||
1
live-build/apparmor/6.17/network/af_mask
Normal file
1
live-build/apparmor/6.17/network/af_mask
Normal 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
|
||||
1
live-build/apparmor/6.17/network/af_unix
Normal file
1
live-build/apparmor/6.17/network/af_unix
Normal file
@ -0,0 +1 @@
|
||||
yes
|
||||
1
live-build/apparmor/6.17/network_v8/af_inet
Normal file
1
live-build/apparmor/6.17/network_v8/af_inet
Normal file
@ -0,0 +1 @@
|
||||
yes
|
||||
1
live-build/apparmor/6.17/network_v8/af_mask
Normal file
1
live-build/apparmor/6.17/network_v8/af_mask
Normal 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
|
||||
1
live-build/apparmor/6.17/network_v9/af_mask
Normal file
1
live-build/apparmor/6.17/network_v9/af_mask
Normal 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
|
||||
1
live-build/apparmor/6.17/network_v9/af_unix
Normal file
1
live-build/apparmor/6.17/network_v9/af_unix
Normal file
@ -0,0 +1 @@
|
||||
yes
|
||||
1
live-build/apparmor/6.17/policy/metadata_tagging_version
Normal file
1
live-build/apparmor/6.17/policy/metadata_tagging_version
Normal file
@ -0,0 +1 @@
|
||||
0x000001
|
||||
1
live-build/apparmor/6.17/policy/notify/user
Normal file
1
live-build/apparmor/6.17/policy/notify/user
Normal file
@ -0,0 +1 @@
|
||||
file tags
|
||||
1
live-build/apparmor/6.17/policy/notify_versions/v3
Normal file
1
live-build/apparmor/6.17/policy/notify_versions/v3
Normal file
@ -0,0 +1 @@
|
||||
yes
|
||||
1
live-build/apparmor/6.17/policy/notify_versions/v5
Normal file
1
live-build/apparmor/6.17/policy/notify_versions/v5
Normal file
@ -0,0 +1 @@
|
||||
yes
|
||||
1
live-build/apparmor/6.17/policy/outofband
Normal file
1
live-build/apparmor/6.17/policy/outofband
Normal file
@ -0,0 +1 @@
|
||||
0x000001
|
||||
1
live-build/apparmor/6.17/policy/permstable32
Normal file
1
live-build/apparmor/6.17/policy/permstable32
Normal file
@ -0,0 +1 @@
|
||||
allow deny subtree cond kill complain prompt audit quiet hide xindex tag label
|
||||
1
live-build/apparmor/6.17/policy/permstable32_version
Normal file
1
live-build/apparmor/6.17/policy/permstable32_version
Normal file
@ -0,0 +1 @@
|
||||
0x000003
|
||||
1
live-build/apparmor/6.17/policy/set_load
Normal file
1
live-build/apparmor/6.17/policy/set_load
Normal file
@ -0,0 +1 @@
|
||||
yes
|
||||
1
live-build/apparmor/6.17/policy/state32
Normal file
1
live-build/apparmor/6.17/policy/state32
Normal file
@ -0,0 +1 @@
|
||||
0x000001
|
||||
@ -0,0 +1 @@
|
||||
yes
|
||||
@ -0,0 +1 @@
|
||||
1
|
||||
@ -0,0 +1 @@
|
||||
1
|
||||
1
live-build/apparmor/6.17/policy/versions/v5
Normal file
1
live-build/apparmor/6.17/policy/versions/v5
Normal file
@ -0,0 +1 @@
|
||||
yes
|
||||
1
live-build/apparmor/6.17/policy/versions/v6
Normal file
1
live-build/apparmor/6.17/policy/versions/v6
Normal file
@ -0,0 +1 @@
|
||||
yes
|
||||
1
live-build/apparmor/6.17/policy/versions/v7
Normal file
1
live-build/apparmor/6.17/policy/versions/v7
Normal file
@ -0,0 +1 @@
|
||||
yes
|
||||
1
live-build/apparmor/6.17/policy/versions/v8
Normal file
1
live-build/apparmor/6.17/policy/versions/v8
Normal file
@ -0,0 +1 @@
|
||||
yes
|
||||
1
live-build/apparmor/6.17/policy/versions/v9
Normal file
1
live-build/apparmor/6.17/policy/versions/v9
Normal file
@ -0,0 +1 @@
|
||||
yes
|
||||
1
live-build/apparmor/6.17/ptrace/mask
Normal file
1
live-build/apparmor/6.17/ptrace/mask
Normal file
@ -0,0 +1 @@
|
||||
read trace
|
||||
1
live-build/apparmor/6.17/query/label/data
Normal file
1
live-build/apparmor/6.17/query/label/data
Normal file
@ -0,0 +1 @@
|
||||
yes
|
||||
1
live-build/apparmor/6.17/query/label/multi_transaction
Normal file
1
live-build/apparmor/6.17/query/label/multi_transaction
Normal file
@ -0,0 +1 @@
|
||||
yes
|
||||
1
live-build/apparmor/6.17/query/label/perms
Normal file
1
live-build/apparmor/6.17/query/label/perms
Normal file
@ -0,0 +1 @@
|
||||
allow deny audit quiet
|
||||
1
live-build/apparmor/6.17/rlimit/mask
Normal file
1
live-build/apparmor/6.17/rlimit/mask
Normal file
@ -0,0 +1 @@
|
||||
cpu fsize data stack core rss nproc nofile memlock as locks sigpending msgqueue nice rtprio rttime
|
||||
1
live-build/apparmor/6.17/signal/mask
Normal file
1
live-build/apparmor/6.17/signal/mask
Normal 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
|
||||
@ -119,6 +119,12 @@ Expire-Date: 0
|
||||
exit $ec
|
||||
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.
|
||||
echo "LANG=C.UTF-8" > chroot/etc/default/locale
|
||||
|
||||
@ -202,6 +208,22 @@ EOF
|
||||
undivert_update_initramfs
|
||||
undivert_grub chroot
|
||||
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
|
||||
# https://mastodon.social/@scream@botsin.space
|
||||
@ -353,7 +375,7 @@ EOF
|
||||
(cd chroot && find usr/share/doc -maxdepth 1 -type d | xargs du -s | sort -nr)
|
||||
echo END docdirs
|
||||
|
||||
/usr/share/livecd-rootfs/minimize-manual chroot
|
||||
${LIVECD_ROOTFS_ROOT}/minimize-manual chroot
|
||||
|
||||
clean_debian_chroot
|
||||
fi
|
||||
@ -421,13 +443,6 @@ if [ -e config/manifest-minimal-remove ]; then
|
||||
cp config/manifest-minimal-remove "$PREFIX.manifest-minimal-remove"
|
||||
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
|
||||
(cd "binary/$INITFS/filesystem.dir/" && tar -c --sort=name --xattrs *) | \
|
||||
gzip -9 --rsyncable > "$PREFIX.rootfs.tar.gz"
|
||||
@ -552,3 +567,28 @@ case $PROJECT in
|
||||
ubuntu-cpc)
|
||||
config/hooks.d/remove-implicit-artifacts
|
||||
esac
|
||||
|
||||
if [ "${MAKE_ISO}" = "yes" ]; then
|
||||
# Link build artifacts with "for-iso." prefix for isobuild to consume.
|
||||
# Layered builds create squashfs via lb_binary_layered (which already
|
||||
# creates for-iso.*.squashfs files). Single-pass builds only have
|
||||
# ${PREFIX}.squashfs, which does not contain cdrom.sources, so we
|
||||
# create a for-iso.filesystem.squashfs that does.
|
||||
if [ -z "$PASSES" ]; then
|
||||
isobuild generate-sources --mountpoint=/cdrom > chroot/etc/apt/sources.list.d/cdrom.sources
|
||||
create_squashfs chroot ${PWD}/for-iso.filesystem.squashfs
|
||||
fi
|
||||
# Link kernel and initrd files. The ${thing#${PREFIX}} expansion strips
|
||||
# the PREFIX, so "livecd.ubuntu-server.kernel-generic" becomes
|
||||
# "for-iso.kernel-generic".
|
||||
for thing in ${PREFIX}.kernel-* ${PREFIX}.initrd-*; do
|
||||
for_iso_path=for-iso${thing#${PREFIX}}
|
||||
if [ ! -f $for_iso_path ]; then
|
||||
ln -v $thing $for_iso_path
|
||||
fi
|
||||
done
|
||||
isobuild add-live-filesystem --artifact-prefix for-iso.
|
||||
isobuild make-bootable --project "${PROJECT}" --capproject "$(cat config/iso-ids/capproject)" \
|
||||
${SUBARCH:+--subarch "${SUBARCH}"}
|
||||
isobuild make-iso --volid "$(cat config/iso-ids/vol-id)" --dest ${PREFIX}.iso
|
||||
fi
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
LIVECD_ROOTFS_ROOT=${LIVECD_ROOTFS_ROOT:-/usr/share/livecd-rootfs}
|
||||
|
||||
case $ARCH:$SUBARCH in
|
||||
amd64:|amd64:generic|amd64:intel-iot|\
|
||||
arm64:|arm64:generic|arm64:raspi|arm64:snapdragon|arm64:nvidia|\
|
||||
@ -9,9 +11,7 @@ case $ARCH:$SUBARCH in
|
||||
armhf:|\
|
||||
i386:|\
|
||||
ppc64el:|\
|
||||
riscv64:|riscv64:generic|riscv64:icicle|riscv64:jh7110|riscv64:licheerv|\
|
||||
riscv64:milkvmars|riscv64:nezha|riscv64:pic64gx|riscv64:unmatched|\
|
||||
riscv64:visionfive|riscv64:visionfive2|\
|
||||
riscv64:|riscv64:generic|\
|
||||
s390x:|\
|
||||
*appliance*)
|
||||
;;
|
||||
@ -49,11 +49,14 @@ if [ -z "$MIRROR" ]; then
|
||||
fi
|
||||
|
||||
mkdir -p config
|
||||
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/snap-seed-parse.py config/snap-seed-parse
|
||||
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/
|
||||
echo "LIVECD_ROOTFS_ROOT=\"$LIVECD_ROOTFS_ROOT\"" > config/functions
|
||||
chmod --reference=${LIVECD_ROOTFS_ROOT}/live-build/functions config/functions
|
||||
cat ${LIVECD_ROOTFS_ROOT}/live-build/functions >> config/functions
|
||||
cp -af ${LIVECD_ROOTFS_ROOT}/live-build/lb_*_layered config/
|
||||
cp -af ${LIVECD_ROOTFS_ROOT}/live-build/snap-seed-parse.py config/snap-seed-parse
|
||||
cp -af ${LIVECD_ROOTFS_ROOT}/live-build/snap-seed-missing-providers.py config/snap-seed-missing-providers
|
||||
cp -af ${LIVECD_ROOTFS_ROOT}/live-build/expand-task config/expand-task
|
||||
cp -af ${LIVECD_ROOTFS_ROOT}/live-build/squashfs-exclude-files config/
|
||||
|
||||
mkdir -p config/package-lists
|
||||
|
||||
@ -391,7 +394,7 @@ if [ -z "${IMAGEFORMAT:-}" ]; then
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
ubuntu-server:live|ubuntu-mini-iso:|ubuntu-core-installer:*)
|
||||
ubuntu-server:live|ubuntu-mini-iso:|ubuntu-test-iso:|ubuntu-core-installer:*)
|
||||
IMAGEFORMAT=plain
|
||||
;;
|
||||
esac
|
||||
@ -401,15 +404,6 @@ fi
|
||||
# one also must request disk1-img-xz image format
|
||||
if [ "$IMAGEFORMAT" = "ext4" ] && [ "$PROJECT" = "ubuntu-cpc" ]; then
|
||||
case $ARCH:$SUBARCH in
|
||||
riscv64:icicle | \
|
||||
riscv64:jh7110 | \
|
||||
riscv64:licheerv | \
|
||||
riscv64:milkvmars | \
|
||||
riscv64:nezha | \
|
||||
riscv64:pic64gx | \
|
||||
riscv64:unmatched | \
|
||||
riscv64:visionfive | \
|
||||
riscv64:visionfive2 | \
|
||||
*:generic)
|
||||
IMAGE_HAS_HARDCODED_PASSWORD=1
|
||||
if [ -z "${IMAGE_TARGETS:-}" ]; then
|
||||
@ -436,7 +430,7 @@ case $IMAGEFORMAT in
|
||||
ubuntu-server:live|ubuntu-core-installer:*)
|
||||
touch config/universe-enabled
|
||||
;;
|
||||
ubuntu-mini-iso:)
|
||||
ubuntu-mini-iso:|ubuntu-test-iso:)
|
||||
fs=none
|
||||
;;
|
||||
*)
|
||||
@ -646,7 +640,7 @@ case $PROJECT in
|
||||
esac
|
||||
|
||||
case $PROJECT in
|
||||
ubuntu-server|ubuntu-mini-iso)
|
||||
ubuntu-mini-iso|ubuntu-test-iso)
|
||||
COMPONENTS='main'
|
||||
;;
|
||||
edubuntu|ubuntu-budgie|ubuntucinnamon|ubuntukylin)
|
||||
@ -663,20 +657,45 @@ case $SUBPROJECT in
|
||||
;;
|
||||
esac
|
||||
|
||||
if ! [ -e config/germinate-output/structure ]; then
|
||||
echo "Running germinate..."
|
||||
if [ -n "$COMPONENTS" ]; then
|
||||
GERMINATE_ARG="-c $(echo $COMPONENTS | sed -e's/ \+/,/g')"
|
||||
fi
|
||||
(cd config/germinate-output && germinate --no-rdepends --no-installer \
|
||||
-S $SEEDMIRROR -m $MIRROR -d $SUITE,$SUITE-updates \
|
||||
-s $FLAVOUR.$SUITE $GERMINATE_ARG -a $ARCH)
|
||||
fi
|
||||
case $PROJECT in
|
||||
ubuntu-test-iso)
|
||||
# ubuntu-test-iso uses only add_package (not add_task) and has no
|
||||
# pool, so germinate output is never needed.
|
||||
touch config/germinate-output/structure
|
||||
;;
|
||||
*)
|
||||
if ! [ -e config/germinate-output/structure ]; then
|
||||
echo "Running germinate..."
|
||||
if [ -n "$COMPONENTS" ]; then
|
||||
GERMINATE_ARG="-c $(echo $COMPONENTS | sed -e's/ \+/,/g')"
|
||||
fi
|
||||
(cd config/germinate-output && germinate --no-rdepends --no-installer \
|
||||
-S $SEEDMIRROR -m $MIRROR -d $SUITE,$SUITE-updates \
|
||||
-s $FLAVOUR.$SUITE $GERMINATE_ARG -a ${ARCH_VARIANT:-$ARCH})
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
# ISO build configuration. These defaults are overridden per-project below.
|
||||
#
|
||||
# MAKE_ISO: Set to "yes" to generate an installer ISO at the end of the build.
|
||||
# This triggers isobuild to run in auto/build.
|
||||
MAKE_ISO=no
|
||||
# POOL_SEED_NAME: The germinate output file defining packages for the ISO's
|
||||
# package pool (repository). Different flavors use different seeds:
|
||||
# - "ship-live" for most desktop images
|
||||
# - "server-ship-live" for Ubuntu Server (includes server-specific packages)
|
||||
# - "" (empty) for images without a pool, like Ubuntu Core Installer
|
||||
POOL_SEED_NAME=ship-live
|
||||
# SQUASHFS_COMP: compression algorithm for squashfs images. lz4 is ~10x
|
||||
# faster than xz and useful for test builds that don't need small images.
|
||||
SQUASHFS_COMP=xz
|
||||
|
||||
# Common functionality for layered desktop images
|
||||
common_layered_desktop_image() {
|
||||
touch config/universe-enabled
|
||||
PASSES_TO_LAYERS="true"
|
||||
MAKE_ISO=yes
|
||||
|
||||
if [ -n "$HAS_MINIMAL" ]; then
|
||||
if [ -z "$MINIMAL_TASKS" ]; then
|
||||
@ -801,7 +820,7 @@ do_layered_desktop_image() {
|
||||
DEFAULT_KERNEL="linux-$KERNEL_FLAVOURS"
|
||||
|
||||
if [ "$LOCALE_SUPPORT" != none ]; then
|
||||
/usr/share/livecd-rootfs/checkout-translations-branch \
|
||||
${LIVECD_ROOTFS_ROOT}/checkout-translations-branch \
|
||||
https://git.launchpad.net/subiquity po \
|
||||
config/catalog-translations
|
||||
fi
|
||||
@ -907,6 +926,7 @@ case $PROJECT in
|
||||
add_task install minimal standard
|
||||
add_task install kubuntu-desktop
|
||||
LIVE_TASK='kubuntu-live'
|
||||
MAKE_ISO=yes
|
||||
add_chroot_hook remove-gnome-icon-cache
|
||||
;;
|
||||
|
||||
@ -917,6 +937,7 @@ case $PROJECT in
|
||||
MINIMAL_DESC="A minimal installation of the Edubuntu Desktop."
|
||||
STANDARD_TASKS=edubuntu-desktop-gnome
|
||||
KERNEL_FLAVOURS=generic
|
||||
NEEDS_DRACUT=yes
|
||||
do_layered_desktop_image
|
||||
;;
|
||||
|
||||
@ -932,6 +953,7 @@ case $PROJECT in
|
||||
ubuntu-unity)
|
||||
add_task install minimal standard ${PROJECT}-desktop
|
||||
LIVE_TASK=${PROJECT}-live
|
||||
MAKE_ISO=yes
|
||||
;;
|
||||
|
||||
lubuntu)
|
||||
@ -1006,6 +1028,8 @@ case $PROJECT in
|
||||
live)
|
||||
OPTS="${OPTS:+$OPTS }--bootstrap-flavour=minimal"
|
||||
PASSES_TO_LAYERS=true
|
||||
MAKE_ISO=yes
|
||||
POOL_SEED_NAME=server-ship-live
|
||||
add_task ubuntu-server-minimal server-minimal
|
||||
add_package ubuntu-server-minimal lxd-installer
|
||||
add_task ubuntu-server-minimal.ubuntu-server minimal standard server
|
||||
@ -1106,6 +1130,9 @@ case $PROJECT in
|
||||
arm64)
|
||||
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
|
||||
;;
|
||||
@ -1113,7 +1140,7 @@ case $PROJECT in
|
||||
NO_SQUASHFS_PASSES=ubuntu-server-minimal.ubuntu-server.installer.$flavor.netboot
|
||||
|
||||
DEFAULT_KERNEL="$kernel_metapkg"
|
||||
/usr/share/livecd-rootfs/checkout-translations-branch \
|
||||
${LIVECD_ROOTFS_ROOT}/checkout-translations-branch \
|
||||
https://git.launchpad.net/subiquity po config/catalog-translations
|
||||
;;
|
||||
*)
|
||||
@ -1131,10 +1158,12 @@ case $PROJECT in
|
||||
# created in ubuntu-core-installer/hooks/05-prepare-image.binary, which
|
||||
# subiquity knows how to install.
|
||||
if [ ${SUBPROJECT} == "desktop" ]; then
|
||||
cp /usr/share/livecd-rootfs/live-build/${PROJECT}/ubuntu-core-desktop-24-amd64.model-assertion config/
|
||||
cp ${LIVECD_ROOTFS_ROOT}/live-build/${PROJECT}/ubuntu-core-desktop-24-amd64.model-assertion config/
|
||||
fi
|
||||
OPTS="${OPTS:+$OPTS }--bootstrap-flavour=minimal"
|
||||
PASSES_TO_LAYERS=true
|
||||
MAKE_ISO=yes
|
||||
POOL_SEED_NAME=
|
||||
add_task base server-minimal server
|
||||
add_task base.live server-live
|
||||
add_package base.live linux-image-generic
|
||||
@ -1143,7 +1172,7 @@ case $PROJECT in
|
||||
USE_BRIDGE_KERNEL=false
|
||||
DEFAULT_KERNEL="snap:pc-kernel"
|
||||
|
||||
/usr/share/livecd-rootfs/checkout-translations-branch \
|
||||
${LIVECD_ROOTFS_ROOT}/checkout-translations-branch \
|
||||
https://git.launchpad.net/subiquity po config/catalog-translations
|
||||
;;
|
||||
|
||||
@ -1153,6 +1182,7 @@ case $PROJECT in
|
||||
OPTS="${OPTS:+$OPTS }--linux-packages=none --initramfs=none"
|
||||
KERNEL_FLAVOURS=none
|
||||
BINARY_REMOVE_LINUX=false
|
||||
MAKE_ISO=yes
|
||||
|
||||
add_package install mini-iso-tools linux-generic
|
||||
case $ARCH in
|
||||
@ -1165,6 +1195,22 @@ case $PROJECT in
|
||||
esac
|
||||
;;
|
||||
|
||||
ubuntu-test-iso)
|
||||
OPTS="${OPTS:+$OPTS }--bootstrap-flavour=minimal"
|
||||
KERNEL_FLAVOURS=virtual
|
||||
BINARY_REMOVE_LINUX=false
|
||||
MAKE_ISO=yes
|
||||
POOL_SEED_NAME=
|
||||
SQUASHFS_COMP=lz4
|
||||
PASSES_TO_LAYERS=true
|
||||
add_package base linux-$KERNEL_FLAVOURS
|
||||
add_package base.live casper
|
||||
case $ARCH in
|
||||
amd64) ;;
|
||||
*) echo "ubuntu-test-iso only supports amd64"; exit 1 ;;
|
||||
esac
|
||||
;;
|
||||
|
||||
ubuntu-base|ubuntu-oci)
|
||||
OPTS="${OPTS:+$OPTS }--bootstrap-flavour=minimal"
|
||||
;;
|
||||
@ -1218,14 +1264,7 @@ case $PROJECT in
|
||||
;;
|
||||
riscv64*)
|
||||
if [ -n "$SUBARCH" ]; then
|
||||
case "${SUBARCH:-}" in
|
||||
visionfive)
|
||||
KERNEL_FLAVOURS=starfive
|
||||
;;
|
||||
*)
|
||||
KERNEL_FLAVOURS=generic
|
||||
;;
|
||||
esac
|
||||
KERNEL_FLAVOURS=generic
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
@ -1271,7 +1310,7 @@ case $SUBPROJECT in
|
||||
# and a variety of things fail without it.
|
||||
add_package install tzdata
|
||||
|
||||
cp -af /usr/share/livecd-rootfs/live-build/make-lxd-metadata.py config/make-lxd-metadata
|
||||
cp -af ${LIVECD_ROOTFS_ROOT}/live-build/make-lxd-metadata.py config/make-lxd-metadata
|
||||
;;
|
||||
esac
|
||||
|
||||
@ -1378,6 +1417,7 @@ lb config noauto \
|
||||
--checksums none \
|
||||
--cache false \
|
||||
${BOOTAPPEND_LIVE:+--bootappend-live "$BOOTAPPEND_LIVE"} \
|
||||
${ARCH_VARIANT:+--bootstrap-arch "$ARCH_VARIANT"} \
|
||||
$OPTS \
|
||||
"$@"
|
||||
|
||||
@ -1389,13 +1429,19 @@ if [ -n "$PASSES" ] && [ -z "$LIVE_PASSES" ]; then
|
||||
"Either set \$LIVE_PASSES or add a pass ending with '.live'."
|
||||
fi
|
||||
|
||||
echo "DEBOOTSTRAP_OPTIONS=\"--keyring=/usr/share/keyrings/ubuntu-archive-keyring.gpg\"" >> config/bootstrap
|
||||
|
||||
echo "LB_CHROOT_HOOKS=\"$CHROOT_HOOKS\"" >> config/chroot
|
||||
echo "SUBPROJECT=\"${SUBPROJECT:-}\"" >> config/chroot
|
||||
echo "LB_DISTRIBUTION=\"$SUITE\"" >> config/chroot
|
||||
echo "IMAGEFORMAT=\"$IMAGEFORMAT\"" >> config/chroot
|
||||
echo "LIVECD_ROOTFS_ROOT=\"$LIVECD_ROOTFS_ROOT\"" >> config/common
|
||||
if [ -n "$PASSES" ]; then
|
||||
echo "PASSES=\"$PASSES\"" >> config/common
|
||||
fi
|
||||
echo "MAKE_ISO=\"$MAKE_ISO\"" >> config/common
|
||||
echo "POOL_SEED_NAME=\"$POOL_SEED_NAME\"" >> config/common
|
||||
echo "SQUASHFS_COMP=\"$SQUASHFS_COMP\"" >> config/common
|
||||
if [ -n "$NO_SQUASHFS_PASSES" ]; then
|
||||
echo "NO_SQUASHFS_PASSES=\"$NO_SQUASHFS_PASSES\"" >> config/common
|
||||
fi
|
||||
@ -1431,7 +1477,7 @@ rm -fv /etc/ssl/private/ssl-cert-snakeoil.key \
|
||||
EOF
|
||||
|
||||
case $PROJECT in
|
||||
ubuntu-cpc|ubuntu-core|ubuntu-base|ubuntu-oci|ubuntu-wsl|ubuntu-mini-iso)
|
||||
ubuntu-cpc|ubuntu-core|ubuntu-base|ubuntu-oci|ubuntu-wsl|ubuntu-mini-iso|ubuntu-test-iso)
|
||||
# ubuntu-cpc gets this added in 025-create-groups.chroot, and we do
|
||||
# not want this group in projects that are effectively just chroots
|
||||
;;
|
||||
@ -1519,11 +1565,11 @@ fi
|
||||
|
||||
case $PROJECT:${SUBPROJECT:-} in
|
||||
ubuntu-cpc:*|ubuntu-server:live|ubuntu:desktop-preinstalled| \
|
||||
ubuntu-wsl:*|ubuntu-mini-iso:*|ubuntu:|ubuntu-oem:*| \
|
||||
ubuntu-wsl:*|ubuntu-mini-iso:*|ubuntu-test-iso:*|ubuntu:|ubuntu:dangerous|ubuntu-oem:*| \
|
||||
ubuntustudio:*|edubuntu:*|ubuntu-budgie:*|ubuntucinnamon:*|xubuntu:*| \
|
||||
ubuntukylin:*|ubuntu-mate:*|ubuntu-core-installer:*|lubuntu:*)
|
||||
# Ensure that most things e.g. includes.chroot are copied as is
|
||||
for entry in /usr/share/livecd-rootfs/live-build/${PROJECT}/*; do
|
||||
for entry in ${LIVECD_ROOTFS_ROOT}/live-build/${PROJECT}/*; do
|
||||
case $entry in
|
||||
*hooks*)
|
||||
# But hooks are shared across the projects with symlinks
|
||||
@ -1558,11 +1604,11 @@ esac
|
||||
case $PROJECT in
|
||||
ubuntu-oem|ubuntustudio|edubuntu|ubuntu-budgie|ubuntucinnamon| \
|
||||
xubuntu|ubuntukylin|ubuntu-mate|lubuntu)
|
||||
cp -af /usr/share/livecd-rootfs/live-build/ubuntu/includes.chroot \
|
||||
cp -af ${LIVECD_ROOTFS_ROOT}/live-build/ubuntu/includes.chroot \
|
||||
config/includes.chroot
|
||||
|
||||
LIVE_LAYER=${LIVE_PREFIX}live
|
||||
cp -af /usr/share/livecd-rootfs/live-build/ubuntu/includes.chroot.minimal.standard.live \
|
||||
cp -af ${LIVECD_ROOTFS_ROOT}/live-build/ubuntu/includes.chroot.minimal.standard.live \
|
||||
config/includes.chroot.$LIVE_LAYER
|
||||
|
||||
if [ $PROJECT != ubuntu-oem ]; then
|
||||
@ -1578,7 +1624,7 @@ esac
|
||||
|
||||
case $SUBPROJECT in
|
||||
buildd)
|
||||
cp -af /usr/share/livecd-rootfs/live-build/buildd/* config/
|
||||
cp -af ${LIVECD_ROOTFS_ROOT}/live-build/buildd/* config/
|
||||
;;
|
||||
esac
|
||||
|
||||
@ -1602,7 +1648,7 @@ if [ "$EXTRA_PPAS" ]; then
|
||||
extra_ppa=${extra_ppa%:*}
|
||||
;;
|
||||
esac
|
||||
extra_ppa_fingerprint="$(/usr/share/livecd-rootfs/get-ppa-fingerprint "$extra_ppa")"
|
||||
extra_ppa_fingerprint="$(${LIVECD_ROOTFS_ROOT}/get-ppa-fingerprint "$extra_ppa")"
|
||||
|
||||
cat >> config/archives/extra-ppas.list.chroot <<EOF
|
||||
deb https://ppa.launchpadcontent.net/$extra_ppa/ubuntu @DISTRIBUTION@ main
|
||||
@ -1689,3 +1735,22 @@ apt-get -y download $PREINSTALL_POOL
|
||||
EOF
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "${MAKE_ISO}" = "yes" ]; then
|
||||
# XXX should pass --build-type here.
|
||||
${LIVECD_ROOTFS_ROOT}/live-build/gen-iso-ids \
|
||||
--project $PROJECT ${SUBPROJECT:+--subproject $SUBPROJECT} \
|
||||
--arch $ARCH ${SUBARCH:+--subarch $SUBARCH} ${NOW+--serial $NOW} \
|
||||
--output-dir config/iso-ids/
|
||||
fi
|
||||
|
||||
if [ -n "$http_proxy" ]; then
|
||||
mkdir -p /etc/systemd/system/snapd.service.d/
|
||||
cat > /etc/systemd/system/snapd.service.d/snap_proxy.conf <<EOF
|
||||
[Service]
|
||||
Environment="HTTP_PROXY=${http_proxy}"
|
||||
Environment="HTTPS_PROXY=${http_proxy}"
|
||||
EOF
|
||||
systemctl daemon-reload
|
||||
systemctl restart snapd.service
|
||||
fi
|
||||
|
||||
218
live-build/build-livefs
Executable file
218
live-build/build-livefs
Executable file
@ -0,0 +1,218 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import configparser
|
||||
import os
|
||||
import pathlib
|
||||
import platform
|
||||
import subprocess
|
||||
|
||||
import click
|
||||
|
||||
|
||||
_CONFIG_FILE = pathlib.Path.home() / ".config" / "livecd-rootfs" / "build-livefs.conf"
|
||||
|
||||
|
||||
def _read_config() -> dict[str, str]:
|
||||
"""Read default values from the user config file if it exists.
|
||||
|
||||
The config file uses INI format with a [defaults] section, e.g.:
|
||||
|
||||
[defaults]
|
||||
http-proxy = http://squid.internal:3128/
|
||||
mirror = http://ftpmaster.internal/ubuntu/
|
||||
"""
|
||||
cp = configparser.ConfigParser()
|
||||
cp.read(_CONFIG_FILE)
|
||||
return dict(cp["defaults"]) if "defaults" in cp else {}
|
||||
|
||||
|
||||
_MACHINE_TO_ARCH = {
|
||||
"x86_64": "amd64",
|
||||
"aarch64": "arm64",
|
||||
"ppc64le": "ppc64el",
|
||||
"s390x": "s390x",
|
||||
"riscv64": "riscv64",
|
||||
"armv7l": "armhf",
|
||||
}
|
||||
|
||||
|
||||
def _default_arch():
|
||||
machine = platform.machine()
|
||||
try:
|
||||
return _MACHINE_TO_ARCH[machine]
|
||||
except KeyError:
|
||||
raise click.UsageError(
|
||||
f"Cannot determine default arch for machine {machine!r}; use --arch"
|
||||
)
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option(
|
||||
"--work-dir",
|
||||
default=".",
|
||||
type=click.Path(file_okay=False, path_type=pathlib.Path),
|
||||
help="Working directory for the build (default: current directory)",
|
||||
)
|
||||
@click.option("--project", required=True, help="Project name (e.g. ubuntu, ubuntu-cpc)")
|
||||
@click.option("--suite", required=True, help="Ubuntu suite/series (e.g. noble)")
|
||||
@click.option("--arch", default=None, help="Target architecture (default: host arch)")
|
||||
@click.option("--arch-variant", default=None, help="Architecture variant")
|
||||
@click.option("--subproject", default=None, help="Subproject")
|
||||
@click.option("--subarch", default=None, help="Sub-architecture")
|
||||
@click.option("--channel", default=None, help="Channel")
|
||||
@click.option(
|
||||
"--image-target",
|
||||
"image_targets",
|
||||
multiple=True,
|
||||
help="Image target (may be repeated)",
|
||||
)
|
||||
@click.option("--repo-snapshot-stamp", default=None, help="Repository snapshot stamp")
|
||||
@click.option(
|
||||
"--snapshot-service-timestamp", default=None, help="Snapshot service timestamp"
|
||||
)
|
||||
@click.option("--cohort-key", default=None, help="Cohort key")
|
||||
@click.option("--datestamp", default=None, help="Datestamp (sets NOW)")
|
||||
@click.option("--image-format", default=None, help="Image format (sets IMAGEFORMAT)")
|
||||
@click.option(
|
||||
"--proposed",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Enable proposed pocket (sets PROPOSED=1)",
|
||||
)
|
||||
@click.option(
|
||||
"--extra-ppa", "extra_ppas", multiple=True, help="Extra PPA (may be repeated)"
|
||||
)
|
||||
@click.option(
|
||||
"--extra-snap", "extra_snaps", multiple=True, help="Extra snap (may be repeated)"
|
||||
)
|
||||
@click.option("--build-type", default=None, help="Build type")
|
||||
@click.option(
|
||||
"--http-proxy",
|
||||
default=None,
|
||||
help="HTTP proxy (sets http_proxy, HTTP_PROXY, LB_APT_HTTP_PROXY)",
|
||||
)
|
||||
@click.option(
|
||||
"--mirror",
|
||||
default=None,
|
||||
help="Ubuntu archive mirror URL (sets MIRROR)",
|
||||
)
|
||||
@click.option(
|
||||
"--debug", is_flag=True, default=False, help="Enable debug mode (set -x in lb scripts)"
|
||||
)
|
||||
def main(
|
||||
work_dir,
|
||||
project,
|
||||
suite,
|
||||
arch,
|
||||
arch_variant,
|
||||
subproject,
|
||||
subarch,
|
||||
channel,
|
||||
image_targets,
|
||||
repo_snapshot_stamp,
|
||||
snapshot_service_timestamp,
|
||||
cohort_key,
|
||||
datestamp,
|
||||
image_format,
|
||||
proposed,
|
||||
extra_ppas,
|
||||
extra_snaps,
|
||||
build_type,
|
||||
http_proxy,
|
||||
mirror,
|
||||
debug,
|
||||
):
|
||||
cfg = _read_config()
|
||||
if http_proxy is None:
|
||||
http_proxy = cfg.get("http-proxy")
|
||||
if mirror is None:
|
||||
mirror = cfg.get("mirror")
|
||||
|
||||
if arch is None:
|
||||
arch = _default_arch()
|
||||
|
||||
# Locate auto/ scripts relative to this script, following symlinks.
|
||||
# Works for: git checkout, installed deb, and /usr/bin/build-livefs symlink.
|
||||
live_build_dir = pathlib.Path(__file__).resolve().parent
|
||||
auto_source = live_build_dir / "auto"
|
||||
|
||||
# base_env is passed to both lb config and lb build
|
||||
base_env = {
|
||||
"PROJECT": project,
|
||||
"ARCH": arch,
|
||||
"LIVECD_ROOTFS_ROOT": str(live_build_dir.parent),
|
||||
}
|
||||
if arch_variant is not None:
|
||||
base_env["ARCH_VARIANT"] = arch_variant
|
||||
if subproject is not None:
|
||||
base_env["SUBPROJECT"] = subproject
|
||||
if subarch is not None:
|
||||
base_env["SUBARCH"] = subarch
|
||||
if channel is not None:
|
||||
base_env["CHANNEL"] = channel
|
||||
if image_targets:
|
||||
base_env["IMAGE_TARGETS"] = " ".join(image_targets)
|
||||
if repo_snapshot_stamp is not None:
|
||||
base_env["REPO_SNAPSHOT_STAMP"] = repo_snapshot_stamp
|
||||
if snapshot_service_timestamp is not None:
|
||||
base_env["SNAPSHOT_SERVICE_TIMESTAMP"] = snapshot_service_timestamp
|
||||
if cohort_key is not None:
|
||||
base_env["COHORT_KEY"] = cohort_key
|
||||
if http_proxy is not None:
|
||||
base_env["http_proxy"] = http_proxy
|
||||
base_env["HTTP_PROXY"] = http_proxy
|
||||
base_env["LB_APT_HTTP_PROXY"] = http_proxy
|
||||
|
||||
# config_env adds lb-config-only vars on top of base_env
|
||||
config_env = {
|
||||
**base_env,
|
||||
"SUITE": suite,
|
||||
}
|
||||
if datestamp is not None:
|
||||
config_env["NOW"] = datestamp
|
||||
if image_format is not None:
|
||||
config_env["IMAGEFORMAT"] = image_format
|
||||
if proposed:
|
||||
config_env["PROPOSED"] = "1"
|
||||
if extra_ppas:
|
||||
config_env["EXTRA_PPAS"] = " ".join(extra_ppas)
|
||||
if extra_snaps:
|
||||
config_env["EXTRA_SNAPS"] = " ".join(extra_snaps)
|
||||
if build_type is not None:
|
||||
config_env["BUILD_TYPE"] = build_type
|
||||
if mirror is not None:
|
||||
config_env["MIRROR"] = mirror
|
||||
|
||||
work_dir = work_dir.resolve()
|
||||
work_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Create/replace auto/ symlinks
|
||||
auto_dir = work_dir / "auto"
|
||||
auto_dir.mkdir(exist_ok=True)
|
||||
for script in ("config", "build", "clean"):
|
||||
link = auto_dir / script
|
||||
if link.is_symlink() or link.exists():
|
||||
link.unlink()
|
||||
link.symlink_to(auto_source / script)
|
||||
|
||||
# Write debug.sh if requested
|
||||
if debug:
|
||||
debug_dir = work_dir / "local" / "functions"
|
||||
debug_dir.mkdir(parents=True, exist_ok=True)
|
||||
(debug_dir / "debug.sh").write_text("set -x\n")
|
||||
|
||||
def run(cmd, env_extra):
|
||||
env = os.environ.copy()
|
||||
env.update(env_extra)
|
||||
if os.getuid() != 0:
|
||||
env_args = [f"{k}={v}" for k, v in env_extra.items()]
|
||||
cmd = ["sudo", "env"] + env_args + cmd
|
||||
subprocess.run(cmd, cwd=work_dir, env=env, check=True)
|
||||
|
||||
run(["lb", "clean", "--purge"], base_env)
|
||||
run(["lb", "config"], config_env)
|
||||
run(["lb", "build"], base_env)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
150
live-build/build-livefs-lxd
Executable file
150
live-build/build-livefs-lxd
Executable file
@ -0,0 +1,150 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import configparser
|
||||
import pathlib
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
import click
|
||||
|
||||
|
||||
_CONFIG_FILE = pathlib.Path.home() / ".config" / "livecd-rootfs" / "build-livefs.conf"
|
||||
|
||||
|
||||
def _read_config() -> dict[str, str]:
|
||||
cp = configparser.ConfigParser()
|
||||
cp.read(_CONFIG_FILE)
|
||||
return dict(cp["defaults"]) if "defaults" in cp else {}
|
||||
|
||||
|
||||
@click.command(
|
||||
context_settings={"ignore_unknown_options": True, "allow_extra_args": True}
|
||||
)
|
||||
@click.option("--suite", required=True, help="Ubuntu suite/series (e.g. noble)")
|
||||
@click.option(
|
||||
"--vm-name",
|
||||
default=None,
|
||||
help="LXD VM name (default: livefs-builder-{suite})",
|
||||
)
|
||||
@click.option(
|
||||
"--http-proxy",
|
||||
default=None,
|
||||
help="HTTP proxy URL for apt inside the VM (also read from build-livefs.conf)",
|
||||
)
|
||||
@click.argument("extra_args", nargs=-1, type=click.UNPROCESSED)
|
||||
def main(suite, vm_name, http_proxy, extra_args):
|
||||
livecd_rootfs_root = pathlib.Path(__file__).resolve().parent.parent
|
||||
vm_name = vm_name or f"livefs-builder-{suite}"
|
||||
host_conf = (
|
||||
pathlib.Path.home() / ".config" / "livecd-rootfs" / "build-livefs.conf"
|
||||
)
|
||||
|
||||
if http_proxy is None:
|
||||
http_proxy = _read_config().get("http-proxy")
|
||||
|
||||
result = subprocess.run(["lxc", "info", vm_name], capture_output=True)
|
||||
if result.returncode != 0:
|
||||
launch_cmd = [
|
||||
"lxc", "launch", f"ubuntu-daily:{suite}", vm_name, "--vm",
|
||||
"--config", "limits.cpu=4",
|
||||
"--config", "limits.memory=8GiB",
|
||||
"--device", "root,size=100GiB",
|
||||
]
|
||||
user_data = "#cloud-config\npackage_update: true\n"
|
||||
if http_proxy is not None:
|
||||
user_data += (
|
||||
"apt:\n"
|
||||
f" http_proxy: {http_proxy}\n"
|
||||
f" https_proxy: {http_proxy}\n"
|
||||
)
|
||||
launch_cmd += ["--config", f"user.user-data={user_data}"]
|
||||
subprocess.run(launch_cmd, check=True)
|
||||
|
||||
device_info = subprocess.run(
|
||||
["lxc", "config", "device", "show", vm_name],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
).stdout
|
||||
if "livecd-rootfs" not in device_info:
|
||||
subprocess.run(
|
||||
[
|
||||
"lxc",
|
||||
"config",
|
||||
"device",
|
||||
"add",
|
||||
vm_name,
|
||||
"livecd-rootfs",
|
||||
"disk",
|
||||
f"source={livecd_rootfs_root}",
|
||||
"path=/srv/livecd-rootfs",
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
|
||||
info = subprocess.run(
|
||||
["lxc", "info", vm_name], capture_output=True, text=True, check=True
|
||||
).stdout
|
||||
if "Status: STOPPED" in info:
|
||||
subprocess.run(["lxc", "start", vm_name], check=True)
|
||||
|
||||
for _ in range(30):
|
||||
result = subprocess.run(
|
||||
["lxc", "exec", vm_name, "--", "true"], capture_output=True
|
||||
)
|
||||
if result.returncode == 0:
|
||||
break
|
||||
time.sleep(2)
|
||||
else:
|
||||
raise click.ClickException(f"VM {vm_name!r} did not become ready in time")
|
||||
|
||||
subprocess.run(
|
||||
["lxc", "exec", vm_name, "--", "cloud-init", "status", "--wait"], check=True
|
||||
)
|
||||
|
||||
subprocess.run(
|
||||
["lxc", "exec", vm_name, "--", "apt-get", "install", "-y", "livecd-rootfs"],
|
||||
check=True,
|
||||
)
|
||||
|
||||
if host_conf.exists():
|
||||
subprocess.run(
|
||||
[
|
||||
"lxc",
|
||||
"exec",
|
||||
vm_name,
|
||||
"--",
|
||||
"mkdir",
|
||||
"-p",
|
||||
"/root/.config/livecd-rootfs",
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
subprocess.run(
|
||||
[
|
||||
"lxc",
|
||||
"file",
|
||||
"push",
|
||||
str(host_conf),
|
||||
f"{vm_name}/root/.config/livecd-rootfs/build-livefs.conf",
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
|
||||
subprocess.run(
|
||||
[
|
||||
"lxc",
|
||||
"exec",
|
||||
vm_name,
|
||||
"--",
|
||||
"/srv/livecd-rootfs/live-build/build-livefs",
|
||||
"--suite",
|
||||
suite,
|
||||
*extra_args,
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -137,7 +137,9 @@ EOF
|
||||
|
||||
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}"
|
||||
mount_image "${disk_image}" 1
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
# vi: ts=4 expandtab syntax=sh
|
||||
|
||||
# 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}
|
||||
fs_label="${FS_LABEL:-rootfs}"
|
||||
|
||||
@ -33,7 +34,8 @@ clean_loops() {
|
||||
|
||||
create_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() {
|
||||
@ -42,6 +44,7 @@ create_manifest() {
|
||||
local base_default_sbom_name="ubuntu-cloud-image-$(grep "VERSION_ID" $chroot_root/etc/os-release | cut --delimiter "=" --field 2 | tr -d '"')-${ARCH}-$(date +%Y%m%dT%H:%M:%S)"
|
||||
local sbom_file_name=${3:-"${base_default_sbom_name}.spdx"}
|
||||
local sbom_document_name=${4:-"${base_default_sbom_name}"}
|
||||
local should_include_sbom=${5:-"true"}
|
||||
local sbom_log=${sbom_document_name}.log
|
||||
echo "create_manifest chroot_root: ${chroot_root}"
|
||||
dpkg-query --show --admindir="${chroot_root}/var/lib/dpkg" > ${target_file}
|
||||
@ -52,22 +55,26 @@ create_manifest() {
|
||||
echo "create_manifest creating file listing."
|
||||
local target_filelist=${2%.manifest}.filelist
|
||||
(cd "${chroot_root}" && find -xdev) | sort > "${target_filelist}"
|
||||
# only creating sboms for CPC project at this time
|
||||
if [[ ! $(which cpc-sbom) ]]; then
|
||||
# ensure the tool is installed
|
||||
sudo snap install --classic --edge cpc-sbom
|
||||
fi
|
||||
# generate the SBOM
|
||||
cpc-sbom --rootdir ${chroot_root} --ignore-copyright-parsing-errors --ignore-copyright-file-not-found-errors --document-name ${sbom_document_name} >"${sbom_file_name}" 2>"${sbom_log}"
|
||||
SBOM_GENERATION_EXIT_CODE=$?
|
||||
if [[ ${SBOM_GENERATION_EXIT_CODE} != "0" ]]; then
|
||||
# check for failure and print log
|
||||
echo "ERROR: SBOM generation failed. See ${sbom_log}"
|
||||
cat "$sbom_log"
|
||||
exit 1
|
||||
if [ "$should_include_sbom" = "true" ]; then
|
||||
# only creating sboms for CPC project at this time
|
||||
if [[ ! $(which cpc-sbom) ]]; then
|
||||
# ensure the tool is installed
|
||||
sudo snap install --classic --edge cpc-sbom
|
||||
fi
|
||||
# generate the SBOM
|
||||
cpc-sbom --rootdir ${chroot_root} --ignore-copyright-parsing-errors --ignore-copyright-file-not-found-errors --document-name ${sbom_document_name} >"${sbom_file_name}" 2>"${sbom_log}"
|
||||
SBOM_GENERATION_EXIT_CODE=$?
|
||||
if [[ ${SBOM_GENERATION_EXIT_CODE} != "0" ]]; then
|
||||
# check for failure and print log
|
||||
echo "ERROR: SBOM generation failed. See ${sbom_log}"
|
||||
cat "$sbom_log"
|
||||
exit 1
|
||||
else
|
||||
echo "SBOM generation succeeded. see ${sbom_log} for details"
|
||||
fi
|
||||
else
|
||||
echo "SBOM generation succeeded. see ${sbom_log} for details"
|
||||
fi
|
||||
echo "SBOM generation skipped"
|
||||
fi
|
||||
fi
|
||||
echo "create_manifest finished"
|
||||
}
|
||||
@ -181,8 +188,8 @@ setup_mountpoint() {
|
||||
mount sysfs-live -t sysfs "$mountpoint/sys"
|
||||
mount securityfs -t securityfs "$mountpoint/sys/kernel/security"
|
||||
# Provide more up to date apparmor features, matching target kernel
|
||||
mount -o bind /usr/share/livecd-rootfs/live-build/apparmor/generic "$mountpoint/sys/kernel/security/apparmor/features/"
|
||||
mount -o bind /usr/share/livecd-rootfs/live-build/seccomp/generic.actions_avail "$mountpoint/proc/sys/kernel/seccomp/actions_avail"
|
||||
mount -o bind ${LIVECD_ROOTFS_ROOT}/live-build/apparmor/generic "$mountpoint/sys/kernel/security/apparmor/features/"
|
||||
mount -o bind ${LIVECD_ROOTFS_ROOT}/live-build/seccomp/generic.actions_avail "$mountpoint/proc/sys/kernel/seccomp/actions_avail"
|
||||
# cgroup2 mount for LP: 1944004
|
||||
mount -t cgroup2 none "$mountpoint/sys/fs/cgroup"
|
||||
mount -t tmpfs none "$mountpoint/tmp"
|
||||
@ -401,7 +408,7 @@ create_squashfs() {
|
||||
squashfs_file="$2"
|
||||
config_dir="$PWD/config"
|
||||
(cd $rootfs_dir &&
|
||||
mksquashfs . $squashfs_file -no-progress -xattrs -comp xz \
|
||||
mksquashfs . $squashfs_file -no-progress -xattrs -comp "${SQUASHFS_COMP:-xz}" \
|
||||
-ef "$config_dir/squashfs-exclude-files")
|
||||
|
||||
}
|
||||
@ -566,8 +573,8 @@ _snap_post_process() {
|
||||
# If the 'core' snap is not present, assume we are coreXX-only and
|
||||
# install the snapd snap.
|
||||
channel=stable
|
||||
if [ $SUBPROJECT = "dangerous" ]; then
|
||||
channel=$CHANNEL
|
||||
if [ "$SUBPROJECT" = "dangerous" ]; then
|
||||
channel=edge
|
||||
fi
|
||||
if [ ! -f ${snaps_dir}/core_[0-9]*.snap ]; then
|
||||
_snap_preseed $CHROOT_ROOT snapd "$channel"
|
||||
@ -666,7 +673,12 @@ _snap_preseed() {
|
||||
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
|
||||
;;
|
||||
esac
|
||||
@ -795,6 +807,14 @@ snap_preseed() {
|
||||
esac
|
||||
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:
|
||||
# SNAP_NAME is just the snap name
|
||||
# SNAP is either $SNAP_NAME or $SNAP_NAME/classic for classic confined
|
||||
@ -840,7 +860,7 @@ snap_validate_seed() {
|
||||
fi
|
||||
if [ ${boot_filename} != undefined ]; then # we have a known boot file so we can proceed with checking for features to mount
|
||||
kern_major_min=$(readlink --canonicalize --no-newline ${CHROOT_ROOT}/boot/${boot_filename} | grep --extended-regexp --only-matching --max-count 1 '[0-9]+\.[0-9]+')
|
||||
if [ -d /usr/share/livecd-rootfs/live-build/apparmor/${kern_major_min} ]; then
|
||||
if [ -d ${LIVECD_ROOTFS_ROOT}/live-build/apparmor/${kern_major_min} ]; then
|
||||
# if an Ubuntu version has different kernel apparmor features between LTS and HWE kernels
|
||||
# a snap pre-seeding issue can occur, where the incorrect apparmor features are reported
|
||||
# basic copy of a directory structure overriding the "generic" feature set
|
||||
@ -848,11 +868,22 @@ snap_validate_seed() {
|
||||
|
||||
# Bind kernel apparmor directory to feature directory for snap preseeding
|
||||
umount "${CHROOT_ROOT}/sys/kernel/security/apparmor/features/"
|
||||
mount --bind /usr/share/livecd-rootfs/live-build/apparmor/${kern_major_min} "${CHROOT_ROOT}/sys/kernel/security/apparmor/features/"
|
||||
mount --bind ${LIVECD_ROOTFS_ROOT}/live-build/apparmor/${kern_major_min} "${CHROOT_ROOT}/sys/kernel/security/apparmor/features/"
|
||||
fi
|
||||
fi
|
||||
|
||||
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"
|
||||
/usr/lib/snapd/snap-preseed --reset $(realpath "${CHROOT_ROOT}")
|
||||
/usr/lib/snapd/snap-preseed $(realpath "${CHROOT_ROOT}")
|
||||
@ -863,7 +894,7 @@ snap_validate_seed() {
|
||||
# mount generic apparmor feature again (cleanup)
|
||||
if [ -d /build/config/hooks.d/extra/apparmor/${kern_major_min} ]; then
|
||||
umount "${CHROOT_ROOT}/sys/kernel/security/apparmor/features/"
|
||||
mount -o bind /usr/share/livecd-rootfs/live-build/apparmor/generic "${CHROOT_ROOT}/sys/kernel/security/apparmor/features/"
|
||||
mount -o bind ${LIVECD_ROOTFS_ROOT}/live-build/apparmor/generic "${CHROOT_ROOT}/sys/kernel/security/apparmor/features/"
|
||||
fi
|
||||
|
||||
}
|
||||
@ -1057,6 +1088,7 @@ network:
|
||||
version: 2
|
||||
renderer: NetworkManager
|
||||
EOF
|
||||
chmod 600 chroot/etc/netplan/01-network-manager-all.yaml
|
||||
# Do not limit cloud-init renderers to network-manager as suggested
|
||||
# in LP: #1982855 because subiquity needs to render full networking
|
||||
# in ephemeral boot time when autoinstall.network is provided.
|
||||
@ -1085,11 +1117,11 @@ EOF
|
||||
mkdir -p chroot/etc/systemd/system/
|
||||
cat <<EOF > chroot/etc/systemd/system/cloud-init-network.service
|
||||
${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
|
||||
# (LP: #2008952)
|
||||
[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
|
||||
DefaultDependencies=no
|
||||
Wants=cloud-init-local.service
|
||||
@ -1119,7 +1151,7 @@ Type=oneshot
|
||||
# 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
|
||||
# "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
|
||||
TimeoutSec=0
|
||||
|
||||
@ -1222,7 +1254,7 @@ setup_cidata() {
|
||||
local mountpoint=$(mktemp -d)
|
||||
mkfs.vfat -F 32 -n CIDATA ${cidata_dev}
|
||||
mount ${cidata_dev} ${mountpoint}
|
||||
cp /usr/share/livecd-rootfs/live-build/cidata/* ${mountpoint}
|
||||
cp ${LIVECD_ROOTFS_ROOT}/live-build/cidata/* ${mountpoint}
|
||||
cat >>${mountpoint}/meta-data.sample <<END
|
||||
#instance-id: iid-$(openssl rand -hex 8)
|
||||
|
||||
@ -1377,3 +1409,50 @@ EOF
|
||||
EOF
|
||||
fi
|
||||
}
|
||||
|
||||
# Determine the appropriate partition type UUID for the root filesystem
|
||||
# based on the architecture.
|
||||
# Please see https://uapi-group.org/specifications/specs/discoverable_partitions_specification/
|
||||
# for where the GUIDs come from.
|
||||
gpt_root_partition_uuid() {
|
||||
local ARCH="$1"
|
||||
|
||||
if [ -z "${ARCH:-}" ]; then
|
||||
echo "usage: gpt_root_partition_uuid <arch>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "${ARCH}" in
|
||||
"amd64")
|
||||
ROOTFS_PARTITION_TYPE="4f68bce3-e8cd-4db1-96e7-fbcaf984b709"
|
||||
;;
|
||||
"arm64")
|
||||
ROOTFS_PARTITION_TYPE="b921b045-1df0-41c3-af44-4c6f280d3fae"
|
||||
;;
|
||||
"armhf")
|
||||
ROOTFS_PARTITION_TYPE="69dad710-2ce4-4e3c-b16c-21a1d49abed3"
|
||||
;;
|
||||
"riscv64")
|
||||
ROOTFS_PARTITION_TYPE="72ec70a6-cf74-40e6-bd49-4bda08e8f224"
|
||||
;;
|
||||
"ppc64el")
|
||||
ROOTFS_PARTITION_TYPE="c31c45e6-3f39-412e-80fb-4809c4980599"
|
||||
;;
|
||||
"s390x")
|
||||
ROOTFS_PARTITION_TYPE="5eead9a9-fe09-4a1e-a1d7-520d00531306"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported architecture: ${ARCH}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "${ROOTFS_PARTITION_TYPE}"
|
||||
}
|
||||
|
||||
# Wrapper for the isobuild tool. Sets PYTHONPATH so the isobuilder module
|
||||
# is importable, and uses config/iso-dir as the standard working directory
|
||||
# for ISO metadata and intermediate files.
|
||||
isobuild () {
|
||||
PYTHONPATH=${LIVECD_ROOTFS_ROOT}/live-build/ ${LIVECD_ROOTFS_ROOT}/live-build/isobuild --workdir config/iso-dir "$@"
|
||||
}
|
||||
|
||||
198
live-build/gen-iso-ids
Executable file
198
live-build/gen-iso-ids
Executable file
@ -0,0 +1,198 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
# Compute various slightly obscure IDs and labels used by ISO builds.
|
||||
#
|
||||
# * ISO9660 images have a "volume id".
|
||||
# * Our ISOs contain a ".disk/info" file that is read by various
|
||||
# other things (casper, the installer) and is generally used as a
|
||||
# record of where an installation came from.
|
||||
# * The code that sets up grub for the ISO needs a "capitalized
|
||||
# project name" or capproject.
|
||||
#
|
||||
# All of these are derived from other build parameters (and/or
|
||||
# information in etc/os-release) in slightly non-obvious ways so the
|
||||
# logic to do so is confined to this file to avoid it cluttering
|
||||
# anywhere else.
|
||||
|
||||
import pathlib
|
||||
import platform
|
||||
import time
|
||||
|
||||
import click
|
||||
|
||||
|
||||
# Be careful about the values here. They end up in .disk/info, which is read by
|
||||
# casper to create the live session user, so if there is a space in the
|
||||
# capproject things go a bit wonky.
|
||||
#
|
||||
# It will also be used by make_vol_id to construct an ISO9660 volume ID as
|
||||
#
|
||||
# "$(CAPPROJECT) $(DEBVERSION) $(ARCH)",
|
||||
#
|
||||
# e.g. "Ubuntu 14.10 amd64". The volume ID is limited to 32 characters. This
|
||||
# therefore imposes a limit on the length of project_map values of 25 - (length
|
||||
# of longest relevant architecture name).
|
||||
project_to_capproject_map = {
|
||||
"edubuntu": "Edubuntu",
|
||||
"kubuntu": "Kubuntu",
|
||||
"lubuntu": "Lubuntu",
|
||||
"ubuntu": "Ubuntu",
|
||||
"ubuntu-base": "Ubuntu-Base",
|
||||
"ubuntu-budgie": "Ubuntu-Budgie",
|
||||
"ubuntu-core-installer": "Ubuntu-Core-Installer",
|
||||
"ubuntu-mate": "Ubuntu-MATE",
|
||||
"ubuntu-mini-iso": "Ubuntu-Mini-ISO",
|
||||
"ubuntu-test-iso": "Ubuntu-Test-ISO",
|
||||
"ubuntu-oem": "Ubuntu OEM",
|
||||
"ubuntu-server": "Ubuntu-Server",
|
||||
"ubuntu-unity": "Ubuntu-Unity",
|
||||
"ubuntu-wsl": "Ubuntu WSL",
|
||||
"ubuntucinnamon": "Ubuntu-Cinnamon",
|
||||
"ubuntukylin": "Ubuntu-Kylin",
|
||||
"ubuntustudio": "Ubuntu-Studio",
|
||||
"xubuntu": "Xubuntu",
|
||||
}
|
||||
|
||||
|
||||
def make_disk_info(
|
||||
os_release: dict[str, str],
|
||||
arch: str,
|
||||
subarch: str,
|
||||
capproject: str,
|
||||
subproject: str,
|
||||
build_type: str,
|
||||
serial: str,
|
||||
) -> str:
|
||||
# os-release VERSION is _almost_ what goes into .disk/info...
|
||||
# it can be
|
||||
# VERSION="24.04.3 LTS (Noble Numbat)"
|
||||
# or
|
||||
# VERSION="25.10 (Questing Quokka)"
|
||||
# We want the Adjective Animal to be in quotes, not parentheses, e.g.
|
||||
# 'Ubuntu 24.04.3 LTS "Noble Numbat"'. This format is expected by casper
|
||||
# (which parses .disk/info to set up the live session) and the installer.
|
||||
version = os_release["VERSION"]
|
||||
version = version.replace("(", '"')
|
||||
version = version.replace(")", '"')
|
||||
|
||||
capsubproject = ""
|
||||
if subproject == "minimal":
|
||||
capsubproject = " Minimal"
|
||||
|
||||
fullarch = arch
|
||||
if subarch:
|
||||
fullarch += "+" + subarch
|
||||
|
||||
return f"{capproject}{capsubproject} {version} - {build_type} {fullarch} ({serial})"
|
||||
|
||||
|
||||
def make_vol_id(os_release: dict[str, str], arch: str, capproject: str) -> str:
|
||||
# ISO9660 volume IDs are limited to 32 characters. The volume ID format is
|
||||
# "CAPPROJECT VERSION ARCH", e.g. "Ubuntu 24.04.3 LTS amd64". Longer arch
|
||||
# names like ppc64el and riscv64 can push us over the limit, so we shorten
|
||||
# them here. This is why capproject names are also kept short (see the
|
||||
# comment above project_to_capproject_map).
|
||||
arch_for_volid_map = {
|
||||
"ppc64el": "ppc64",
|
||||
"riscv64": "riscv",
|
||||
}
|
||||
arch_for_volid = arch_for_volid_map.get(arch, arch)
|
||||
|
||||
# from
|
||||
# VERSION="24.04.3 LTS (Noble Numbat)"
|
||||
# or
|
||||
# VERSION="25.10 (Questing Quokka)"
|
||||
# we want "24.04.3 LTS" or "25.10", i.e. everything up to the first "(" (apart
|
||||
# from the whitespace).
|
||||
version = os_release["VERSION"].split("(")[0].strip()
|
||||
|
||||
volid = f"{capproject} {version} {arch_for_volid}"
|
||||
|
||||
# If still over 32 characters (e.g. long capproject + LTS version), fall
|
||||
# back to shorter forms. amd64 gets "x64" since it's widely recognized and
|
||||
# fits; other architectures just drop the arch entirely since multi-arch
|
||||
# ISOs are less common for non-amd64 platforms.
|
||||
if len(volid) > 32:
|
||||
if arch == "amd64":
|
||||
volid = f"{capproject} {version} x64"
|
||||
else:
|
||||
volid = f"{capproject} {version}"
|
||||
return volid
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option(
|
||||
"--project",
|
||||
type=str,
|
||||
required=True,
|
||||
)
|
||||
@click.option(
|
||||
"--subproject",
|
||||
type=str,
|
||||
default=None,
|
||||
)
|
||||
@click.option(
|
||||
"--arch",
|
||||
type=str,
|
||||
required=True,
|
||||
)
|
||||
@click.option(
|
||||
"--subarch",
|
||||
type=str,
|
||||
default=None,
|
||||
)
|
||||
@click.option(
|
||||
"--serial",
|
||||
type=str,
|
||||
default=time.strftime("%Y%m%d"),
|
||||
)
|
||||
@click.option(
|
||||
"--build-type",
|
||||
type=str,
|
||||
default="Daily",
|
||||
)
|
||||
@click.option(
|
||||
"--output-dir",
|
||||
type=click.Path(file_okay=False, resolve_path=True, path_type=pathlib.Path),
|
||||
required=True,
|
||||
help="working directory",
|
||||
)
|
||||
def main(
|
||||
project: str,
|
||||
subproject: str,
|
||||
arch: str,
|
||||
subarch: str,
|
||||
serial: str,
|
||||
build_type: str,
|
||||
output_dir: pathlib.Path,
|
||||
):
|
||||
output_dir.mkdir(exist_ok=True)
|
||||
capproject = project_to_capproject_map[project]
|
||||
|
||||
os_release = platform.freedesktop_os_release()
|
||||
|
||||
with output_dir.joinpath("disk-info").open("w") as fp:
|
||||
disk_info = make_disk_info(
|
||||
os_release,
|
||||
arch,
|
||||
subarch,
|
||||
capproject,
|
||||
subproject,
|
||||
build_type,
|
||||
serial,
|
||||
)
|
||||
print(f"disk_info: {disk_info!r}")
|
||||
fp.write(disk_info)
|
||||
|
||||
with output_dir.joinpath("vol-id").open("w") as fp:
|
||||
vol_id = make_vol_id(os_release, arch, capproject)
|
||||
print(f"vol_id: {vol_id!r} {len(vol_id)}")
|
||||
fp.write(vol_id)
|
||||
|
||||
with output_dir.joinpath("capproject").open("w") as fp:
|
||||
print(f"capproject: {capproject!r}")
|
||||
fp.write(capproject)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
221
live-build/isobuild
Executable file
221
live-build/isobuild
Executable 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()
|
||||
1
live-build/isobuilder/__init__.py
Normal file
1
live-build/isobuilder/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
#
|
||||
117
live-build/isobuilder/apt_state.py
Normal file
117
live-build/isobuilder/apt_state.py
Normal file
@ -0,0 +1,117 @@
|
||||
import dataclasses
|
||||
import os
|
||||
import pathlib
|
||||
import shutil
|
||||
import subprocess
|
||||
from typing import Iterator
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class PackageInfo:
|
||||
package: str
|
||||
filename: str
|
||||
architecture: str
|
||||
version: str
|
||||
|
||||
@property
|
||||
def spec(self) -> str:
|
||||
return f"{self.package}:{self.architecture}={self.version}"
|
||||
|
||||
|
||||
def check_proc(proc, ok_codes=(0,)) -> None:
|
||||
proc.wait()
|
||||
if proc.returncode not in ok_codes:
|
||||
raise Exception(f"{proc} failed")
|
||||
|
||||
|
||||
class AptStateManager:
|
||||
"""Maintain and use an apt state directory to access package info and debs."""
|
||||
|
||||
def __init__(self, logger, series: str, apt_dir: pathlib.Path):
|
||||
self.logger = logger
|
||||
self.series = series
|
||||
self.apt_root = apt_dir.joinpath("root")
|
||||
self.apt_conf_path = apt_dir.joinpath("apt.conf")
|
||||
|
||||
def _apt_env(self) -> dict[str, str]:
|
||||
return dict(os.environ, APT_CONFIG=str(self.apt_conf_path))
|
||||
|
||||
def setup(self, chroot: pathlib.Path):
|
||||
"""Set up the manager by copying the apt configuration from `chroot`."""
|
||||
for path in "etc/apt", "var/lib/apt":
|
||||
tgt = self.apt_root.joinpath(path)
|
||||
tgt.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copytree(chroot.joinpath(path), tgt, ignore_dangling_symlinks=True)
|
||||
self.apt_conf_path.write_text(f'Dir "{self.apt_root}/"; \n')
|
||||
with self.logger.logged("updating apt indices"):
|
||||
self.logger.run(["apt-get", "update"], env=self._apt_env())
|
||||
|
||||
def show(self, pkgs: list[str]) -> Iterator[PackageInfo]:
|
||||
"""Return information about the binary packages named by `pkgs`.
|
||||
|
||||
Parses apt-cache output, which uses RFC822-like format: field names
|
||||
followed by ": " and values, with multi-line values indented with
|
||||
leading whitespace. We skip continuation lines (starting with space)
|
||||
since PackageInfo only needs single-line fields.
|
||||
|
||||
The `fields` set (derived from PackageInfo's dataclass fields) acts as
|
||||
a filter - we only extract fields we care about, ignoring others like
|
||||
Description.
|
||||
"""
|
||||
proc = subprocess.Popen(
|
||||
["apt-cache", "-o", "APT::Cache::AllVersions=0", "show"] + pkgs,
|
||||
stdout=subprocess.PIPE,
|
||||
encoding="utf-8",
|
||||
env=self._apt_env(),
|
||||
)
|
||||
assert proc.stdout is not None
|
||||
fields = {f.name for f in dataclasses.fields(PackageInfo)}
|
||||
params: dict[str, str] = {}
|
||||
for line in proc.stdout:
|
||||
if line == "\n":
|
||||
yield PackageInfo(**params)
|
||||
params = {}
|
||||
continue
|
||||
if line.startswith(" "):
|
||||
continue
|
||||
field, value = line.split(": ", 1)
|
||||
field = field.lower()
|
||||
if field in fields:
|
||||
params[field] = value.strip()
|
||||
check_proc(proc)
|
||||
if params:
|
||||
yield PackageInfo(**params)
|
||||
|
||||
def download(self, rootdir: pathlib.Path, pkg_info: PackageInfo) -> None:
|
||||
"""Download the package specified by `pkg_info` under `rootdir`.
|
||||
|
||||
The package is saved to the same path under `rootdir` as it is
|
||||
at in the archive it comes from.
|
||||
"""
|
||||
target_dir = rootdir.joinpath(pkg_info.filename).parent
|
||||
target_dir.mkdir(parents=True, exist_ok=True)
|
||||
self.download_direct(pkg_info.spec, target_dir)
|
||||
|
||||
def download_direct(self, spec: str, target: pathlib.Path) -> None:
|
||||
"""Download the package specified by spec to target directory.
|
||||
|
||||
The package is downloaded using apt-get download and saved directly
|
||||
in the target directory.
|
||||
"""
|
||||
self.logger.run(
|
||||
["apt-get", "download", spec],
|
||||
cwd=target,
|
||||
check=True,
|
||||
env=self._apt_env(),
|
||||
)
|
||||
|
||||
def in_release_path(self) -> pathlib.Path:
|
||||
"""Return the path to the InRelease file.
|
||||
|
||||
This ignores all but the first path.
|
||||
Will raise Error if there isn't at least one match.
|
||||
"""
|
||||
[path] = self.apt_root.joinpath("var/lib/apt/lists").glob(
|
||||
f"*ubuntu.com*_dists_{self.series}_InRelease"
|
||||
)
|
||||
return path
|
||||
49
live-build/isobuilder/boot/__init__.py
Normal file
49
live-build/isobuilder/boot/__init__.py
Normal file
@ -0,0 +1,49 @@
|
||||
"""Boot configuration package for ISO builder.
|
||||
|
||||
This package contains architecture-specific boot configurators for building
|
||||
bootable ISOs for different architectures.
|
||||
"""
|
||||
|
||||
import pathlib
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..apt_state import AptStateManager
|
||||
from ..builder import Logger
|
||||
from .base import BaseBootConfigurator
|
||||
|
||||
|
||||
def make_boot_configurator_for_arch(
|
||||
arch: str,
|
||||
logger: "Logger",
|
||||
apt_state: "AptStateManager",
|
||||
workdir: pathlib.Path,
|
||||
iso_root: pathlib.Path,
|
||||
) -> "BaseBootConfigurator":
|
||||
"""Factory function to create boot configurator for a specific architecture."""
|
||||
match arch:
|
||||
case "amd64":
|
||||
from .amd64 import AMD64BootConfigurator
|
||||
|
||||
return AMD64BootConfigurator(logger, apt_state, workdir, iso_root)
|
||||
case "arm64":
|
||||
from .arm64 import ARM64BootConfigurator
|
||||
|
||||
return ARM64BootConfigurator(logger, apt_state, workdir, iso_root)
|
||||
case "ppc64el":
|
||||
from .ppc64el import PPC64ELBootConfigurator
|
||||
|
||||
return PPC64ELBootConfigurator(logger, apt_state, workdir, iso_root)
|
||||
case "riscv64":
|
||||
from .riscv64 import RISCV64BootConfigurator
|
||||
|
||||
return RISCV64BootConfigurator(logger, apt_state, workdir, iso_root)
|
||||
case "s390x":
|
||||
from .s390x import S390XBootConfigurator
|
||||
|
||||
return S390XBootConfigurator(logger, apt_state, workdir, iso_root)
|
||||
case _:
|
||||
raise ValueError(f"Unsupported architecture: {arch}")
|
||||
|
||||
|
||||
__all__ = ["make_boot_configurator_for_arch"]
|
||||
216
live-build/isobuilder/boot/amd64.py
Normal file
216
live-build/isobuilder/boot/amd64.py
Normal file
@ -0,0 +1,216 @@
|
||||
"""AMD64/x86_64 architecture boot configuration."""
|
||||
|
||||
import pathlib
|
||||
import shutil
|
||||
|
||||
from .base import default_kernel_params
|
||||
from .grub import copy_grub_modules
|
||||
from .uefi import UEFIBootConfigurator
|
||||
|
||||
|
||||
CALAMARES_PROJECTS = ["kubuntu", "lubuntu"]
|
||||
|
||||
|
||||
class AMD64BootConfigurator(UEFIBootConfigurator):
|
||||
"""Boot setup for AMD64/x86_64 architecture."""
|
||||
|
||||
efi_suffix = "x64"
|
||||
grub_target = "x86_64"
|
||||
arch = "amd64"
|
||||
|
||||
def mkisofs_opts(self) -> list[str | pathlib.Path]:
|
||||
# Boring mkisofs options that should be set somewhere architecture independent.
|
||||
opts: list[str | pathlib.Path] = ["-J", "-joliet-long", "-l"]
|
||||
|
||||
# Generalities on booting
|
||||
#
|
||||
# There is a 2x2 matrix of boot modes we care about: legacy or UEFI
|
||||
# boot modes and having the installer be on a cdrom or a disk. Booting
|
||||
# from cdrom uses the el torito standard and booting from disk expects
|
||||
# a MBR or GPT partition table.
|
||||
#
|
||||
# https://wiki.osdev.org/El-Torito has a lot more background on this.
|
||||
|
||||
# ## Set up the mkisofs options for legacy boot.
|
||||
|
||||
# Set the el torito boot image "name", i.e. the path on the ISO
|
||||
# containing the bootloader for legacy-cdrom boot.
|
||||
opts.extend(["-b", "boot/grub/i386-pc/eltorito.img"])
|
||||
|
||||
# Back in the day, el torito booting worked by emulating a floppy
|
||||
# drive. This hasn't been a useful way of operating for a long time.
|
||||
opts.append("-no-emul-boot")
|
||||
|
||||
# Misc options to make the legacy-cdrom boot work.
|
||||
opts.extend(["-boot-load-size", "4", "-boot-info-table", "--grub2-boot-info"])
|
||||
|
||||
# The bootloader to write to the MBR for legacy-disk boot.
|
||||
#
|
||||
# We use the grub stage1 bootloader, boot_hybrid.img, which then jumps
|
||||
# to the eltorito image based on the information xorriso provides it
|
||||
# via the --grub2-boot-info option.
|
||||
opts.extend(
|
||||
[
|
||||
"--grub2-mbr",
|
||||
self.scratch.joinpath("boot_hybrid.img"),
|
||||
]
|
||||
)
|
||||
|
||||
# ## Set up the mkisofs options for UEFI boot.
|
||||
opts.extend(self.get_uefi_mkisofs_opts())
|
||||
|
||||
return opts
|
||||
|
||||
def extract_files(self) -> None:
|
||||
with self.logger.logged("extracting AMD64 boot files"):
|
||||
|
||||
# Extract UEFI files (common with ARM64)
|
||||
self.extract_uefi_files()
|
||||
|
||||
# AMD64-specific: Add BIOS/legacy boot files
|
||||
with self.logger.logged("adding BIOS/legacy boot files"):
|
||||
grub_pc_pkg_dir = self.scratch.joinpath("grub-pc-pkg")
|
||||
self.download_and_extract_package("grub-pc-bin", grub_pc_pkg_dir)
|
||||
|
||||
grub_boot_dir = self.iso_root.joinpath("boot", "grub", "i386-pc")
|
||||
grub_boot_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
src_grub_dir = grub_pc_pkg_dir.joinpath("usr", "lib", "grub", "i386-pc")
|
||||
|
||||
shutil.copy(src_grub_dir.joinpath("eltorito.img"), grub_boot_dir)
|
||||
shutil.copy(src_grub_dir.joinpath("boot_hybrid.img"), self.scratch)
|
||||
|
||||
copy_grub_modules(
|
||||
grub_pc_pkg_dir,
|
||||
self.iso_root,
|
||||
"i386-pc",
|
||||
["*.mod", "*.lst", "*.o"],
|
||||
)
|
||||
|
||||
def generate_grub_config(self) -> str:
|
||||
"""Generate grub.cfg content for AMD64."""
|
||||
result = self.grub_header()
|
||||
|
||||
if self.project == "ubuntu-mini-iso":
|
||||
result += """\
|
||||
menuentry "Choose an Ubuntu version to install" {
|
||||
set gfxpayload=keep
|
||||
linux /casper/vmlinuz iso-chooser-menu ip=dhcp ---
|
||||
initrd /casper/initrd
|
||||
}
|
||||
"""
|
||||
return result
|
||||
|
||||
kernel_params = default_kernel_params(self.project)
|
||||
|
||||
# Main menu entry
|
||||
result += f"""\
|
||||
menuentry "Try or Install {self.humanproject}" {{
|
||||
set gfxpayload=keep
|
||||
linux /casper/vmlinuz {kernel_params}
|
||||
initrd /casper/initrd
|
||||
}}
|
||||
"""
|
||||
|
||||
# All but server get safe-graphics mode
|
||||
if self.project != "ubuntu-server":
|
||||
result += f"""\
|
||||
menuentry "{self.humanproject} (safe graphics)" {{
|
||||
set gfxpayload=keep
|
||||
linux /casper/vmlinuz nomodeset {kernel_params}
|
||||
initrd /casper/initrd
|
||||
}}
|
||||
"""
|
||||
|
||||
# ubiquity based projects get OEM mode
|
||||
if "maybe-ubiquity" in kernel_params:
|
||||
oem_kernel_params = kernel_params.replace(
|
||||
"maybe-ubiquity", "only-ubiquity oem-config/enable=true"
|
||||
)
|
||||
result += f"""\
|
||||
menuentry "OEM install (for manufacturers)" {{
|
||||
set gfxpayload=keep
|
||||
linux /casper/vmlinuz {oem_kernel_params}
|
||||
initrd /casper/initrd
|
||||
}}
|
||||
"""
|
||||
|
||||
# Calamares-based projects get OEM mode
|
||||
if self.project in CALAMARES_PROJECTS:
|
||||
result += f"""\
|
||||
menuentry "OEM install (for manufacturers)" {{
|
||||
set gfxpayload=keep
|
||||
linux /casper/vmlinuz {kernel_params} oem-config/enable=true
|
||||
initrd /casper/initrd
|
||||
}}
|
||||
"""
|
||||
|
||||
# Currently only server is built with HWE, hence no safe-graphics/OEM
|
||||
if self.hwe:
|
||||
result += f"""\
|
||||
menuentry "{self.humanproject} with the HWE kernel" {{
|
||||
set gfxpayload=keep
|
||||
linux /casper/hwe-vmlinuz {kernel_params}
|
||||
initrd /casper/hwe-initrd
|
||||
}}
|
||||
"""
|
||||
|
||||
# UEFI Entries (wrapped in grub_platform check for dual BIOS/UEFI support)
|
||||
uefi_menu_entries = self.uefi_menu_entries()
|
||||
|
||||
result += f"""\
|
||||
grub_platform
|
||||
if [ "$grub_platform" = "efi" ]; then
|
||||
{uefi_menu_entries}\
|
||||
fi
|
||||
"""
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def generate_loopback_config(grub_content: str) -> str:
|
||||
"""Derive loopback.cfg from grub.cfg content.
|
||||
|
||||
Strips the header (up to menu_color_highlight) and the UEFI
|
||||
trailer (from grub_platform to end), and adds iso-scan/filename
|
||||
to linux lines.
|
||||
"""
|
||||
lines = grub_content.split("\n")
|
||||
start_idx = 0
|
||||
for i, line in enumerate(lines):
|
||||
if "menu_color_highlight" in line:
|
||||
start_idx = i + 1
|
||||
break
|
||||
|
||||
end_idx = len(lines)
|
||||
for i, line in enumerate(lines):
|
||||
if "grub_platform" in line:
|
||||
end_idx = i
|
||||
break
|
||||
|
||||
loopback_lines = lines[start_idx:end_idx]
|
||||
loopback_lines = [
|
||||
(
|
||||
line.replace("---", "iso-scan/filename=${iso_path} ---")
|
||||
if "linux" in line
|
||||
else line
|
||||
)
|
||||
for line in loopback_lines
|
||||
]
|
||||
|
||||
return "\n".join(loopback_lines)
|
||||
|
||||
def make_bootable(
|
||||
self,
|
||||
project: str,
|
||||
capproject: str,
|
||||
subarch: str,
|
||||
hwe: bool,
|
||||
) -> None:
|
||||
"""Make the ISO bootable, including generating loopback.cfg."""
|
||||
super().make_bootable(project, capproject, subarch, hwe)
|
||||
grub_cfg = self.iso_root.joinpath("boot", "grub", "grub.cfg")
|
||||
grub_content = grub_cfg.read_text()
|
||||
self.iso_root.joinpath("boot", "grub", "loopback.cfg").write_text(
|
||||
self.generate_loopback_config(grub_content)
|
||||
)
|
||||
76
live-build/isobuilder/boot/arm64.py
Normal file
76
live-build/isobuilder/boot/arm64.py
Normal file
@ -0,0 +1,76 @@
|
||||
"""ARM 64-bit architecture boot configuration."""
|
||||
|
||||
import pathlib
|
||||
|
||||
from .uefi import UEFIBootConfigurator
|
||||
from .base import default_kernel_params
|
||||
|
||||
|
||||
class ARM64BootConfigurator(UEFIBootConfigurator):
|
||||
"""Boot setup for ARM 64-bit architecture."""
|
||||
|
||||
efi_suffix = "aa64"
|
||||
grub_target = "arm64"
|
||||
arch = "arm64"
|
||||
|
||||
def mkisofs_opts(self) -> list[str | pathlib.Path]:
|
||||
"""Return mkisofs options for ARM64."""
|
||||
opts: list[str | pathlib.Path] = [
|
||||
"-J",
|
||||
"-joliet-long",
|
||||
"-l",
|
||||
"-c",
|
||||
"boot/boot.cat",
|
||||
]
|
||||
# Add common UEFI options
|
||||
opts.extend(self.get_uefi_mkisofs_opts())
|
||||
# ARM64-specific: partition cylinder alignment
|
||||
opts.extend(["-partition_cyl_align", "all"])
|
||||
return opts
|
||||
|
||||
def extract_files(self) -> None:
|
||||
"""Download and extract bootloader packages for ARM64."""
|
||||
with self.logger.logged("extracting ARM64 boot files"):
|
||||
self.extract_uefi_files()
|
||||
|
||||
def generate_grub_config(self) -> str:
|
||||
"""Generate grub.cfg for ARM64."""
|
||||
kernel_params = default_kernel_params(self.project)
|
||||
|
||||
result = self.grub_header()
|
||||
|
||||
# ARM64-specific: Snapdragon workarounds
|
||||
result += f"""\
|
||||
set cmdline=
|
||||
smbios --type 4 --get-string 5 --set proc_version
|
||||
regexp "Snapdragon.*" "$proc_version"
|
||||
if [ $? = 0 ]; then
|
||||
# Work around Snapdragon X firmware bug. cutmem is not allowed in lockdown mode.
|
||||
if [ $lockdown != "y" ]; then
|
||||
cutmem 0x8800000000 0x8fffffffff
|
||||
fi
|
||||
# arm64.nopauth works around 8cx Gen 3 firmware bug
|
||||
cmdline="clk_ignore_unused pd_ignore_unused arm64.nopauth"
|
||||
fi
|
||||
|
||||
menuentry "Try or Install {self.humanproject}" {{
|
||||
set gfxpayload=keep
|
||||
linux /casper/vmlinuz $cmdline {kernel_params} console=tty0
|
||||
initrd /casper/initrd
|
||||
}}
|
||||
"""
|
||||
|
||||
# HWE kernel option if available
|
||||
result += self.hwe_menu_entry(
|
||||
"vmlinuz",
|
||||
f"{kernel_params} console=tty0",
|
||||
extra_params="$cmdline ",
|
||||
)
|
||||
|
||||
# Note: ARM64 HWE also includes $dtb in the original shell script,
|
||||
# but it's not actually set anywhere in the grub.cfg, so we omit it here
|
||||
|
||||
# UEFI Entries (ARM64 is UEFI-only, no grub_platform check needed)
|
||||
result += self.uefi_menu_entries()
|
||||
|
||||
return result
|
||||
98
live-build/isobuilder/boot/base.py
Normal file
98
live-build/isobuilder/boot/base.py
Normal file
@ -0,0 +1,98 @@
|
||||
"""Base classes and helper functions for boot configuration."""
|
||||
|
||||
import pathlib
|
||||
import subprocess
|
||||
import tempfile
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from ..builder import Logger
|
||||
from ..apt_state import AptStateManager
|
||||
|
||||
|
||||
def default_kernel_params(project: str) -> str:
|
||||
if project == "ubuntukylin":
|
||||
return (
|
||||
"file=/cdrom/preseed/ubuntu.seed locale=zh_CN "
|
||||
"keyboard-configuration/layoutcode?=cn quiet splash --- "
|
||||
)
|
||||
if project == "ubuntu-server":
|
||||
return " --- "
|
||||
else:
|
||||
return " --- quiet splash"
|
||||
|
||||
|
||||
class BaseBootConfigurator(ABC):
|
||||
"""Abstract base class for architecture-specific boot configurators.
|
||||
|
||||
Subclasses must implement:
|
||||
- extract_files(): Download and extract bootloader packages
|
||||
- mkisofs_opts(): Return mkisofs command-line options
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
logger: Logger,
|
||||
apt_state: AptStateManager,
|
||||
workdir: pathlib.Path,
|
||||
iso_root: pathlib.Path,
|
||||
) -> None:
|
||||
self.logger = logger
|
||||
self.apt_state = apt_state
|
||||
self.scratch = workdir.joinpath("boot-stuff")
|
||||
self.iso_root = iso_root
|
||||
|
||||
def download_and_extract_package(
|
||||
self, pkg_name: str, target_dir: pathlib.Path
|
||||
) -> None:
|
||||
"""Download a Debian package and extract its contents to target directory."""
|
||||
self.logger.log(f"downloading and extracting {pkg_name}")
|
||||
target_dir.mkdir(exist_ok=True, parents=True)
|
||||
with tempfile.TemporaryDirectory() as tdir_str:
|
||||
tdir = pathlib.Path(tdir_str)
|
||||
self.apt_state.download_direct(pkg_name, tdir)
|
||||
[deb] = tdir.glob("*.deb")
|
||||
dpkg_proc = subprocess.Popen(
|
||||
["dpkg-deb", "--fsys-tarfile", deb], stdout=subprocess.PIPE
|
||||
)
|
||||
tar_proc = subprocess.Popen(
|
||||
["tar", "xf", "-", "-C", target_dir], stdin=dpkg_proc.stdout
|
||||
)
|
||||
assert dpkg_proc.stdout is not None
|
||||
dpkg_proc.stdout.close()
|
||||
tar_proc.communicate()
|
||||
|
||||
@abstractmethod
|
||||
def extract_files(self) -> None:
|
||||
"""Download and extract bootloader packages to the boot tree.
|
||||
|
||||
Each architecture must implement this to set up its specific bootloader files.
|
||||
"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def mkisofs_opts(self) -> list[str | pathlib.Path]:
|
||||
"""Return mkisofs command-line options for this architecture.
|
||||
|
||||
Returns:
|
||||
List of command-line options to pass to mkisofs/xorriso.
|
||||
"""
|
||||
...
|
||||
|
||||
def post_process_iso(self, iso_path: pathlib.Path) -> None:
|
||||
"""Post-process the ISO image after xorriso creates it."""
|
||||
|
||||
def make_bootable(
|
||||
self,
|
||||
project: str,
|
||||
capproject: str,
|
||||
subarch: str,
|
||||
hwe: bool,
|
||||
) -> None:
|
||||
"""Make the ISO bootable by extracting bootloader files."""
|
||||
self.project = project
|
||||
self.humanproject = capproject.replace("-", " ")
|
||||
self.subarch = subarch
|
||||
self.hwe = hwe
|
||||
self.scratch.mkdir(exist_ok=True)
|
||||
with self.logger.logged("configuring boot"):
|
||||
self.extract_files()
|
||||
104
live-build/isobuilder/boot/grub.py
Normal file
104
live-build/isobuilder/boot/grub.py
Normal file
@ -0,0 +1,104 @@
|
||||
"""GRUB boot configuration for multiple architectures."""
|
||||
|
||||
import pathlib
|
||||
import shutil
|
||||
from abc import abstractmethod
|
||||
|
||||
from .base import BaseBootConfigurator
|
||||
|
||||
|
||||
def copy_grub_common_files(grub_pkg_dir: pathlib.Path, iso_root: pathlib.Path) -> None:
|
||||
fonts_dir = iso_root.joinpath("boot", "grub", "fonts")
|
||||
fonts_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
src = grub_pkg_dir.joinpath("usr", "share", "grub", "unicode.pf2")
|
||||
dst = fonts_dir.joinpath("unicode.pf2")
|
||||
shutil.copy(src, dst)
|
||||
|
||||
|
||||
def copy_grub_modules(
|
||||
grub_pkg_dir: pathlib.Path,
|
||||
iso_root: pathlib.Path,
|
||||
grub_target: str,
|
||||
patterns: list[str],
|
||||
) -> None:
|
||||
"""Copy GRUB module files matching given patterns from src to dest."""
|
||||
src_dir = grub_pkg_dir.joinpath("usr", "lib", "grub", grub_target)
|
||||
dest_dir = iso_root.joinpath("boot", "grub", grub_target)
|
||||
dest_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
for pat in patterns:
|
||||
for file in src_dir.glob(pat):
|
||||
shutil.copy(file, dest_dir)
|
||||
|
||||
|
||||
class GrubBootConfigurator(BaseBootConfigurator):
|
||||
"""Base class for architectures that use GRUB (all except S390X).
|
||||
|
||||
Common GRUB functionality shared across AMD64, ARM64, PPC64EL, and RISC-V64.
|
||||
Subclasses must implement generate_grub_config().
|
||||
"""
|
||||
|
||||
def grub_header(self, include_loadfont: bool = True) -> str:
|
||||
"""Return common GRUB config header (timeout, colors).
|
||||
|
||||
Args:
|
||||
include_loadfont: Whether to include 'loadfont unicode'
|
||||
(not needed for RISC-V)
|
||||
"""
|
||||
result = "set timeout=30\n\n"
|
||||
if include_loadfont:
|
||||
result += "loadfont unicode\n\n"
|
||||
result += """\
|
||||
set menu_color_normal=white/black
|
||||
set menu_color_highlight=black/light-gray
|
||||
|
||||
"""
|
||||
return result
|
||||
|
||||
def hwe_menu_entry(
|
||||
self,
|
||||
kernel_name: str,
|
||||
kernel_params: str,
|
||||
extra_params: str = "",
|
||||
) -> str:
|
||||
"""Return HWE kernel menu entry if HWE is enabled.
|
||||
|
||||
Args:
|
||||
kernel_name: Kernel binary name (vmlinuz or vmlinux)
|
||||
kernel_params: Kernel parameters to append
|
||||
extra_params: Additional parameters (e.g., console=tty0, $cmdline)
|
||||
"""
|
||||
if not self.hwe:
|
||||
return ""
|
||||
return f"""\
|
||||
menuentry "{self.humanproject} with the HWE kernel" {{
|
||||
set gfxpayload=keep
|
||||
linux /casper/hwe-{kernel_name} {extra_params}{kernel_params}
|
||||
initrd /casper/hwe-initrd
|
||||
}}
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def generate_grub_config(self) -> str:
|
||||
"""Generate grub.cfg content.
|
||||
|
||||
Each GRUB-based architecture must implement this to return the
|
||||
GRUB configuration.
|
||||
"""
|
||||
...
|
||||
|
||||
def make_bootable(
|
||||
self,
|
||||
project: str,
|
||||
capproject: str,
|
||||
subarch: str,
|
||||
hwe: bool,
|
||||
) -> None:
|
||||
"""Make the ISO bootable by extracting files and generating GRUB config."""
|
||||
super().make_bootable(project, capproject, subarch, hwe)
|
||||
with self.logger.logged("generating grub config"):
|
||||
content = self.generate_grub_config()
|
||||
grub_dir = self.iso_root.joinpath("boot", "grub")
|
||||
grub_dir.mkdir(parents=True, exist_ok=True)
|
||||
grub_dir.joinpath("grub.cfg").write_text(content)
|
||||
74
live-build/isobuilder/boot/ppc64el.py
Normal file
74
live-build/isobuilder/boot/ppc64el.py
Normal file
@ -0,0 +1,74 @@
|
||||
"""PowerPC 64-bit Little Endian architecture boot configuration."""
|
||||
|
||||
import pathlib
|
||||
import shutil
|
||||
|
||||
from .grub import (
|
||||
copy_grub_common_files,
|
||||
copy_grub_modules,
|
||||
GrubBootConfigurator,
|
||||
)
|
||||
from .base import default_kernel_params
|
||||
|
||||
|
||||
class PPC64ELBootConfigurator(GrubBootConfigurator):
|
||||
"""Boot setup for PowerPC 64-bit Little Endian architecture."""
|
||||
|
||||
def mkisofs_opts(self) -> list[str | pathlib.Path]:
|
||||
"""Return mkisofs options for PPC64EL."""
|
||||
return []
|
||||
|
||||
def extract_files(self) -> None:
|
||||
"""Download and extract bootloader packages for PPC64EL."""
|
||||
self.logger.log("extracting PPC64EL boot files")
|
||||
|
||||
grub_pkg_dir = self.scratch.joinpath("grub-pkg")
|
||||
|
||||
# Download and extract bootloader packages
|
||||
self.download_and_extract_package("grub2-common", grub_pkg_dir)
|
||||
self.download_and_extract_package("grub-ieee1275-bin", grub_pkg_dir)
|
||||
|
||||
# Add common files for GRUB to tree
|
||||
copy_grub_common_files(grub_pkg_dir, self.iso_root)
|
||||
|
||||
# Add IEEE1275 ppc boot files
|
||||
ppc_dir = self.iso_root.joinpath("ppc")
|
||||
ppc_dir.mkdir()
|
||||
|
||||
src_grub_dir = grub_pkg_dir.joinpath("usr", "lib", "grub", "powerpc-ieee1275")
|
||||
|
||||
# Copy bootinfo.txt to ppc directory
|
||||
shutil.copy(
|
||||
src_grub_dir.joinpath("bootinfo.txt"), ppc_dir.joinpath("bootinfo.txt")
|
||||
)
|
||||
|
||||
# Copy eltorito.elf to boot/grub as powerpc.elf
|
||||
shutil.copy(
|
||||
src_grub_dir.joinpath("eltorito.elf"),
|
||||
self.iso_root.joinpath("boot", "grub", "powerpc.elf"),
|
||||
)
|
||||
|
||||
# Copy GRUB modules
|
||||
copy_grub_modules(
|
||||
grub_pkg_dir, self.iso_root, "powerpc-ieee1275", ["*.mod", "*.lst"]
|
||||
)
|
||||
|
||||
def generate_grub_config(self) -> str:
|
||||
"""Generate grub.cfg for PPC64EL."""
|
||||
kernel_params = default_kernel_params(self.project)
|
||||
|
||||
result = self.grub_header()
|
||||
|
||||
# Main menu entry
|
||||
result += f"""\
|
||||
menuentry "Try or Install {self.humanproject}" {{
|
||||
set gfxpayload=keep
|
||||
linux /casper/vmlinux quiet {kernel_params}
|
||||
initrd /casper/initrd
|
||||
}}
|
||||
"""
|
||||
|
||||
# HWE kernel option if available
|
||||
result += self.hwe_menu_entry("vmlinux", kernel_params, extra_params="quiet ")
|
||||
|
||||
return result
|
||||
207
live-build/isobuilder/boot/riscv64.py
Normal file
207
live-build/isobuilder/boot/riscv64.py
Normal file
@ -0,0 +1,207 @@
|
||||
"""RISC-V 64-bit architecture boot configuration."""
|
||||
|
||||
import pathlib
|
||||
import shutil
|
||||
|
||||
from .grub import GrubBootConfigurator, copy_grub_common_files, copy_grub_modules
|
||||
|
||||
|
||||
def copy_unsigned_monolithic_grub(
|
||||
grub_pkg_dir: pathlib.Path,
|
||||
efi_suffix: str,
|
||||
grub_target: str,
|
||||
iso_root: pathlib.Path,
|
||||
) -> None:
|
||||
efi_boot_dir = iso_root.joinpath("EFI", "boot")
|
||||
efi_boot_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
shutil.copy(
|
||||
grub_pkg_dir.joinpath(
|
||||
"usr",
|
||||
"lib",
|
||||
"grub",
|
||||
grub_target,
|
||||
"monolithic",
|
||||
f"gcd{efi_suffix}.efi",
|
||||
),
|
||||
efi_boot_dir.joinpath(f"boot{efi_suffix}.efi"),
|
||||
)
|
||||
|
||||
copy_grub_modules(grub_pkg_dir, iso_root, grub_target, ["*.mod", "*.lst"])
|
||||
|
||||
|
||||
class RISCV64BootConfigurator(GrubBootConfigurator):
|
||||
"""Boot setup for RISC-V 64-bit architecture."""
|
||||
|
||||
def mkisofs_opts(self) -> list[str | pathlib.Path]:
|
||||
"""Return mkisofs options for RISC-V64."""
|
||||
efi_img = self.scratch.joinpath("efi.img")
|
||||
|
||||
return [
|
||||
"-joliet",
|
||||
"on",
|
||||
"-compliance",
|
||||
"joliet_long_names",
|
||||
"--append_partition",
|
||||
"2",
|
||||
"0xef",
|
||||
efi_img,
|
||||
"-boot_image",
|
||||
"any",
|
||||
"partition_offset=10240",
|
||||
"-boot_image",
|
||||
"any",
|
||||
"partition_cyl_align=all",
|
||||
"-boot_image",
|
||||
"any",
|
||||
"efi_path=--interval:appended_partition_2:all::",
|
||||
"-boot_image",
|
||||
"any",
|
||||
"appended_part_as=gpt",
|
||||
"-boot_image",
|
||||
"any",
|
||||
"cat_path=/boot/boot.cat",
|
||||
"-fs",
|
||||
"64m",
|
||||
]
|
||||
|
||||
def extract_files(self) -> None:
|
||||
"""Download and extract bootloader packages for RISC-V64."""
|
||||
self.logger.log("extracting RISC-V64 boot files")
|
||||
u_boot_dir = self.scratch.joinpath("u-boot-sifive")
|
||||
|
||||
grub_pkg_dir = self.scratch.joinpath("grub-pkg")
|
||||
|
||||
# Download and extract bootloader packages
|
||||
self.download_and_extract_package("grub2-common", grub_pkg_dir)
|
||||
self.download_and_extract_package("grub-efi-riscv64-bin", grub_pkg_dir)
|
||||
self.download_and_extract_package("grub-efi-riscv64-unsigned", grub_pkg_dir)
|
||||
self.download_and_extract_package("u-boot-sifive", u_boot_dir)
|
||||
|
||||
# Add GRUB to tree
|
||||
copy_grub_common_files(grub_pkg_dir, self.iso_root)
|
||||
|
||||
copy_unsigned_monolithic_grub(
|
||||
grub_pkg_dir, "riscv64", "riscv64-efi", self.iso_root
|
||||
)
|
||||
|
||||
# Extract DTBs to tree
|
||||
self.logger.log("extracting device tree files")
|
||||
kernel_layer = self.scratch.joinpath("kernel-layer")
|
||||
squashfs_path = self.iso_root.joinpath(
|
||||
"casper", "ubuntu-server-minimal.squashfs"
|
||||
)
|
||||
|
||||
# Extract device tree firmware from squashfs
|
||||
self.logger.run(
|
||||
[
|
||||
"unsquashfs",
|
||||
"-no-xattrs",
|
||||
"-d",
|
||||
kernel_layer,
|
||||
squashfs_path,
|
||||
"usr/lib/firmware",
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
|
||||
# Copy DTBs if they exist
|
||||
dtb_dir = self.iso_root.joinpath("dtb")
|
||||
dtb_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
firmware_dir = kernel_layer.joinpath("usr", "lib", "firmware")
|
||||
|
||||
for dtb_file in firmware_dir.glob("*/device-tree/*"):
|
||||
if dtb_file.is_file():
|
||||
shutil.copy(dtb_file, dtb_dir)
|
||||
|
||||
# Create ESP image with GRUB and dtbs
|
||||
efi_img = self.scratch.joinpath("efi.img")
|
||||
self.logger.run(
|
||||
["mkfs.msdos", "-n", "ESP", "-C", "-v", efi_img, "32768"], check=True
|
||||
)
|
||||
|
||||
# Add EFI files to ESP
|
||||
efi_dir = self.iso_root.joinpath("EFI")
|
||||
self.logger.run(["mcopy", "-s", "-i", efi_img, efi_dir, "::/."], check=True)
|
||||
|
||||
# Add DTBs to ESP
|
||||
self.logger.run(["mcopy", "-s", "-i", efi_img, dtb_dir, "::/."], check=True)
|
||||
|
||||
def generate_grub_config(self) -> str:
|
||||
"""Generate grub.cfg for RISC-V64."""
|
||||
result = self.grub_header(include_loadfont=False)
|
||||
|
||||
# Main menu entry
|
||||
result += f"""\
|
||||
menuentry "Try or Install {self.humanproject}" {{
|
||||
set gfxpayload=keep
|
||||
linux /casper/vmlinux efi=debug sysctl.kernel.watchdog_thresh=60 ---
|
||||
initrd /casper/initrd
|
||||
}}
|
||||
"""
|
||||
|
||||
# HWE kernel option if available
|
||||
result += self.hwe_menu_entry(
|
||||
"vmlinux",
|
||||
"---",
|
||||
extra_params="efi=debug sysctl.kernel.watchdog_thresh=60 ",
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
def post_process_iso(self, iso_path: pathlib.Path) -> None:
|
||||
"""Add GPT partitions with U-Boot for SiFive Unmatched board.
|
||||
|
||||
The SiFive Unmatched board needs a GPT table containing U-Boot in
|
||||
order to boot. U-Boot does not currently support booting from a CD,
|
||||
so the GPT table also contains an entry pointing to the ESP so that
|
||||
U-Boot can find it.
|
||||
"""
|
||||
u_boot_dir = self.scratch.joinpath(
|
||||
"u-boot-sifive", "usr", "lib", "u-boot", "sifive_unmatched"
|
||||
)
|
||||
self.logger.run(
|
||||
[
|
||||
"sgdisk",
|
||||
iso_path,
|
||||
"--set-alignment=2",
|
||||
"-d",
|
||||
"1",
|
||||
"-n",
|
||||
"1:2082:10273",
|
||||
"-c",
|
||||
"1:loader2",
|
||||
"-t",
|
||||
"1:2E54B353-1271-4842-806F-E436D6AF6985",
|
||||
"-n",
|
||||
"3:10274:12321",
|
||||
"-c",
|
||||
"3:loader1",
|
||||
"-t",
|
||||
"3:5B193300-FC78-40CD-8002-E86C45580B47",
|
||||
"-c",
|
||||
"2:ESP",
|
||||
"-r=2:3",
|
||||
],
|
||||
)
|
||||
self.logger.run(
|
||||
[
|
||||
"dd",
|
||||
f"if={u_boot_dir / 'u-boot.itb'}",
|
||||
f"of={iso_path}",
|
||||
"bs=512",
|
||||
"seek=2082",
|
||||
"conv=notrunc",
|
||||
],
|
||||
)
|
||||
self.logger.run(
|
||||
[
|
||||
"dd",
|
||||
f"if={u_boot_dir / 'u-boot-spl.bin'}",
|
||||
f"of={iso_path}",
|
||||
"bs=512",
|
||||
"seek=10274",
|
||||
"conv=notrunc",
|
||||
],
|
||||
)
|
||||
206
live-build/isobuilder/boot/s390x.py
Normal file
206
live-build/isobuilder/boot/s390x.py
Normal file
@ -0,0 +1,206 @@
|
||||
"""IBM S/390 architecture boot configuration."""
|
||||
|
||||
import pathlib
|
||||
import shutil
|
||||
import struct
|
||||
|
||||
from .base import BaseBootConfigurator
|
||||
|
||||
|
||||
README_dot_boot = """\
|
||||
About the S/390 installation CD
|
||||
===============================
|
||||
|
||||
It is possible to "boot" the installation system off this CD using
|
||||
the files provided in the /boot directory.
|
||||
|
||||
Although you can boot the installer from this CD, the installation
|
||||
itself is *not* actually done from the CD. Once the initrd is loaded,
|
||||
the installer will ask you to configure your network connection and
|
||||
uses the network-console component to allow you to continue the
|
||||
installation over SSH. The rest of the installation is done over the
|
||||
network: all installer components and Debian packages are retrieved
|
||||
from a mirror.
|
||||
|
||||
Instead of SSH, one can also use the ASCII terminal available in HMC.
|
||||
|
||||
Exporting full .iso contents (including the hidden .disk directory)
|
||||
allows one to use the result as a valid mirror for installation.
|
||||
"""
|
||||
|
||||
ubuntu_dot_exec = """\
|
||||
/* REXX EXEC TO IPL Ubuntu for */
|
||||
/* z Systems FROM THE VM READER. */
|
||||
/* */
|
||||
'CP CLOSE RDR'
|
||||
'PURGE RDR ALL'
|
||||
'SPOOL PUNCH * RDR'
|
||||
'PUNCH KERNEL UBUNTU * (NOHEADER'
|
||||
'PUNCH PARMFILE UBUNTU * (NOHEADER'
|
||||
'PUNCH INITRD UBUNTU * (NOHEADER'
|
||||
'CHANGE RDR ALL KEEP NOHOLD'
|
||||
'CP IPL 000C CLEAR'
|
||||
"""
|
||||
|
||||
ubuntu_dot_ins = """\
|
||||
* Ubuntu for IBM Z (default kernel)
|
||||
kernel.ubuntu 0x00000000
|
||||
initrd.off 0x0001040c
|
||||
initrd.siz 0x00010414
|
||||
parmfile.ubuntu 0x00010480
|
||||
initrd.ubuntu 0x01000000
|
||||
"""
|
||||
|
||||
|
||||
def gen_s390_cd_kernel(
|
||||
kernel: pathlib.Path, initrd: pathlib.Path, cmdline: str, outfile: pathlib.Path
|
||||
) -> None:
|
||||
"""Generate a bootable S390X CD kernel image.
|
||||
|
||||
This is a Python translation of gen-s390-cd-kernel.pl from debian-cd.
|
||||
It creates a bootable image for S/390 architecture by combining kernel,
|
||||
initrd, and boot parameters in a specific format.
|
||||
"""
|
||||
# Calculate sizes
|
||||
initrd_size = initrd.stat().st_size
|
||||
|
||||
# The initrd is placed at a fixed offset of 16 MiB
|
||||
initrd_offset = 0x1000000
|
||||
|
||||
# Calculate total boot image size (rounded up to 4K blocks)
|
||||
boot_size = ((initrd_offset + initrd_size) >> 12) + 1
|
||||
boot_size = boot_size << 12
|
||||
|
||||
# Validate cmdline length (max 896 bytes)
|
||||
if len(cmdline) >= 896:
|
||||
raise ValueError(f"Kernel commandline too long ({len(cmdline)} bytes)")
|
||||
|
||||
# Create output file and fill with zeros
|
||||
with outfile.open("wb") as out_fh:
|
||||
# Fill entire file with zeros
|
||||
out_fh.write(b"\x00" * boot_size)
|
||||
|
||||
# Copy kernel to offset 0
|
||||
out_fh.seek(0)
|
||||
with kernel.open("rb") as kernel_fh:
|
||||
out_fh.write(kernel_fh.read())
|
||||
|
||||
# Copy initrd to offset 0x1000000 (16 MiB)
|
||||
out_fh.seek(initrd_offset)
|
||||
with initrd.open("rb") as initrd_fh:
|
||||
out_fh.write(initrd_fh.read())
|
||||
|
||||
# Write boot loader control value at offset 4
|
||||
# This tells the S/390 boot loader where to find the kernel
|
||||
out_fh.seek(4)
|
||||
out_fh.write(struct.pack("!I", 0x80010000))
|
||||
|
||||
# Write kernel command line at offset 0x10480
|
||||
out_fh.seek(0x10480)
|
||||
out_fh.write(cmdline.encode("utf-8"))
|
||||
|
||||
# Write initrd parameters
|
||||
# Initrd offset at 0x1040C
|
||||
out_fh.seek(0x1040C)
|
||||
out_fh.write(struct.pack("!I", initrd_offset))
|
||||
|
||||
# Initrd size at 0x10414
|
||||
out_fh.seek(0x10414)
|
||||
out_fh.write(struct.pack("!I", initrd_size))
|
||||
|
||||
|
||||
class S390XBootConfigurator(BaseBootConfigurator):
|
||||
"""Boot setup for IBM S/390 architecture."""
|
||||
|
||||
def mkisofs_opts(self) -> list[str | pathlib.Path]:
|
||||
"""Return mkisofs options for S390X."""
|
||||
return [
|
||||
"-J",
|
||||
"-no-emul-boot",
|
||||
"-b",
|
||||
"boot/ubuntu.ikr",
|
||||
]
|
||||
|
||||
def extract_files(self) -> None:
|
||||
"""Set up boot files for S390X."""
|
||||
self.logger.log("extracting S390X boot files")
|
||||
boot_dir = self.iso_root.joinpath("boot")
|
||||
boot_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Copy static .ins & exec scripts, docs from data directory
|
||||
self.iso_root.joinpath("README.boot").write_text(README_dot_boot)
|
||||
boot_dir.joinpath("ubuntu.exec").write_text(ubuntu_dot_exec)
|
||||
boot_dir.joinpath("ubuntu.ins").write_text(ubuntu_dot_ins)
|
||||
|
||||
# Move kernel image to the name used in .ins & exec scripts
|
||||
kernel_src = self.iso_root.joinpath("casper", "vmlinuz")
|
||||
kernel_dst = boot_dir.joinpath("kernel.ubuntu")
|
||||
kernel_src.replace(kernel_dst)
|
||||
|
||||
# Move initrd to the name used in .ins & exec scripts
|
||||
initrd_src = self.iso_root.joinpath("casper", "initrd")
|
||||
initrd_dst = boot_dir.joinpath("initrd.ubuntu")
|
||||
initrd_src.replace(initrd_dst)
|
||||
|
||||
# Compute initrd offset & size, store in files used by .ins & exec scripts
|
||||
# Offset is always 0x1000000 (16 MiB)
|
||||
initrd_offset_file = boot_dir.joinpath("initrd.off")
|
||||
with initrd_offset_file.open("wb") as f:
|
||||
f.write(struct.pack("!I", 0x1000000))
|
||||
|
||||
# Size is the actual size of the initrd
|
||||
initrd_size = initrd_dst.stat().st_size
|
||||
initrd_size_file = boot_dir.joinpath("initrd.siz")
|
||||
with initrd_size_file.open("wb") as f:
|
||||
f.write(struct.pack("!I", initrd_size))
|
||||
|
||||
# Compute cmdline, store in parmfile used by .ins & exec scripts
|
||||
parmfile = boot_dir.joinpath("parmfile.ubuntu")
|
||||
with parmfile.open("w") as f:
|
||||
f.write(" --- ")
|
||||
|
||||
# Generate secondary top-level ubuntu.ins file
|
||||
# This transforms lines not starting with * by prepending "boot/"
|
||||
ubuntu_ins_src = boot_dir.joinpath("ubuntu.ins")
|
||||
ubuntu_ins_dst = self.iso_root.joinpath("ubuntu.ins")
|
||||
if ubuntu_ins_src.exists():
|
||||
self.logger.run(
|
||||
["sed", "-e", "s,^[^*],boot/&,g", ubuntu_ins_src],
|
||||
stdout=ubuntu_ins_dst.open("w"),
|
||||
check=True,
|
||||
)
|
||||
|
||||
# Generate QEMU-KVM boot image using gen_s390_cd_kernel
|
||||
cmdline = parmfile.read_text().strip()
|
||||
ikr_file = boot_dir.joinpath("ubuntu.ikr")
|
||||
gen_s390_cd_kernel(kernel_dst, initrd_dst, cmdline, ikr_file)
|
||||
|
||||
# Extract bootloader signing certificate
|
||||
installed_pem = pathlib.Path("/usr/lib/s390-tools/stage3.pem")
|
||||
squashfs_root = self.iso_root.joinpath("squashfs-root")
|
||||
squashfs_path = self.iso_root.joinpath(
|
||||
"casper", "ubuntu-server-minimal.squashfs"
|
||||
)
|
||||
|
||||
if squashfs_path.exists():
|
||||
self.logger.run(
|
||||
[
|
||||
"unsquashfs",
|
||||
"-no-xattrs",
|
||||
"-i",
|
||||
"-d",
|
||||
squashfs_root,
|
||||
squashfs_path,
|
||||
installed_pem,
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
|
||||
# Move certificate to iso root
|
||||
cert_src = squashfs_root.joinpath(str(installed_pem).lstrip("/"))
|
||||
cert_dst = self.iso_root.joinpath("ubuntu.pem")
|
||||
if cert_src.exists():
|
||||
cert_src.replace(cert_dst)
|
||||
|
||||
# Clean up squashfs extraction
|
||||
shutil.rmtree(squashfs_root)
|
||||
168
live-build/isobuilder/boot/uefi.py
Normal file
168
live-build/isobuilder/boot/uefi.py
Normal file
@ -0,0 +1,168 @@
|
||||
"""UEFI boot configuration for AMD64 and ARM64 architectures."""
|
||||
|
||||
import pathlib
|
||||
import shutil
|
||||
|
||||
from ..builder import Logger
|
||||
from .grub import copy_grub_common_files, GrubBootConfigurator
|
||||
|
||||
|
||||
def copy_signed_shim_grub(
|
||||
shim_pkg_dir: pathlib.Path,
|
||||
grub_pkg_dir: pathlib.Path,
|
||||
efi_suffix: str,
|
||||
grub_target: str,
|
||||
iso_root: pathlib.Path,
|
||||
) -> None:
|
||||
efi_boot_dir = iso_root.joinpath("EFI", "boot")
|
||||
efi_boot_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
shutil.copy(
|
||||
shim_pkg_dir.joinpath(
|
||||
"usr", "lib", "shim", f"shim{efi_suffix}.efi.signed.latest"
|
||||
),
|
||||
efi_boot_dir.joinpath(f"boot{efi_suffix}.efi"),
|
||||
)
|
||||
shutil.copy(
|
||||
shim_pkg_dir.joinpath("usr", "lib", "shim", f"mm{efi_suffix}.efi"),
|
||||
efi_boot_dir.joinpath(f"mm{efi_suffix}.efi"),
|
||||
)
|
||||
shutil.copy(
|
||||
grub_pkg_dir.joinpath(
|
||||
"usr",
|
||||
"lib",
|
||||
"grub",
|
||||
f"{grub_target}-efi-signed",
|
||||
f"gcd{efi_suffix}.efi.signed",
|
||||
),
|
||||
efi_boot_dir.joinpath(f"grub{efi_suffix}.efi"),
|
||||
)
|
||||
|
||||
grub_boot_dir = iso_root.joinpath("boot", "grub", f"{grub_target}-efi")
|
||||
grub_boot_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
src_grub_dir = grub_pkg_dir.joinpath("usr", "lib", "grub", f"{grub_target}-efi")
|
||||
for mod_file in src_grub_dir.glob("*.mod"):
|
||||
shutil.copy(mod_file, grub_boot_dir)
|
||||
for lst_file in src_grub_dir.glob("*.lst"):
|
||||
shutil.copy(lst_file, grub_boot_dir)
|
||||
|
||||
|
||||
def create_eltorito_esp_image(
|
||||
logger: Logger, iso_root: pathlib.Path, target_file: pathlib.Path
|
||||
) -> None:
|
||||
logger.log("creating El Torito ESP image")
|
||||
efi_dir = iso_root.joinpath("EFI")
|
||||
|
||||
# Calculate size: du -s --apparent-size --block-size=1024 + 1024
|
||||
result = logger.run(
|
||||
["du", "-s", "--apparent-size", "--block-size=1024", efi_dir],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
size_kb = int(result.stdout.split()[0]) + 1024
|
||||
|
||||
# Create filesystem: mkfs.msdos -n ESP -C -v
|
||||
logger.run(
|
||||
["mkfs.msdos", "-n", "ESP", "-C", "-v", target_file, str(size_kb)],
|
||||
check=True,
|
||||
)
|
||||
|
||||
# Copy files: mcopy -s -i target_file EFI ::/.
|
||||
logger.run(["mcopy", "-s", "-i", target_file, efi_dir, "::/."], check=True)
|
||||
|
||||
|
||||
class UEFIBootConfigurator(GrubBootConfigurator):
|
||||
"""Base class for UEFI-based architectures (AMD64, ARM64).
|
||||
|
||||
Subclasses should set:
|
||||
- efi_suffix: EFI binary suffix (e.g., "x64", "aa64")
|
||||
- grub_target: GRUB target name (e.g., "x86_64", "arm64")
|
||||
"""
|
||||
|
||||
# Subclasses must override these
|
||||
efi_suffix: str = ""
|
||||
grub_target: str = ""
|
||||
arch: str = ""
|
||||
|
||||
def get_uefi_grub_packages(self) -> list[str]:
|
||||
"""Return list of UEFI GRUB packages to download."""
|
||||
return [
|
||||
"grub2-common",
|
||||
f"grub-efi-{self.arch}-bin",
|
||||
f"grub-efi-{self.arch}-signed",
|
||||
]
|
||||
|
||||
def extract_uefi_files(self) -> None:
|
||||
"""Extract common UEFI files to boot tree."""
|
||||
|
||||
shim_pkg_dir = self.scratch.joinpath("shim-pkg")
|
||||
grub_pkg_dir = self.scratch.joinpath("grub-pkg")
|
||||
|
||||
# Download UEFI packages
|
||||
self.download_and_extract_package("shim-signed", shim_pkg_dir)
|
||||
for pkg in self.get_uefi_grub_packages():
|
||||
self.download_and_extract_package(pkg, grub_pkg_dir)
|
||||
|
||||
# Add common files for GRUB to tree
|
||||
copy_grub_common_files(grub_pkg_dir, self.iso_root)
|
||||
|
||||
# Add EFI GRUB to tree
|
||||
copy_signed_shim_grub(
|
||||
shim_pkg_dir,
|
||||
grub_pkg_dir,
|
||||
self.efi_suffix,
|
||||
self.grub_target,
|
||||
self.iso_root,
|
||||
)
|
||||
|
||||
# Create ESP image for El-Torito catalog and hybrid boot
|
||||
create_eltorito_esp_image(
|
||||
self.logger, self.iso_root, self.scratch.joinpath("cd-boot-efi.img")
|
||||
)
|
||||
|
||||
def uefi_menu_entries(self) -> str:
|
||||
"""Return UEFI firmware menu entries."""
|
||||
return """\
|
||||
menuentry 'Boot from next volume' {
|
||||
exit 1
|
||||
}
|
||||
menuentry 'UEFI Firmware Settings' {
|
||||
fwsetup
|
||||
}
|
||||
"""
|
||||
|
||||
def get_uefi_mkisofs_opts(self) -> list[str | pathlib.Path]:
|
||||
"""Return common UEFI mkisofs options."""
|
||||
# To make our ESP / El-Torito image compliant with MBR/GPT standards,
|
||||
# we first append it as a partition and then point the El Torito at
|
||||
# it. See https://lists.debian.org/debian-cd/2019/07/msg00007.html
|
||||
opts: list[str | pathlib.Path] = [
|
||||
"-append_partition",
|
||||
"2",
|
||||
"0xef",
|
||||
self.scratch.joinpath("cd-boot-efi.img"),
|
||||
"-appended_part_as_gpt",
|
||||
]
|
||||
|
||||
# Some BIOSes ignore removable disks with no partitions marked bootable
|
||||
# in the MBR. Make sure our protective MBR partition is marked bootable.
|
||||
opts.append("--mbr-force-bootable")
|
||||
|
||||
# Start a new entry in the el torito boot catalog
|
||||
opts.append("-eltorito-alt-boot")
|
||||
|
||||
# Specify where the el torito UEFI boot image "name". We use a special
|
||||
# syntax available in latest xorriso to point at our newly-created
|
||||
# partition.
|
||||
opts.extend(["-e", "--interval:appended_partition_2:all::"])
|
||||
|
||||
# Whether to emulate a floppy or not is a per-boot-catalog-entry
|
||||
# thing, so we need to say it again.
|
||||
opts.append("-no-emul-boot")
|
||||
|
||||
# Create a partition table entry that covers the iso9660 filesystem
|
||||
opts.extend(["-partition_offset", "16"])
|
||||
|
||||
return opts
|
||||
368
live-build/isobuilder/builder.py
Normal file
368
live-build/isobuilder/builder.py
Normal file
@ -0,0 +1,368 @@
|
||||
import contextlib
|
||||
import json
|
||||
import pathlib
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from isobuilder.apt_state import AptStateManager
|
||||
from isobuilder.boot import make_boot_configurator_for_arch
|
||||
from isobuilder.gpg_key import EphemeralGPGKey
|
||||
from isobuilder.pool_builder import PoolBuilder
|
||||
|
||||
# Constants
|
||||
PACKAGE_BATCH_SIZE = 200
|
||||
MAX_CMD_DISPLAY_LENGTH = 80
|
||||
|
||||
|
||||
def package_list_packages(package_list_file: pathlib.Path) -> list[str]:
|
||||
# Parse germinate output to extract package names. Germinate is Ubuntu's
|
||||
# package dependency resolver that outputs dependency trees for seeds (like
|
||||
# "ship-live" or "server-ship-live").
|
||||
#
|
||||
# Germinate output format has 2 header lines at the start and 2 footer lines
|
||||
# at the end (showing statistics), so we skip them with [2:-2].
|
||||
# Each data line starts with the package name followed by whitespace and
|
||||
# dependency info. This format is stable but if germinate ever changes its
|
||||
# header/footer count, this will break silently.
|
||||
lines = package_list_file.read_text().splitlines()[2:-2]
|
||||
return [line.split(None, 1)[0] for line in lines]
|
||||
|
||||
|
||||
def make_sources_text(
|
||||
series: str, gpg_key: EphemeralGPGKey, components: list[str], mountpoint: str
|
||||
) -> str:
|
||||
"""Generate a deb822-format apt source file for the ISO's package pool.
|
||||
|
||||
deb822 is the modern apt sources format (see sources.list(5) and deb822(5)).
|
||||
It uses RFC822-style fields where multi-line values must be indented with a
|
||||
leading space, and empty lines within a value are represented as " ."
|
||||
(space-dot). This format is required for inline GPG keys in the Signed-By
|
||||
field.
|
||||
"""
|
||||
key = gpg_key.export_public()
|
||||
quoted_key = []
|
||||
for line in key.splitlines():
|
||||
if not line:
|
||||
quoted_key.append(" .")
|
||||
else:
|
||||
quoted_key.append(" " + line)
|
||||
return f"""\
|
||||
Types: deb
|
||||
URIs: file://{mountpoint}
|
||||
Suites: {series}
|
||||
Components: {" ".join(components)}
|
||||
Check-Date: no
|
||||
Signed-By:
|
||||
""" + "\n".join(
|
||||
quoted_key
|
||||
)
|
||||
|
||||
|
||||
class Logger:
|
||||
|
||||
def __init__(self):
|
||||
self._indent = ""
|
||||
|
||||
def log(self, msg):
|
||||
print(self._indent + msg, file=sys.stderr)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def logged(self, msg, done_msg=None):
|
||||
self.log(msg)
|
||||
self._indent += " "
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
self._indent = self._indent[:-2]
|
||||
if done_msg is not None:
|
||||
self.log(done_msg)
|
||||
|
||||
def msg_for_cmd(self, cmd, limit_length=True, cwd=None) -> str:
|
||||
if cwd is None:
|
||||
_cwd = pathlib.Path().cwd()
|
||||
else:
|
||||
_cwd = cwd
|
||||
fmted_cmd = []
|
||||
for arg in cmd:
|
||||
if isinstance(arg, pathlib.Path):
|
||||
if arg.is_relative_to(_cwd):
|
||||
arg = arg.relative_to(_cwd)
|
||||
arg = str(arg)
|
||||
fmted_cmd.append(shlex.quote(arg))
|
||||
fmted_cmd_str = " ".join(fmted_cmd)
|
||||
if len(fmted_cmd_str) > MAX_CMD_DISPLAY_LENGTH and limit_length:
|
||||
fmted_cmd_str = fmted_cmd_str[:MAX_CMD_DISPLAY_LENGTH] + "..."
|
||||
msg = f"running `{fmted_cmd_str}`"
|
||||
if cwd is not None:
|
||||
msg += f" in {cwd}"
|
||||
return msg
|
||||
|
||||
def run(
|
||||
self, cmd: list[str | pathlib.Path], *args, limit_length=True, check=True, **kw
|
||||
):
|
||||
with self.logged(
|
||||
self.msg_for_cmd(cmd, cwd=kw.get("cwd"), limit_length=limit_length)
|
||||
):
|
||||
return subprocess.run(cmd, *args, check=check, **kw)
|
||||
|
||||
|
||||
class ISOBuilder:
|
||||
|
||||
def __init__(self, workdir: pathlib.Path):
|
||||
self.workdir = workdir
|
||||
self.logger = Logger()
|
||||
self.iso_root = workdir.joinpath("iso-root")
|
||||
self._config: dict | None = None
|
||||
self._gpg_key = self._apt_state = None
|
||||
|
||||
# UTILITY STUFF
|
||||
|
||||
def _read_config(self):
|
||||
with self.workdir.joinpath("config.json").open() as fp:
|
||||
self._config = json.load(fp)
|
||||
|
||||
@property
|
||||
def config(self):
|
||||
if self._config is None:
|
||||
self._read_config()
|
||||
return self._config
|
||||
|
||||
def save_config(self):
|
||||
with self.workdir.joinpath("config.json").open("w") as fp:
|
||||
json.dump(self._config, fp)
|
||||
|
||||
@property
|
||||
def arch(self):
|
||||
return self.config["arch"]
|
||||
|
||||
@property
|
||||
def series(self):
|
||||
if self._config is None:
|
||||
self._read_config()
|
||||
return self._config["series"]
|
||||
|
||||
@property
|
||||
def gpg_key(self):
|
||||
if self._gpg_key is None:
|
||||
self._gpg_key = EphemeralGPGKey(
|
||||
self.logger, self.workdir.joinpath("gpg-home")
|
||||
)
|
||||
return self._gpg_key
|
||||
|
||||
@property
|
||||
def apt_state(self):
|
||||
if self._apt_state is None:
|
||||
self._apt_state = AptStateManager(
|
||||
self.logger, self.series, self.workdir.joinpath("apt-state")
|
||||
)
|
||||
return self._apt_state
|
||||
|
||||
# COMMANDS
|
||||
|
||||
def init(self, disk_info: str, series: str, arch: str):
|
||||
self.logger.log("creating directories")
|
||||
self.workdir.mkdir(exist_ok=True)
|
||||
self.iso_root.mkdir()
|
||||
dot_disk = self.iso_root.joinpath(".disk")
|
||||
dot_disk.mkdir()
|
||||
|
||||
self.logger.log("saving config")
|
||||
self._config = {"arch": arch, "series": series}
|
||||
self.save_config()
|
||||
|
||||
self.logger.log("populating .disk")
|
||||
dot_disk.joinpath("base_installable").touch()
|
||||
dot_disk.joinpath("cd_type").write_text("full_cd/single\n")
|
||||
dot_disk.joinpath("info").write_text(disk_info)
|
||||
self.iso_root.joinpath("casper").mkdir()
|
||||
|
||||
self.gpg_key.create()
|
||||
|
||||
def setup_apt(self, chroot: pathlib.Path):
|
||||
self.apt_state.setup(chroot)
|
||||
|
||||
def generate_pool(self, package_list_file: pathlib.Path):
|
||||
# do we need any of the symlinks we create here??
|
||||
self.logger.log("creating pool skeleton")
|
||||
self.iso_root.joinpath("ubuntu").symlink_to(".")
|
||||
if self.arch not in ("amd64", "i386"):
|
||||
self.iso_root.joinpath("ubuntu-ports").symlink_to(".")
|
||||
self.iso_root.joinpath("dists", self.series).mkdir(parents=True)
|
||||
|
||||
builder = PoolBuilder(
|
||||
self.logger,
|
||||
series=self.series,
|
||||
rootdir=self.iso_root,
|
||||
apt_state=self.apt_state,
|
||||
)
|
||||
pkgs = package_list_packages(package_list_file)
|
||||
# XXX include 32-bit deps of 32-bit packages if needed here
|
||||
with self.logger.logged("adding packages"):
|
||||
for i in range(0, len(pkgs), PACKAGE_BATCH_SIZE):
|
||||
builder.add_packages(
|
||||
self.apt_state.show(pkgs[i : i + PACKAGE_BATCH_SIZE])
|
||||
)
|
||||
builder.make_packages()
|
||||
release_file = builder.make_release()
|
||||
self.gpg_key.sign(release_file)
|
||||
for name in "stable", "unstable":
|
||||
self.iso_root.joinpath("dists", name).symlink_to(self.series)
|
||||
|
||||
def generate_sources(self, mountpoint: str):
|
||||
components = [p.name for p in self.iso_root.joinpath("pool").iterdir()]
|
||||
print(
|
||||
make_sources_text(
|
||||
self.series, self.gpg_key, mountpoint=mountpoint, components=components
|
||||
)
|
||||
)
|
||||
|
||||
def _extract_casper_uuids(self):
|
||||
# Extract UUID files from initrd images for casper (the live boot system).
|
||||
# Each initrd contains a conf/uuid.conf with a unique identifier that
|
||||
# casper uses at boot time to locate the correct root filesystem. These
|
||||
# UUIDs must be placed in .disk/casper-uuid-<flavor> on the ISO so casper
|
||||
# can verify it's booting from the right media.
|
||||
with self.logger.logged("extracting casper uuids"):
|
||||
casper_dir = self.iso_root.joinpath("casper")
|
||||
dot_disk = self.iso_root.joinpath(".disk")
|
||||
for initrd in casper_dir.glob("*initrd"):
|
||||
initrddir = self.workdir.joinpath("initrd")
|
||||
with self.logger.logged(
|
||||
f"unpacking {initrd.name} ...", done_msg="... done"
|
||||
):
|
||||
self.logger.run(["unmkinitramfs", initrd, initrddir])
|
||||
# unmkinitramfs can produce different directory structures:
|
||||
# - Platforms with early firmware: subdirs like "main/" or "early/"
|
||||
# containing conf/uuid.conf
|
||||
# - Other platforms: conf/uuid.conf directly in the root
|
||||
# Try to find uuid.conf in both locations.
|
||||
confs = list(initrddir.glob("*/conf/uuid.conf"))
|
||||
if confs:
|
||||
[uuid_conf] = confs
|
||||
elif initrddir.joinpath("conf/uuid.conf").exists():
|
||||
uuid_conf = initrddir.joinpath("conf/uuid.conf")
|
||||
else:
|
||||
raise Exception("uuid.conf not found")
|
||||
self.logger.log(f"found {uuid_conf.relative_to(initrddir)}")
|
||||
if initrd.name == "initrd":
|
||||
suffix = "generic"
|
||||
elif initrd.name == "hwe-initrd":
|
||||
suffix = "generic-hwe"
|
||||
else:
|
||||
raise Exception(f"unexpected initrd name {initrd.name}")
|
||||
uuid_conf.rename(dot_disk.joinpath(f"casper-uuid-{suffix}"))
|
||||
shutil.rmtree(initrddir)
|
||||
|
||||
def add_live_filesystem(self, artifact_prefix: pathlib.Path):
|
||||
casper_dir = self.iso_root.joinpath("casper")
|
||||
artifact_dir = artifact_prefix.parent
|
||||
filename_prefix = artifact_prefix.name
|
||||
|
||||
def link(src: pathlib.Path, target_name: str):
|
||||
target = casper_dir.joinpath(target_name)
|
||||
self.logger.log(
|
||||
f"creating link from $ISOROOT/casper/{target_name} to $src/{src.name}"
|
||||
)
|
||||
target.hardlink_to(src)
|
||||
|
||||
kernel_name = "vmlinuz"
|
||||
if self.arch in ("ppc64el", "riscv64"):
|
||||
kernel_name = "vmlinux"
|
||||
|
||||
with self.logger.logged(
|
||||
f"linking artifacts from {casper_dir} to {artifact_dir}"
|
||||
):
|
||||
for ext in "squashfs", "squashfs.gpg", "size", "manifest", "yaml":
|
||||
for path in artifact_dir.glob(f"{filename_prefix}*.{ext}"):
|
||||
newname = path.name[len(filename_prefix) :]
|
||||
link(path, newname)
|
||||
|
||||
for kernel_path in artifact_dir.glob(f"{filename_prefix}kernel*"):
|
||||
suffix = kernel_path.name[len(filename_prefix) + len("kernel") :]
|
||||
prefix = "hwe-" if suffix.endswith("-hwe") else ""
|
||||
link(
|
||||
artifact_dir.joinpath(f"{filename_prefix}kernel{suffix}"),
|
||||
f"{prefix}{kernel_name}",
|
||||
)
|
||||
link(
|
||||
artifact_dir.joinpath(f"{filename_prefix}initrd{suffix}"),
|
||||
f"{prefix}initrd",
|
||||
)
|
||||
self._extract_casper_uuids()
|
||||
|
||||
def make_bootable(self, project: str, capproject: str, subarch: str):
|
||||
configurator = make_boot_configurator_for_arch(
|
||||
self.arch,
|
||||
self.logger,
|
||||
self.apt_state,
|
||||
self.workdir,
|
||||
self.iso_root,
|
||||
)
|
||||
configurator.make_bootable(
|
||||
project,
|
||||
capproject,
|
||||
subarch,
|
||||
self.iso_root.joinpath("casper/hwe-initrd").exists(),
|
||||
)
|
||||
|
||||
def checksum(self):
|
||||
# Generate md5sum.txt for ISO integrity verification.
|
||||
# - Symlinks are excluded because their targets are already checksummed
|
||||
# - Files are sorted for deterministic, reproducible output across builds
|
||||
# - Paths use "./" prefix and we run md5sum from iso_root so the output
|
||||
# matches what users get when they verify with "md5sum -c" from the ISO
|
||||
all_files = []
|
||||
for dirpath, dirnames, filenames in self.iso_root.walk():
|
||||
filepaths = [dirpath.joinpath(filename) for filename in filenames]
|
||||
all_files.extend(
|
||||
"./" + str(filepath.relative_to(self.iso_root))
|
||||
for filepath in filepaths
|
||||
if not filepath.is_symlink()
|
||||
)
|
||||
self.iso_root.joinpath("md5sum.txt").write_bytes(
|
||||
self.logger.run(
|
||||
["md5sum"] + sorted(all_files),
|
||||
cwd=self.iso_root,
|
||||
stdout=subprocess.PIPE,
|
||||
).stdout
|
||||
)
|
||||
|
||||
def make_iso(self, dest: pathlib.Path, volid: str | None):
|
||||
# xorriso with "-as mkisofs" runs in mkisofs compatibility mode.
|
||||
# -r enables Rock Ridge extensions for Unix metadata (permissions, symlinks).
|
||||
# -iso-level 3 (amd64 only) allows files >4GB which some amd64 ISOs need.
|
||||
# mkisofs_opts comes from the boot configurator and contains architecture-
|
||||
# specific options for boot sectors, EFI images, etc.
|
||||
self.checksum()
|
||||
configurator = make_boot_configurator_for_arch(
|
||||
self.arch,
|
||||
self.logger,
|
||||
self.apt_state,
|
||||
self.workdir,
|
||||
self.iso_root,
|
||||
)
|
||||
mkisofs_opts = configurator.mkisofs_opts()
|
||||
cmd: list[str | pathlib.Path] = ["xorriso"]
|
||||
if self.arch == "riscv64":
|
||||
# For $reasons, xorriso is not run in mkisofs mode on riscv64 only.
|
||||
cmd.extend(["-rockridge", "on", "-outdev", dest])
|
||||
if volid:
|
||||
cmd.extend(["-volid", volid])
|
||||
cmd.extend(mkisofs_opts)
|
||||
cmd.extend(["-map", self.iso_root, "/"])
|
||||
else:
|
||||
# xorriso with "-as mkisofs" runs in mkisofs compatibility mode on
|
||||
# other architectures. -r enables Rock Ridge extensions for Unix
|
||||
# metadata (permissions, symlinks). -iso-level 3 (amd64 only)
|
||||
# allows files >4GB which some amd64 ISOs need.
|
||||
cmd.extend(["-as", "mkisofs", "-r"])
|
||||
if self.arch == "amd64":
|
||||
cmd.extend(["-iso-level", "3"])
|
||||
if volid:
|
||||
cmd.extend(["-V", volid])
|
||||
cmd.extend(mkisofs_opts + [self.iso_root, "-o", dest])
|
||||
with self.logger.logged("running xorriso"):
|
||||
self.logger.run(cmd, cwd=self.workdir, check=True, limit_length=False)
|
||||
configurator.post_process_iso(dest)
|
||||
58
live-build/isobuilder/gpg_key.py
Normal file
58
live-build/isobuilder/gpg_key.py
Normal 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
|
||||
166
live-build/isobuilder/pool_builder.py
Normal file
166
live-build/isobuilder/pool_builder.py
Normal 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
|
||||
@ -180,10 +180,22 @@ build_layered_squashfs () {
|
||||
# Operate on the upperdir directly, so that we are only
|
||||
# modifying mtime on files that are actually changed in
|
||||
# this layer. LP: #2107332
|
||||
/usr/share/livecd-rootfs/sync-mtime chroot "$overlay_dir"
|
||||
${LIVECD_ROOTFS_ROOT}/sync-mtime chroot "$overlay_dir"
|
||||
fi
|
||||
|
||||
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
|
||||
echo "Expanding catalog entry template for $pass"
|
||||
@ -194,7 +206,7 @@ build_layered_squashfs () {
|
||||
if [ -f config/seeded-languages ]; then
|
||||
usc_opts="$usc_opts --langs $(cat config/seeded-languages)"
|
||||
fi
|
||||
/usr/share/livecd-rootfs/update-source-catalog source $usc_opts
|
||||
${LIVECD_ROOTFS_ROOT}/update-source-catalog source $usc_opts
|
||||
else
|
||||
echo "No catalog entry template for $pass"
|
||||
fi
|
||||
@ -215,7 +227,7 @@ done
|
||||
|
||||
if [ -n "$DEFAULT_KERNEL" -a -f livecd.${PROJECT_FULL}.install-sources.yaml ]; then
|
||||
write_kernel_yaml "$DEFAULT_KERNEL" "$BRIDGE_KERNEL_REASONS"
|
||||
/usr/share/livecd-rootfs/update-source-catalog merge \
|
||||
${LIVECD_ROOTFS_ROOT}/update-source-catalog merge \
|
||||
--output livecd.${PROJECT_FULL}.install-sources.yaml \
|
||||
--template config/kernel.yaml
|
||||
fi
|
||||
@ -227,3 +239,11 @@ if [ -n "$(ls livecd.${PROJECT_FULL}.*install.live.manifest.full 2>/dev/null)" ]
|
||||
fi
|
||||
|
||||
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
|
||||
|
||||
@ -237,7 +237,7 @@ create_chroot_pass () {
|
||||
lb chroot_interactive ${*}
|
||||
|
||||
# Misc ubuntu cleanup and post-layer configuration
|
||||
/usr/share/livecd-rootfs/minimize-manual chroot
|
||||
${LIVECD_ROOTFS_ROOT}/minimize-manual chroot
|
||||
clean_debian_chroot
|
||||
|
||||
Chroot chroot "dpkg-query -W" > chroot.packages.${pass}
|
||||
|
||||
53
live-build/snap-seed-missing-providers.py
Executable file
53
live-build/snap-seed-missing-providers.py
Executable 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:])
|
||||
@ -11,6 +11,7 @@ case ${PASS:-} in
|
||||
esac
|
||||
|
||||
. config/binary
|
||||
. config/common
|
||||
. config/functions
|
||||
|
||||
case ${SUBPROJECT} in
|
||||
@ -56,4 +57,4 @@ PROJECT_FULL=$PROJECT${SUBARCH:+-$SUBARCH}
|
||||
usc_opts="--output livecd.${PROJECT_FULL}.install-sources.yaml \
|
||||
--template config/edge.catalog-in.yaml \
|
||||
--size 0"
|
||||
/usr/share/livecd-rootfs/update-source-catalog source $usc_opts
|
||||
${LIVECD_ROOTFS_ROOT}/update-source-catalog source $usc_opts
|
||||
|
||||
@ -24,7 +24,10 @@ create_partitions() {
|
||||
sgdisk "${disk_image}" \
|
||||
--new=2::+8M \
|
||||
--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}" \
|
||||
--print
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ if [ "$ARCH" = "riscv64" ]; then
|
||||
fi
|
||||
|
||||
case ${PROJECT:-}:${SUBPROJECT:-} in
|
||||
ubuntu:)
|
||||
ubuntu:|ubuntu:dangerous)
|
||||
echo "We don't create EFI images for Ubuntu Desktop."
|
||||
exit 0
|
||||
;;
|
||||
@ -70,105 +70,12 @@ create_partitions() {
|
||||
fi
|
||||
;;
|
||||
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}" \
|
||||
--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 \
|
||||
--typecode=15:ef00 \
|
||||
--new=12::+4M \
|
||||
--change-name=12:CIDATA \
|
||||
--new=1:: \
|
||||
--attributes=1:set:2
|
||||
sgdisk "${disk_image}" \
|
||||
--new=15::+106M \
|
||||
--typecode=15:ef00 \
|
||||
--new=12::+4M \
|
||||
--change-name=12:CIDATA \
|
||||
--new=1:: \
|
||||
fi
|
||||
;;
|
||||
amd64)
|
||||
@ -265,126 +172,31 @@ install_grub() {
|
||||
fi
|
||||
;;
|
||||
riscv64)
|
||||
if [ -n "${SUBARCH:-}" ]; then
|
||||
# Per-device images
|
||||
local my_d=$(dirname $(readlink -f ${0}))
|
||||
echo "Adjusting GRUB defaults for ${ARCH}"
|
||||
mkdir -p 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"
|
||||
kver=$(ls mountpoint/lib/modules | sort -V | tail -n 1)
|
||||
dtb_src_dirs=(
|
||||
"mountpoint/usr/lib/linux-image-$kver"
|
||||
"mountpoint/lib/firmware/$kver/device-tree"
|
||||
)
|
||||
dtb_tgt_dir="mountpoint/boot/efi/dtb/"
|
||||
mkdir -p "$dtb_tgt_dir"
|
||||
for src_dir in "${dtb_src_dirs[@]}"; do
|
||||
[ -d "$src_dir" ] && cp -r -v "$src_dir"/* "$dtb_tgt_dir" || echo "Skipping missing: $src_dir"
|
||||
done
|
||||
chroot mountpoint bash -c 'FK_FORCE=yes apt-get install -qqy grub-efi-riscv64 flash-kernel'
|
||||
efi_target=riscv64-efi
|
||||
# Provide end-user modifyable CIDATA
|
||||
cidata_dev="${loop_device}p12"
|
||||
setup_cidata "${cidata_dev}"
|
||||
# Provide stock nocloud datasource
|
||||
# Allow interactive login without a cloud datasource.
|
||||
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
|
||||
# Per-device images
|
||||
local my_d=$(dirname $(readlink -f ${0}))
|
||||
echo "Adjusting GRUB defaults for ${ARCH}/${SUBARCH}"
|
||||
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/
|
||||
echo "Copying device trees"
|
||||
kver=$(ls mountpoint/lib/modules | sort -V | tail -n 1)
|
||||
dtb_src_dirs=(
|
||||
"mountpoint/usr/lib/linux-image-$kver"
|
||||
"mountpoint/lib/firmware/$kver/device-tree"
|
||||
)
|
||||
dtb_tgt_dir="mountpoint/boot/efi/dtb/"
|
||||
mkdir -p "$dtb_tgt_dir"
|
||||
for src_dir in "${dtb_src_dirs[@]}"; do
|
||||
[ -d "$src_dir" ] && cp -r -v "$src_dir"/* "$dtb_tgt_dir" || echo "Skipping missing: $src_dir"
|
||||
done
|
||||
chroot mountpoint bash -c 'FK_FORCE=yes apt-get install -qqy grub-efi-riscv64 flash-kernel'
|
||||
efi_target=riscv64-efi
|
||||
# Provide end-user modifyable CIDATA
|
||||
cidata_dev="${loop_device}p12"
|
||||
setup_cidata "${cidata_dev}"
|
||||
# Provide stock nocloud datasource
|
||||
# Allow interactive login without a cloud datasource.
|
||||
setup_cinocloud mountpoint
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
@ -41,7 +41,9 @@ create_partitions() {
|
||||
--typecode=15:ef00 \
|
||||
--new=13::1G \
|
||||
--typecode=13:ea00 \
|
||||
--new=1:
|
||||
--new=1: \
|
||||
--change-name=1:"$FS_LABEL" \
|
||||
--typecode=1:"$(gpt_root_partition_uuid $ARCH)"
|
||||
;;
|
||||
riscv64)
|
||||
sgdisk "${disk_image}" \
|
||||
@ -51,6 +53,8 @@ create_partitions() {
|
||||
--new=15::+106M \
|
||||
--typecode=15:ef00 \
|
||||
--new=1:: \
|
||||
--change-name=1:"$FS_LABEL" \
|
||||
--typecode=1:"$(gpt_root_partition_uuid $ARCH)" \
|
||||
--attributes=1:set:2
|
||||
;;
|
||||
amd64)
|
||||
@ -61,8 +65,10 @@ create_partitions() {
|
||||
--new=15::+106M \
|
||||
--new=1::
|
||||
sgdisk "${disk_image}" \
|
||||
--change-name=1:"$FS_LABEL" \
|
||||
-t 14:ef02 \
|
||||
-t 15:ef00
|
||||
-t 15:ef00 \
|
||||
-t 1:"$(gpt_root_partition_uuid $ARCH)"
|
||||
;;
|
||||
esac
|
||||
sgdisk "${disk_image}" \
|
||||
|
||||
@ -14,6 +14,10 @@ ROOTPART_START=1
|
||||
|
||||
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
|
||||
ppc64el:*|powerpc:*)
|
||||
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
|
||||
# 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 df --all --human-readable --print-type
|
||||
chroot mountpoint grub-install ${loop_device}
|
||||
chroot mountpoint grub-bios-setup \
|
||||
--boot-image=i386-pc/boot.img \
|
||||
|
||||
@ -0,0 +1 @@
|
||||
datasource_list: [ OpenStack, None ]
|
||||
@ -0,0 +1,2 @@
|
||||
dsmode: local
|
||||
instance_id: ubuntu-server
|
||||
@ -0,0 +1,104 @@
|
||||
name: ubuntu-minimal
|
||||
version: "0.1"
|
||||
base: bare
|
||||
build-base: devel
|
||||
summary: Minimal Ubuntu image for CPC
|
||||
description: A minimal Ubuntu image to be built using livecd-rootfs by CPC
|
||||
|
||||
platforms:
|
||||
amd64:
|
||||
|
||||
volumes:
|
||||
pc:
|
||||
schema: gpt
|
||||
structure:
|
||||
# 1. BIOS Boot
|
||||
- name: bios-boot
|
||||
type: 21686148-6449-6E6F-744E-656564454649
|
||||
role: system-boot
|
||||
filesystem: vfat
|
||||
size: 4M
|
||||
partition-number: 14
|
||||
# 2. EFI System Partition
|
||||
- name: efi
|
||||
type: C12A7328-F81F-11D2-BA4B-00A0C93EC93B
|
||||
filesystem: vfat
|
||||
filesystem-label: UEFI
|
||||
role: system-boot
|
||||
size: 106M
|
||||
partition-number: 15
|
||||
# 3. Linux Extended Boot
|
||||
- name: boot
|
||||
type: 0FC63DAF-8483-4772-8E79-3D69D8477DE4
|
||||
filesystem: ext4
|
||||
filesystem-label: BOOT
|
||||
role: system-data
|
||||
size: 1G
|
||||
partition-number: 13
|
||||
# 4. Root Filesystem
|
||||
- name: rootfs
|
||||
type: 0FC63DAF-8483-4772-8E79-3D69D8477DE4
|
||||
filesystem: ext4
|
||||
filesystem-label: cloudimg-rootfs
|
||||
role: system-data
|
||||
size: 3G
|
||||
partition-number: 1
|
||||
|
||||
filesystems:
|
||||
default:
|
||||
- mount: "/"
|
||||
device: "(volume/pc/rootfs)"
|
||||
- mount: "/boot"
|
||||
device: "(volume/pc/boot)"
|
||||
- mount: "/boot/efi"
|
||||
device: "(volume/pc/efi)"
|
||||
|
||||
parts:
|
||||
rootfs:
|
||||
plugin: nil
|
||||
build-packages: ["mmdebstrap"]
|
||||
override-build: |
|
||||
mmdebstrap --arch $CRAFT_ARCH_BUILD_FOR \
|
||||
--mode=sudo \
|
||||
--format=dir \
|
||||
--variant=minbase \
|
||||
--include=apt \
|
||||
resolute \
|
||||
$CRAFT_PART_INSTALL/ \
|
||||
http://archive.ubuntu.com/ubuntu/
|
||||
rm -r $CRAFT_PART_INSTALL/dev/*
|
||||
mkdir $CRAFT_PART_INSTALL/boot/efi
|
||||
organize:
|
||||
'*': (overlay)/
|
||||
|
||||
packages:
|
||||
plugin: nil
|
||||
overlay-packages:
|
||||
- ubuntu-server-minimal
|
||||
- grub2-common
|
||||
- grub-pc
|
||||
- shim-signed
|
||||
- linux-image-generic
|
||||
overlay-script: |
|
||||
rm $CRAFT_OVERLAY/etc/cloud/cloud.cfg.d/90_dpkg.cfg
|
||||
|
||||
snaps:
|
||||
plugin: nil
|
||||
after: [packages]
|
||||
overlay-script: |
|
||||
env SNAPPY_STORE_NO_CDN=1 snap prepare-image --classic \
|
||||
--arch=amd64 --snap snapd --snap core24 "" $CRAFT_OVERLAY
|
||||
|
||||
fstab:
|
||||
plugin: nil
|
||||
after: [snaps]
|
||||
overlay-script: |
|
||||
cat << EOF > $CRAFT_OVERLAY/etc/fstab
|
||||
LABEL=cloudimg-rootfs / ext4 discard,errors=remount-ro 0 1
|
||||
LABEL=BOOT /boot ext4 defaults 0 2
|
||||
LABEL=UEFI /boot/efi vfat umask=0077 0 1
|
||||
EOF
|
||||
|
||||
cloud-init:
|
||||
plugin: dump
|
||||
source: cloud-init/
|
||||
81
live-build/ubuntu-cpc/hooks.d/base/imagecraft-image.binary
Normal file
81
live-build/ubuntu-cpc/hooks.d/base/imagecraft-image.binary
Normal file
@ -0,0 +1,81 @@
|
||||
#!/bin/bash -eux
|
||||
|
||||
. config/functions
|
||||
|
||||
ARCH="${ARCH:-}"
|
||||
SUBPROJECT="${SUBPROJECT:-}"
|
||||
|
||||
# We want to start off imagecraft builds with just amd64 support right now
|
||||
case $ARCH in
|
||||
amd64)
|
||||
;;
|
||||
*)
|
||||
echo "imagecraft build is currently not implemented for ARCH=${ARCH:-unset}."
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
case ${SUBPROJECT} in
|
||||
minimized)
|
||||
;;
|
||||
*)
|
||||
echo "imagecraft build is currently not implemented for SUBPROJECT=${SUBPROJECT:-unset}."
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
_src_d=$(dirname $(readlink -f ${0}))
|
||||
|
||||
snap install imagecraft --classic --channel latest/edge
|
||||
|
||||
cp -r "$_src_d"/imagecraft-configs/* .
|
||||
|
||||
CRAFT_BUILD_ENVIRONMENT=host imagecraft --verbosity debug pack
|
||||
|
||||
# We are using this function instead of mount_disk_image from functions
|
||||
# because imagecraft doesn't currently support XBOOTLDR's GUID and
|
||||
# mount_disk_image has an explicit check for the XBOOTLDR GUID
|
||||
# TODO: Use mount_disk_image once imagecraft supports XBOOTLDR's GUID
|
||||
mount_image_partitions() {
|
||||
mount_image "${disk_image}" "$ROOT_PARTITION"
|
||||
|
||||
# Making sure that the loop device is ready
|
||||
partprobe "${loop_device}"
|
||||
udevadm settle
|
||||
mount_partition "${rootfs_dev_mapper}" "$mountpoint"
|
||||
mount "${loop_device}p13" "$mountpoint/boot"
|
||||
mount "${loop_device}p15" "$mountpoint/boot/efi"
|
||||
}
|
||||
|
||||
install_grub_on_image() {
|
||||
divert_grub "$mountpoint"
|
||||
chroot "$mountpoint" grub-install --target=i386-pc "${loop_device}"
|
||||
chroot "$mountpoint" update-grub
|
||||
undivert_grub "$mountpoint"
|
||||
|
||||
echo "GRUB for BIOS boot installed successfully."
|
||||
}
|
||||
|
||||
unmount_image_partitions() {
|
||||
umount "$mountpoint/boot/efi"
|
||||
umount "$mountpoint/boot"
|
||||
|
||||
umount_partition "$mountpoint"
|
||||
rmdir "$mountpoint"
|
||||
}
|
||||
|
||||
disk_image="pc.img"
|
||||
ROOT_PARTITION=1
|
||||
mountpoint=$(mktemp -d)
|
||||
|
||||
mount_image_partitions
|
||||
|
||||
install_grub_on_image
|
||||
create_manifest "$mountpoint/" "$PWD/livecd.ubuntu-cpc.imagecraft.manifest" "$PWD/livecd.ubuntu-cpc.imagecraft.spdx" "cloud-image-$ARCH-$(date +%Y%m%dT%H:%M:%S)" "false"
|
||||
|
||||
unmount_image_partitions
|
||||
|
||||
clean_loops
|
||||
trap - EXIT
|
||||
|
||||
qemu-img convert -f raw -O qcow2 "${disk_image}" livecd.ubuntu-cpc.imagecraft.img
|
||||
@ -2,3 +2,4 @@
|
||||
# For minimum output use
|
||||
# GRUB_CMDLINE_LINUX_DEFAULT="quiet"
|
||||
GRUB_CMDLINE_LINUX_DEFAULT="efi=debug earlycon=sbi"
|
||||
GRUB_TERMINAL=console
|
||||
|
||||
@ -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"
|
||||
@ -1,3 +0,0 @@
|
||||
# Required drivers to boot off MMC
|
||||
mmc-block
|
||||
sunxi-mmc
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user