Source Tree
If you need more control or just want to take advantage of your CPU, you can compile the FreeBSD source tree in at least three ways using AppJail.
fetch src
This is a method of the fetch
command and its use is very simple.
# appjail fetch src
[00:00:02] [ info ] Build log will be releases/default/build/2023-10-12_06h18m40s.log
[00:00:02] [ info ] Starting installworld with 8 jobs ...
[00:03:57] [ info ] installworld finished!
[00:03:57] [ info ] Starting distrib-dirs ...
[00:04:02] [ info ] distrib-dirs finished!
[00:04:02] [ info ] Starting distribution ...
[00:04:36] [ info ] distribution finished!
[00:04:36] [ info ] Starting delete-old delete-old-libs ...
[00:05:28] [ info ] delete-old delete-old-libs finished!
The above command assumes a few things. First, that you already have a source tree on your system, and second, that you ran buildworld
previously. Since both assumptions may not make sense in your case, the first thing to do is to obtain the source tree.
git clone --branch releng/13.2 --depth 1 https://github.com/freebsd/freebsd-src.git /usr/src
You did a shallow clone of a source tree that points to the releng/13.2
branch.
Let's build the world.
# appjail fetch src -b
[00:00:01] [ info ] Build log will be releases/default/build/2023-10-12_06h33m06s.log
[00:00:01] [ info ] Starting buildworld with 8 jobs ...
[00:14:00] [ info ] buildworld finished!
[00:14:00] [ info ] Starting installworld with 8 jobs ...
[00:18:14] [ info ] installworld finished!
[00:18:14] [ info ] Starting distrib-dirs ...
[00:18:20] [ info ] distrib-dirs finished!
[00:18:20] [ info ] Starting distribution ...
[00:18:55] [ info ] distribution finished!
[00:18:55] [ info ] Starting delete-old delete-old-libs ...
[00:19:48] [ info ] delete-old delete-old-libs finished!
As you can see, just add -b
and AppJail will do all the work for you.
If you want to read the build in progress:
appjail logs tail releases/default/build/2023-10-12_06h33m06s.log -f
Of course, this compilation has been completed, so you'll probably just want to get it all.
appjail logs read releases/default/build/2023-10-12_06h33m06s.log
By default only the world is compiled, but you can indicate AppJail that you want to compile the kernel (not necessary in almost all cases, except when you have to).
# appjail fetch src -bk
[00:00:01] [ info ] Build log will be releases/default/build/2023-10-12_06h53m47s.log
[00:00:01] [ info ] Starting buildworld with 8 jobs ...
[00:13:38] [ info ] buildworld finished!
[00:13:38] [ info ] Starting buildkernel with 8 jobs ...
[00:14:28] [ info ] buildkernel finished!
[00:14:28] [ info ] Starting installworld with 8 jobs ...
[00:18:29] [ info ] installworld finished!
[00:18:29] [ info ] Starting distrib-dirs ...
[00:18:35] [ info ] distrib-dirs finished!
[00:18:35] [ info ] Starting distribution ...
[00:19:09] [ info ] distribution finished!
[00:19:09] [ info ] Starting installkernel with 8 jobs ...
[00:19:40] [ info ] installkernel finished!
[00:19:40] [ info ] Starting delete-old delete-old-libs ...
[00:20:35] [ info ] delete-old delete-old-libs finished!
-k
is sufficient, AppJail just fires.
To indicate AppJail to cross-compile the world to another architecture, say, i386
, just add -a i386
.
appjail fetch src -a i386
This will set TARGET
to i386
and TARGET_ARCH
will be left empty, so the Makefile in the source tree will use a reasonable value. To specify TARGET_ARCH
explicitly just add -a target/target_arch
, for example, -a arm/armv7
.
Now we have a release, we can create a jail just like in a standard way.
appjail quick jtest \
overwrite=force \
start \
osarch=i386 \
osversion=13.2-RELEASE
Naming convention
As you can see above, the release name is used by default, which is not recommended when using appjail-fetch(1)
src
.
# appjail fetch list
ARCH VERSION NAME
...
amd64 13.2-RELEASE default
...
As you can see, there is not enough information for this release. Of course, the architecture, the version and the name, but when using appjail-fetch(1)
src
AppJail will set ARCH
to TARGET
instead of TARGET_ARCH
. As you can see below there is an ambiguity.
# make -C /usr/src targets
Supported TARGET/TARGET_ARCH pairs for world and kernel targets
amd64/amd64
arm/armv6
arm/armv7
arm64/aarch64
i386/i386
mips/mips
mips/mips64
powerpc/powerpc
powerpc/powerpc64
riscv/riscv64
riscv/riscv64sf
It is fine for amd64
and i386
, but not for arm
or other architecture. What we can do is set the release name to a meaningful value.
# appjail fetch src -a arm armv7
...
# appjail fetch list
ARCH VERSION NAME
...
arm 13.2-RELEASE armv7
...
There is a subtle difference when creating a jail: we need to add release=<release name>
.
appjail quick jtest \
overwrite=force \
start \
osarch=arm \
osversion=13.2-RELEASE \
release=armv7
Update
AppJail can update both jails and releases easily. Of course, updating an installed jail or a release coming from a source tree is different from doing it using the binary form (aka: freebsd-update(8)
).
jail
The requirement to update a jail is that it must be a thickjail, but to update using the source tree, i.e. appjail-update(1)
jail
, will make some assumptions. If the jail has a release directory that has the dummy file .from_src
, it means that this jail was installed using a source tree, so appjail-update(1)
jail
should proceed to update the jail using the source instead of the binary form. This, of course, creates a link between the jail and the release, which we must take into account when we are going to destroy the release, update the jail or export it to another system.
If we export the jail to another system we must export/import the release to that system unless we don't need to use the source tree of that system anymore. As a tip, you can export the jail and export the release with only the dummy files to have less files to send, ignoring the content of the release
directory, but of course, you will need to install the world and the kernel (if you want to).
Suppose we created a jail named jtest
, which is a thickjail (thinjails can be used, of course, but you cannot update a thinjail, so it is useless for the following example).
appjail quick jtest \
overwrite=force \
start \
osarch=amd64 \
osversion=13.2-RELEASE \
type=thick
Run the extract mode. This is only needed once.
# appjail etcupdate jail -m extract jtest
[00:00:00] [ info ] [jtest] etcupdate(8) log will be jails/jtest/etcupdate/2023-10-12_09h54m35s.log
Before running installworld
we need to run etcupdate -p
:
# appjail etcupdate jail jtest -p
[00:00:00] [ info ] [jtest] etcupdate(8) log will be jails/jtest/etcupdate/2023-10-12_10h01m40s.log
Now update the jail (and also build the world and, if any, the kernel):
# appjail update jail -b jtest
[00:00:01] [ info ] [jtest] Build log will be jails/jtest/build/2023-10-12_10h03m16s.log
[00:00:01] [ info ] [jtest] Starting buildworld with 8 jobs ...
[00:13:04] [ info ] [jtest] buildworld finished!
[00:13:04] [ info ] [jtest] Starting buildkernel with 8 jobs ...
[00:13:53] [ info ] [jtest] buildkernel finished!
[00:13:53] [ info ] [jtest] Starting installworld with 8 jobs ...
[00:18:01] [ info ] [jtest] installworld finished!
[00:18:01] [ info ] [jtest] Starting installkernel with 8 jobs ...
[00:18:33] [ info ] [jtest] installkernel finished!
[00:18:33] [ info ] [jtest] Done.
As you can see, AppJail not only build and install the world but also build and install the kernel. AppJail knows this depending on whether you successfully install the kernel when executing appjail-fetch(1)
src
. You can avoid running the buildkernel
and installkernel
target by using the -K
parameter in appjail-update(1)
jail
.
AppJail needs to know some details when running the required targets, such as TARGET
, TARGET_ARCH
(if any), KERNCONF
, and the source tree. AppJail knows these using dummy files of the release directory of that jail.
As you can see, there are some missing targets that are not executed compared to when we run appjail-fetch(1)
src
, this is because you first need to run etcupdate -B
and delete-old
and delete-old-libs
targets for yourself when you consider necessary.
# appjail etcupdate jail jtest -B
[00:00:00] [ info ] [jtest] etcupdate(8) log will be jails/jtest/etcupdate/2023-10-12_10h24m03s.log
# appjail checkOld jail jtest
...
# appjail deleteOld jail jtest
...
release
Although you can perfectly run appjail-update(1)
release
, this will not work as expected.
# appjail update release
[00:00:00] [ error ] [default] This is a release installed from a source tree, so you will have to run `appjail fetch src` by yourself.
As the command guesses, we need to use appjail-fetch(1)
src
. This is because appjail-update(1)
release
would not be able to guess a perfect workflow for all users, so it is preferable to leave the path clean for the user.
As you can see in the previous sections, all the targets that are needed to create a clean release are executed, but this implies that if we call appjail-fetch(1)
src
again, some files will be overwritten. This is fine if you don't have any problems. For example, if you have a thinjail, and you separate the data that must persist (mounted from the host to inside the jail) from the data that is ephemeral, you can recreate the jail and everything will work as expected, plus the new data from the recent update. If you have a thickjail, no problem, remember that the release and a thickjail does not share data in a similar way to thinjails. But if you really want to update a release like jails (see previous section), you just have to indicate appjail-fetch(1)
src
not to execute some targets, namely distrib-dirs
, distribution
, delete-old
and delete-old-libs
. Before updating, you should have run etcupdate extract
(this is only needed once).
# appjail etcupdate release -m extract
[00:00:00] [ info ] [default] etcupdate(8) log will be releases/default/etcupdate/2023-10-12_12h59m53s.log
# appjail etcupdate release - -p
[00:00:00] [ info ] [default] etcupdate(8) log will be releases/default/etcupdate/2023-10-12_13h12m35s.log
# appjail fetch src -bDNR
[00:00:01] [ info ] Build log will be releases/default/build/2023-10-12_13h15m19s.log
[00:00:01] [ info ] Starting buildworld with 8 jobs ...
[00:11:43] [ info ] buildworld finished!
[00:11:43] [ info ] Starting installworld with 8 jobs ...
[00:15:45] [ info ] installworld finished!
Once the previous work has been completed, it is sufficient to execute the rest.
# appjail etcupdate release - -B
...
# appjail checkOld release
...
# appjail deleteOld release
...
Empty
The other two ways to compile the source tree in AppJail is manually, through a single jail or a release that can be used by other jails. There is not much difference in how we use it to compile the source tree since they are empty directories, it is our responsibility to set DESTDIR
to the appropriate directory.
jail
There is not much magic in the following command, it simply creates an empty directory.
# appjail quick jtest empty overwrite=force noresolv_conf notzdata
...
# appjail cmd local jtest pwd
/usr/local/appjail/jails/jtest/jail
release
When we use appjail-fetch(1)
empty
, AppJail does not know what release and architectures we are going to use, although we can specify them using -a
and -v
, but for this example we do not specify them.
# appjail fetch empty
/usr/local/appjail/releases/any/any/default
If the directory does not exist, it is created and displayed in the output. If ls(1)
in it we see some interesting files.
# ls -A /usr/local/appjail/releases/any/any/default
.done .empty release
There are three files. .done
is to indicate to fetch empty
that this release has been successfully created, but it is irrelevant for this case. .empty
is a hint for other commands like appjail-update(1)
release
and appjail-upgrade(1)
release
not to update/upgrade this release. And finally and most importantly, the release
directory is empty and is the one we use to put the release files.
Profit!
# release
D=/usr/local/appjail/releases/any/any/default/release
# or jail
D=/usr/local/appjail/jails/jtest/jail
make -C /usr/src installworld -j$(nproc) DESTDIR=$D DB_FROM_SRC=1
make -C /usr/src distrib-dirs -j$(nproc) DESTDIR=$D DB_FROM_SRC=1
make -C /usr/src distribution -j$(nproc) DESTDIR=$D DB_FROM_SRC=1
In the case of a jail, when you successfully compile the source tree into it, just start it and play with it. When it comes to a release, just create a jail and play with it. That's it.
Update / Upgrade
If an attempt is made to update or upgrade an empty jail or an empty release, the following will occur:
# appjail update release -a any -v any default
[00:00:00] [ error ] [default] This is an empty release, so you will have to update it manually.
# appjail upgrade release -u -n 13.2-RELEASE -a any -v any default
[00:00:00] [ error ] [default] This is an empty release, so you will have to upgrade it manually.
# appjail update jail jtest
[00:00:00] [ error ] [jtest] jtest is not a thickjail.
Note
As you can guess, the empty release follows the same approach as jails that come from a source tree, since they have a dummy file indicating that they are an empty release, jails that use them depend on said release directory and its files.