Building LineageOS on FreeBSD

2025-02-27

My FreeBSD is so beefy that I want it to build some of my custom Android OS, However, it's not that simple as on linux, so I ended up using a linux jail just for that.

Setting Up Linux Jail

Enable Linux ABI:

doas sysrc linux_enable="YES"
doas service linux start

A thin jail is needed to bootstrap a Debian Linux Distribution.

Create a thin jail

Follow the FreeBSD Handbook, as shown below:

# Jail template
doas fetch https://mirrors.ustc.edu.cn/freebsd/releases/amd64/amd64/14.2-RELEASE/base.txz -o /usr/local/jails/media/14.2-RELEASE-base.txz
doas tar -xf /usr/local/jails/media/14.2-RELEASE-base.txz -C /usr/local/jails/templates/14.2-RELEASE --unlink

# Set up dns and time zone
doas cp /etc/resolv.conf /usr/local/jails/templates/14.2-RELEASE/etc/resolv.conf
doas cp /etc/localtime /usr/local/jails/templates/14.2-RELEASE/etc/localtime

# Update the jail to next patch level
doas freebsd-update -b /usr/local/jails/templates/14.2-RELEASE/ fetch install

# Snapshot the template
doas zfs snapshot zroot/jails/templates/14.2-RELEASE@base

# Clone snapshot from template
doas zfs clone zroot/jails/templates/14.2-RELEASE@base zroot/jails/containers/debian

Configuring the Linux Jail

We stop at the configuration part, and then we configure the jail with a special linux configuration:

doas jail -cm \
    name=debian \
    host.hostname="debian.latinus.local" \
    path="/usr/local/jails/containers/debian" \
    interface="re0" \
    ip4="inherit"\
    exec.start="/bin/sh /etc/rc" \ exec.stop="/bin/sh /etc/rc.shutdown" \
    mount.devfs \
    devfs_ruleset=4 \
    allow.mount \
    allow.mount.devfs \
    allow.mount.fdescfs \
    allow.mount.procfs \
    allow.mount.linprocfs \
    allow.mount.linsysfs \
    allow.mount.tmpfs \
    enforce_statfs=1

The following output should be seen, which means the jail is successfully created.

ELF ldconfig path: /lib /usr/lib /usr/lib/compat
32-bit compatibility ldconfig path: /usr/lib32
Updating motd:.
Creating and/or trimming log files.
Clearing /tmp (X related).
Updating /var/run/os-release done.
Starting syslogd.
Starting cron.

Thu Feb 27 15:16:25 CST 2025

Optionally, now you can change mirrors from /usr/local/jails/containers/debian, see my article for more info.

Bootstrap Debian

su into the jail and bootstrap debian:

pkg install debootstrap

# Optionally append a mirror url, like https://mirrors.cernet.edu.cn/debian/
debootstrap stable /compat/ubuntu

After the command finish, the I: Base system installed successfully. message should be displayed on the console.

Use exit to leave the jail, and write the following :

debian {
    # STARTUP/LOGGING
    exec.start = "/bin/sh /etc/rc";
    exec.stop = "/bin/sh /etc/rc.shutdown";
    exec.consolelog = "/var/log/jail_console_${name}.log";
    # PERMISSIONS
    allow.raw_sockets;
    exec.clean;
    mount.devfs;
    devfs_ruleset = 4;
    # HOSTNAME/PATH
    host.hostname = "${name}";
    path = "/usr/local/jails/containers/${name}";
    # NETWORK
    ip4 = inherit;
    interface = em0;
    # MOUNT
    mount += "devfs $path/compat/debian/dev devfs rw 0 0";
    mount += "tmpfs $path/compat/debian/dev/shm tmpfs rw,size=1g,mode=1777 0 0";
    mount += "fdescfs $path/compat/debian/dev/fd fdescfs rw,linrdlnk 0 0";
    mount += "linprocfs $path/compat/debian/proc linprocfs rw 0 0";
    mount += "linsysfs $path/compat/debian/sys linsysfs rw 0 0";
    mount += "/tmp $path/compat/debian/tmp nullfs rw 0 0";
    # Share /home directory
    # mount += "/home $path/compat/debian/home nullfs rw 0 0";
}

Stop the jail:

doas service jail onestop debian

Enable jail in rc.conf:

linux_enable="YES"
jail_enable="YES"
jail_parallel_start="YES"

And start the jail.

doas service jail start debian

Then, you can access the jail via:

doas jexec debian chroot /compat/debian /bin/bash

Fixing common problems

When restarted jail, it shows mount failure

When running the following, the error occurs:

# Notice stop is used without arguments.
service jail stop
service jail start debian
Starting jails: cannot start jail  "debian":
mount_nullfs: /usr/local/jails/containers/debian/compat/debian/tmp: Resource deadlock avoided
jail: debian: /sbin/mount -t nullfs -o rw /tmp /usr/local/jails/containers/debian/compat/debian/tmp: failed

This is because when stopping the jail, some mountpoints are not umounted, so, we need to manually umount:

Run the following with su is easier.

mount | grep -v zroot | grep debian

to see what needs to be u-mounted, then unmount them manually:

umount /usr/local/jails/containers/debian/compat/debian/dev/shm
umount /usr/local/jails/containers/debian/compat/debian/dev/fd
umount /usr/local/jails/containers/debian/compat/debian/dev
umount /usr/local/jails/containers/debian/compat/debian/proc
umount /usr/local/jails/containers/debian/compat/debian/sys
umount /usr/local/jails/containers/debian/compat/debian/tmp
umount /usr/local/jails/containers/debian/compat/debian/home

Back up the debian base system as template

cd /usr/local/jails
# Make new dataset
doas zfs create zroot/jails/templates/debian
# Copy files there
doas rsync -av containers/debian/ templates/debian/
# Take snapshot
doas zfs snapshot zroot/jails/templates/debian@base

Create new linux jails from the template

You should probably stop the original debian jail:

doas service jail stop debian
doas zfs clone zroot/jails/templates/debian@base zroot/jails/containers/debian_dev

And copy or move the debian jail to another one.

cd /etc/jail.conf.d
doas mv debian.conf debian_dev.conf
doas nvim $_

Then edit the debian name to something else, for this example, debian_dev

Then, start the jail:

doas service jail start debian_dev

Setting Up The Debian OS

Enter the jail

doas jexec debian_dev chroot /compat/debian /bin/bash

Install stuff, and set up a normal user account:

apt update
apt upgrade
apt install zsh sudo neovim man kitty-terminfo # Some stuff I found convenient
# Set up account
useradd --create-home --shell /bin/zsh android --groups sudo

Edit line 50 of /etc/sudoers:

# Allow members of group sudo to execute any command
%sudo   ALL=(ALL:ALL) NOPASSWD: ALL

Packages

Add contrib branch for debian, edit /etc/apt/sources.list

deb https://mirrors.cernet.edu.cn/debian stable main contrib

Then refresh the apt cache:

apt update

Su into the new user, and install packages from lineageOS's wiki: [1]

su -l android
sudo apt install bc bison build-essential ccache curl flex g++-multilib gcc-multilib git git-lfs gnupg gperf imagemagick lib32readline-dev lib32z1-dev libelf-dev liblz4-tool lz4 libsdl1.2-dev libssl-dev libxml2 libxml2-utils lzop pngcrush rsync schedtool squashfs-tools xsltproc zip zlib1g-dev

Also set the default python version:

sudo apt install python-is-python3

Some other dependencies:

sudo apt install repo android-sdk-platform-tools

Pull Source code

Configure git:

git config --global user.email "you@example.com"
git config --global user.name "Your Name"

Use a mirror for LineageOS pull: [2]

repo init -u https://mirrors.cernet.edu.cn/lineageOS/LineageOS/android.git -b lineage-21.0 --git-lfs

Then, follow along from either official documentation [3] or un-official ones [4].


  1. wiki link

  2. cernet

  3. official wiki

  4. github page