A dive into ostree
My dive into ostree, which is system of versioning updates for Linux-based operating systems.
There are two ways to go when looking at ostree, using the command line
interface (CLI) or the more application programming interface (API). Starting
with the CLI version is simpler and less overhead to get started (as you
don’t need to start writing code) and it what the introduction shows.
The downside is it harder ot keep track of what you did unless you write it
as a shell script.
Getting Started
Starting out on Alpine Linux, the first step is to install the ostree tooling
by installing the package with it.
apk add ostree
Create a new OSTree repository in a folder called alpine_repo using the
sub-command init.
ostree --repo=alpine_repo init
Prepare a tree (directory) to add and add mini root file-system for Alpine as the starting point for a more detailed example. The official introduction starts with creating a text file and adds that instead.
Download the mini root filesystem for Alpine.
wget https://dl-cdn.alpinelinux.org/alpine/v3.22/releases/x86_64/alpine-minirootfs-3.22.2-x86_64.tar.gz
Commit the contents of the root file system as the first commit in a new branch.
ostree --repo=alpine_repo commit --branch=alpine-v3.22.2-x86_64 --tree=tar=alpine-minirootfs-3.22.2-x86_64.tar.gz \
--subject "Add minimal root file system for Alpine 3.22.1 (x86_64)"
As implied by the above command, the ostree tooling supports operating directly from a tar file.
The alternative would be to:
([ -d starting-tree ] || mkdir starting-tree) && tar xf alpine-minirootfs-3.22.2-x86_64.tar.gz -C starting-tree
ostree --repo=alpine_repo commit --branch=alpine-v3.22.2-x86_64 --tree=dir=starting-tree \
--subject "Add minimal root file system for Alpine 3.22.1 (x86_64)"
Can query the reference which should list the branch we just made
ostree --repo=alpine_repo refs
Outputs: alpine-v3.22.2-x86_64
Check out the contents
ostree --repo=alpine_repo ls alpine-v3.22.2-x86_64
Check out the contents of a file
ostree --repo=alpine_repo cat alpine-v3.22.2-x86_64 /usr/lib/os-release
Gotcha: The path /etc/os/release doesn’t work as it ostree seems to
struggle with the symlinks.
To checkout the entire tree:
ostree --repo=alpine_repo checkout alpine-v3.22.2-x86_64 tree-checkout
Installing packages
On the Writing a buildsystem and managing repositories page of ostree’s documentation, it mentions there are are systems the following the pattern:
$pkg --installroot=/path/to/tmpdir install foo bar baz
$imagesystem commit --root=/path/to/tmpdir
Where $pkg would be apk in the case of Alpine Linux, but otherwise
would be yum or apt-get.
So with that in mind lets install curl in a new commit.
Installing curl
If you are following along on a system other than Alpine, use apk.static.
The aspect that is unclear would the process be to checkout the latest commit,
run apk add in that checkout, then commit and thus let it handle the diff.
Or do you install to a new directory and then commit it and if so how do you
tell it what is already installed? A way that comes to mind for the second
approach is to mount a overlayfs with the checkout and a working/writable area
which would be the directory to commit afterwards.
First Option
This is the option where a checkout is performed from ostree and then it is install there.
ostree --repo=alpine_repo checkout alpine-v3.22.2-x86_64 staging-area
apk add --root staging-area --no-cache curl
ostree --repo=alpine_repo commit --branch=alpine-v3.22.2-x86_64 --tree=dir=staging-area --subject "Adding curl."
Can see the changes with:
ostree diff --repo=alpine_repo alpine-v3.22.2-x86_64
In this case, the output is:
M /etc/apk/world
M /lib/apk/db/installed
M /lib/apk/db/scripts.tar
A /usr/bin/curl
A /usr/bin/wcurl
A /usr/lib/libbrotlicommon.so.1
A /usr/lib/libbrotlicommon.so.1.1.0
A /usr/lib/libbrotlidec.so.1
A /usr/lib/libbrotlidec.so.1.1.0
A /usr/lib/libbrotlienc.so.1
A /usr/lib/libbrotlienc.so.1.1.0
A /usr/lib/libcares.so.2
A /usr/lib/libcares.so.2.19.4
A /usr/lib/libcurl.so.4
A /usr/lib/libcurl.so.4.8.0
A /usr/lib/libidn2.so.0
A /usr/lib/libidn2.so.0.4.0
A /usr/lib/libnghttp2.so.14
A /usr/lib/libnghttp2.so.14.28.4
A /usr/lib/libpsl.so.5
A /usr/lib/libpsl.so.5.3.5
A /usr/lib/libunistring.so.5
A /usr/lib/libunistring.so.5.2.0
A /usr/lib/libzstd.so.1
A /usr/lib/libzstd.so.1.5.7
A /var/cache/apk/APKINDEX.6c16d705.tar.gz
A /var/cache/apk/APKINDEX.76ae5dea.tar.gz
Can test it:
ostree --repo=repo checkout alpine-v3.22.2-x86_64 with-curl
chroot with-curl
curl --help
With those commands curl won’t work as the no way to resolve the host within the new root, as changing the root and having working network requires extra steps.
The idea of construction trees with unions - where packages are committed to separate branches is an interesting approach.
Second Option
Follow the pattern from the documentation and utilise the --installroot
command.
Writing a script to help with this would be ideal where it would configure
apk based on the contents within ostree, but since this is the first time
need to figure out the steps anyway.
- Determine repository paths
ostree --repo=alpine_repo cat alpine-v3.22.2-x86_64 /etc/apk/repositories > tmp.repositories - Install the packages into a folder
apk add --initdb --allow-untrusted --logfile=no --repositories-file tmp.repositories --root pkg.curl --no-cache curl--initdb- This tellsapkto set-up the database otherwise the following errors occur:ERROR: Unable to lock database: No such file or directory ERROR: Failed to open apk database: No such file or directory--allow-untrusted- This is a quick and dirty hack to avoid the issues that the packages won’t be trusted due to the lack of signatures. Ideally, they would be pulled out from theostreetoo.`UNTRUSTED signature` happens--logfile=no- prevents the creation of/var/log/apk.log
- Commit the package as its own branch.
ostree --repo=alpine_repo commit --branch=alpine-v3.22.2-x86_64_curl --tree=dir=pkg.curl --subject "Adding curl."It would be nice to include the version number in the subject, but that is more of a nice to have.
- Lets create new branch for teh base system, ideally when populating it from the rootfs this would have been the
branch created.
ostree --repo=alpine_repo commit --branch=alpine-v3.22.2-x86_64_base --tree=ref=$(ostree --repo=alpine_repo rev-parse alpine-v3.22.2-x86_64) - Generate the union of it.
rm -rf alpine-build ostree --repo=alpine_repo checkout -U --union alpine-v3.22.2-x86_64_base alpine-build ostree --repo=alpine_repo checkout -U --union alpine-v3.22.2-x86_64_curl alpine-build ostree --repo=alpine_repo commit --branch=alpine-v3.22.2-x86_64 --link-checkout-speedup alpine-build - Confirm contents
ostree --repo=alpine_repo ls alpine-v3.22.2-x86_64 /usr/binThis should list all the busybox tools plus
curl.
The original idea for the first step was convert the output from ostree cat
into --repository <path> but that has the problem of needing to deal with
commented out lines, not that there should be any in the minirootfs but if
later steps modify it then best to make sure it still works.
My concern with this approach is the pkg.curl/etc/apk/world file will simply
contain curl and that will conflict with the root filesystem.
Sure enough, that is a problem with the above steps, the resulting
/etc/apk/world file only contains curl and lacks alpine-baselayout and
the other packages from the minimal root file system.
This is as documented from --union which is to:
Keep existing directories, overwrite existing files
Questions
How to go about creating branches where they start to diverge? For example, imagining having a common base then splitting it off into server vs desktop, where desktop branch which has X11 or Wayland installed. From there you could then imagine branching off so you had a branch for each desktop environment.
What packages have side-effects in Alpine?
- Is it easy to tell what packages have scripts that run at installation time?
- Is it possible to delay running them until later? Such as running one step before a final image is made.
Next
This can’t be turned into a bootable image as there is no kernel.
Nominally would need to install linux-virt, similar to how curl was installed.
apk add --root staging-area --no-cache linux-virt
The other thing to consider is rpm-ostree , which it takes as input RPMs, and
commits them. This means there could be a apk-ostree created which would
take an input APK (Alpine Package Keeper file) and commit them too.
Revisit the second option for installing packages to address the merging files
such as /etc/apk/world.