Adding a new builtin distro¶
How the build works¶
Each builtin distribution comes as a single layer Docker image published on the github docker registry (ghcr.io). For each distribution, there are two versions:
- A configured version (see here)
- A non configured version that is named after the name of the configured
version with the suffix
-base
(i.e.alpine-base
).
All the builtin distributions are listed in a JSON file named
builtins.rootfs.json
and pushed into the special github repository branch
rootfs
. It is reachable from the internet at the following URL:
The docker container images as well as the above listing are produced by the
.github/workflows/build-rootfs-oci.yaml
github actions workflow.
The process is composed of the following steps:
- Downloading the compressed base root filesystem from upstream.
- Extract distribution information from
/etc/os-release
or alternatively from/usr/lib/os-release
inside the compressed filesystem. - Create a Dockerfile containing the appropriate labels and the root filesystem as the sole layer.
- Build the Docker image and push it to the GitHub container registry.
- Extract the root filesystem and run the
configure.sh
script in it in a chroot jail. - Re-compress the root filesystem.
- Update the Dockerfile to use the new root filesystem.
- Build the configured image and push it to the GitHub container registry.
- Generate the images JSON metadata information intended for
builtins.rootfs.json
.
The workflow works in matrix. It is triggered by a commit on the deploy/images
branch and is scheduled to run each Sunday morning at 2am.
When run, the workflow will build and push the new images automatically. After
that, all the metadata file are gathered into a builtins.rootfs.json
file that
is pushed on the rootfs
branch.
Steps for adding a distribution¶
Adding a new named distro to the builtins involves the following steps:
- Download the distribution base root filesystem from upstream.
- Test it as is with Wsl:
- Adapt and/or test the
configure.sh
script for the new distribution: - Add the distribution name and base root filesystem URL into the matrix configuration (see below).
- Test the build with a workflow dispatch, i.e. running the build manually.
- Test the images:
- Make a pull request.
The following details each step for OpenSuse. OpenSuse is a RPM based distribution close to RHEL. A rolling release version of the distribution is available under the name Tumbleweed.
Metadata description¶
The following example shows the metadata produced by the build workflow:
{
"Type": "Builtin",
"Name": "opensuse-tumbleweed",
"Os": "Opensuse-tumbleweed",
"Url": "docker://ghcr.io/antoinemartin/powershell-wsl-manager/opensuse-tumbleweed#latest",
"Hash": { "Type": "docker" },
"Release": "20250825",
"Configured": true,
"Username": "opensuse-tumbleweed",
"Uid": 1000,
"LocalFilename": "docker.opensuse-tumbleweed.rootfs.tar.gz"
}
Hash Property Explanation¶
The Hash
property in the metadata describes where to find the digest
information of the image in order to verify that the right content has been
downloaded. In the above example, it contains only:
Because the digest is actually retrieved by accessing the manifest of the image. More than that, it's part of the URL, and it ensures the integrity and authenticity of the downloaded image.
When the image is not a docker image and the URL has the following form:
https://cdimages.ubuntu.com/ubuntu-wsl/daily-live/current/questing-wsl-amd64.wsl
The usual way to verify the image is to use a checksum file. The Hash
property
in the metadata describes where to find the digest information of the image in
order to verify that the right content has been downloaded.
It has the following properties:
Url
: Points to the checksum file provided by the distro provider. This file contains the expected digest value for the Image archive.-
Type
:'sums'
indicates that the hash file contains checksum values for several files in a standard format, with the hash value followed by the filename:8a8576ddf9d0fb6bad78acf970a0bf792d062f9125d029007823feaba7216bba rootfs.tar.xz 4469fbcb82ad0b09a3a4b37d15bf1b708e8860fef4a9b43f50bdbd618fb217bf rootfs.squashfs
single
indicates that the destination contains only the hash value of the image, and withdocker
the hash is given by the manifest of the docker image.
Algorithm
: Specifies the hashing algorithm used to compute the digest. Nowadays, this is typically SHA256.
This verification process protects against corrupted downloads and ensures you're installing the exact Image image that the distribution organization intended to distribute.
Performing the addition¶
For OpenSuse, the upstream image URL we find is the following:
https://download.opensuse.org/tumbleweed/appliances/opensuse-tumbleweed-dnf-image.x86_64-lxc-dnf.tar.xz
Create an instance from the Upstream URL¶
Create a local WSL instance with the URL:
PS> New-WslInstance suse -From https://download.opensuse.org/tumbleweed/appliances/opensuse-tumbleweed-dnf-image.x86_64-lxc-dnf.tar.xz
⌛ Creating directory [C:\Users\AntoineMartin\AppData\Local\Wsl\suse]...
⌛ Getting checksums from https://download.opensuse.org/tumbleweed/appliances/SHA256SUMS...
⌛ Downloading https://download.opensuse.org/tumbleweed/appliances/opensuse-tumbleweed-dnf-image.x86_64-lxc-dnf.tar.xz...
opensuse-tumbleweed-dnf-image.x86_64-lxc-dnf.tar.xz (46,3 MB) [===============================================] 100%
🎉 [opensuse:unknown] Synced at [C:\Users\AntoineMartin\AppData\Local\Wsl\RootFS\opensuse-tumbleweed-dnf-image.x86_64-lxc-dnf.tar.xz].
⌛ Creating instance [suse] from [C:\Users\AntoineMartin\AppData\Local\Wsl\RootFS\opensuse-tumbleweed-dnf-image.x86_64-lxc-dnf.tar.xz]...
🎉 Done. Command to enter instance: Invoke-WslInstance -In suse or wsl -d suse
Name State Version Default
---- ----- ------- -------
suse Stopped 2 False
Adapt and test the configure script¶
The configure.sh
script configures the system. It identifies the Linux flavor
by looking at the ID
variable in the /etc/os-release
script:
PS> wsl -d suse cat /etc/os-release
NAME="openSUSE Tumbleweed"
# VERSION="20221215"
ID="opensuse-tumbleweed"
...
PS>
Here it will use opensuse
as the os name as well as the name for the default
user to create.
The script tries to call a configure_suse()
function. Let's create one in the
script:
Note
As the script may be run through bash in posix mode (debian), dash or ash (alpine), you should stick to good old Bourne Again Shell syntax (POSIX) as much as possible.
We can now invoke the script:
PS> Invoke-WslConfigure suse -Verbose
COMMENTAIRES : Piping wsl.exe with arguments: --list --verbose
COMMENTAIRES : Opération « Configure instance » en cours sur la cible « suse ».
⌛ Running initialization script [C:\Users\AntoineMartin\Documents\WindowsPowerShell\Modules\Wsl-Manager/configure.sh] on instance [suse]...
COMMENTAIRES : Running wsl.exe with arguments: -d suse -u root ./configure.sh
COMMENTAIRES : Output:
We are on opensuse
Hello from Tumbleweed...
Configuration done.
<end of output>
🎉 Configuration of instance [suse] completed successfully.
Name State Version Default
---- ----- ------- -------
suse Stopped 2 False
PS>
When the configuration has been performed without errors, the configure.sh
script creates a file named /etc/wsl-configured
to prevent re-configuration in
case the WSL distribution is
exported.
Running the configuration again doesn't work:
However, deleting the file /etc/wsl-configured
allows re-running the
configuration again:
PS> wsl -d suse -u root rm /etc/wsl-configured
PS> Invoke-WslConfigure suse
We are on opensuse
Hello from Tumbleweed...
Configuration done.
PS>
You have the same behavior using the Invoke-WslConfigure
cmdlet with the
-Force
Parameter:
PS> Invoke-WslConfigure suse -Force -Verbose
COMMENTAIRES : Piping wsl.exe with arguments: --list --verbose
COMMENTAIRES : Opération « Configure instance » en cours sur la cible « suse ».
COMMENTAIRES : Force reconfiguration of instance [suse]
COMMENTAIRES : Running wsl.exe with arguments: -d suse -u root rm -rf /etc/wsl-configured
⌛ Running initialization script [C:\Users\AntoineMartin\Documents\WindowsPowerShell\Modules\Wsl-Manager/configure.sh] on instance [suse]...
COMMENTAIRES : Running wsl.exe with arguments: -d suse -u root ./configure.sh
COMMENTAIRES : Output:
We are on opensuse
Hello from Tumbleweed...
Configuration done.
<end of output>
🎉 Configuration of instance [suse] completed successfully.
Name State Version Default
---- ----- ------- -------
suse Stopped 2 False
Now this is a matter of completing the configure_opensuse()
method in order to
perform the configuration.
OpenSuse is a RPM based distribution similar to RHEL. The configuration script
already contains a function configure_rhel_like()
to configure such systems.
The main difference between Suse and the RHEL based distributions is the use of
dnf
as the package manager. dnf
is a fork of yum
and is command line
compatible. Instead of copy/pasting configure_rhel_like()
to create
configure_opensuse()
, we can adapt configure_rhel_like()
to take the name of
the package manager as argument.:
diff --git a/configure.sh b/configure.sh
index f622a5d..87d3c2e 100644
--- a/configure.sh
+++ b/configure.sh
@@ -269,7 +269,8 @@ configure_arch() {
# Configure a RHEL like system (CentOS, Almalinux, ...)
#
-# @param $1 list of groups separated by commas of the groups to add to the sudo
+# @param $1 the name of the package manager (yum, dnf)
+# @param $2 list of groups separated by commas of the groups to add to the sudo
# user. The administrative groups may differ from distribution to
# distribution (staff, wheel, admin).
# @param $@ list of additionnal packages to add.
@@ -283,14 +284,16 @@ configure_arch() {
# - Add a sudo user derived from the name of the distribution with the
# appropriate configuration and groups
configure_rhel_like() {
+ local pkmgr=$1
+ shift
local admin_group_name=$1
shift
local additional_packages="$@"
echo "Adding packages..."
- yum -y -q makecache >/dev/null 2>&1
- yum -y -q install zsh git sudo gnupg socat openssh-clients tar $additional_packages >/dev/null 2>&1
- yum -y clean all >/dev/null 2>&1
+ $pkmgr -y -q makecache >/dev/null 2>&1
+ $pkmgr -y -q install zsh git sudo gnupg socat openssh-clients tar $additional_packages >/dev/null 2>&1
+ $pkmgr -y clean all >/dev/null 2>&1
change_root_shell
Then we need to adapt the already existing configure_...()
functions in order
to pass yum
as argument:
@@ -304,22 +307,31 @@ configure_rhel_like() {
# Configure an Alma Linux System
# @ see configure_rhel_like
configure_almalinux() {
- configure_rhel_like adm,wheel
+ configure_rhel_like yum adm,wheel
}
# Configure a Rocky Linux System
# @ see configure_rhel_like
configure_rocky() {
- configure_rhel_like adm,wheel
+ configure_rhel_like yum adm,wheel
}
# Configure a CentOS Linux System
# @ see configure_rhel_like
configure_centos() {
- configure_rhel_like adm,wheel
+ configure_rhel_like yum adm,wheel
}
And then through trial and error, we find the following peculiarities to Suse:
- The admin group seems to be
trusted
- The
curl
andgzip
commands are not present on the base system and need to be installed. dnf
is slow but can be made faster.
We end up with the following configure_opensuse()
command:
+# Configure an OpenSuse Linux System
+# @ see configure_rhel_like
+configure_opensuse() {
+ echo "max_parallel_downloads=10" >> /etc/dnf/dnf.conf
+ echo "fastestmirror=True" >> /etc/dnf/dnf.conf
+
+ configure_rhel_like dnf trusted curl gzip
+}
+
username=$(cat /etc/os-release | grep ^ID= | cut -d= -f 2 | tr -d '"' | cut -d"-" -f 1)
if [ -z "$username" ]; then
echo "Can't find distribution flavor"
Important
When a error occurs on gitstatus initialization, executing the following is useful for debugging:
Once the modifications are performed, the full test cycle is the following:
PS> Remove-WslInstance suse
PS> New-WslInstance suse -From https://download.opensuse.org/tumbleweed/appliances/opensuse-tumbleweed-dnf-image.x86_64-lxc-dnf.tar.xz
...
PS> Invoke-WslConfigure suse -Verbose
COMMENTAIRES : Piping wsl.exe with arguments: --list --verbose
COMMENTAIRES : Opération « Configure instance » en cours sur la cible « suse ».
⌛ Running initialization script [C:\Users\AntoineMartin\Documents\WindowsPowerShell\Modules\Wsl-Manager/configure.sh] on instance [suse]...
COMMENTAIRES : Running wsl.exe with arguments: -d suse -u root ./configure.sh
COMMENTAIRES : Output:
We are on opensuse
Adding packages...
Change root shell to zsh
Adding oh-my-zsh...
Configuring root home directory /root...
Configuring user opensuse...
Configuring opensuse home directory /home/opensuse...
Configuration done.
<end of output>
🎉 Configuration of instance [suse] completed successfully.
Name State Version Default
---- ----- ------- -------
suse Stopped 2 False
PS> wsl -d suse -u opensuse
[powerlevel10k] fetching gitstatusd .. [ok]
❯ id
uid=1000(opensuse) gid=1000(opensuse) groups=1000(opensuse),42(trusted)
❯ exit
PS>
Adding the flavor to the matrix¶
The .github/templates/builtins_matrix.json.tpl
file contains the image URLs
for each builtin distribution. Add an entry with the name opensuse
and the
above URL:
{
"include": [
...
{
"flavor": "opensuse",
"version": "latest",
"url": "https://download.opensuse.org/tumbleweed/appliances/opensuse-tumbleweed-dnf-image.x86_64-lxc-dnf.tar.xz"
}
]
}
You also need to add the opensuse
flavor has one of the possible values of the
workflow dispatch:
.github/workflows/build-rootfs-oci.yaml | |
---|---|
You can now trigger the workflow manually from your fork of the repository:
Once built, the image should appear in the project's packages.
Testing the installation of the image¶
Once the docker image is built and available in the github registry, you can test it with Wsl-Manager:
We can test built image with Wsl-Manager:
PS> New-WslInstance suse -From docker://ghcr.io/yourorganisation/powershell-wsl-manager/opensuse#latest | iwsl
...
[powerlevel10k] fetching gitstatusd .. [ok]
/mnt/c/Users/AntoineMartin 07:36:00
❯
You can now commit your modifications and make a pull request
.