Two OSs, One Home Partition

Abstract

This project investigates the feasibility of sharing a single home partition between two bare-metal UNIX-like operating systems. The objective is to eliminate the redundancy and configuration complexity associated with virtual machines while maintaining consistent user environments across both systems.

The method involved manually partitioning a GPT disk into four segments: an EFI system partition, a root partition for each OS, and a shared home partition using ZFS. Special attention was given to maintaining consistent UIDs across both systems to preserve file ownership integrity. Custom bootup and shutdown procedures were implemented in each OS to ensure the ZFS pool was properly imported and exported.

Results demonstrate that both operating systems can reliably access and modify a shared home directory, with changes reflected immediately across environments.

Introduction

Sometimes one may find themself in need of both Linux and FreeBSD, especially when administrating a large and diverse network of both servers and clients. Many administrators find VMs adequate for this purpose, but networking, USB passthrough, and many other essential functions of a VM are difficult to set up correctly, and often break when the OS or VM host update. There is a desireable simplicity to running your OSs baremetal. But even running your OSs baremetal, doing away with having to do that extra networking and USB configuration for two different VMs, one may wish they could configure their FreeBSD & Linux not only simultaneously, but that any changes made to their configuration on one would be seamlessly and instantly applied to the other.

This project aims to share a partition mounted to /home/ between a dual-boot installation of FreeBSD & Linux.

Method

Equipment

Artix OpenRC was chosen largely because it is the Linux distribution I am most comfortable with. I am confident that this could easily be adapted to Gentoo with OpenRC, or even to a more traditional Systemd-based distribution such as Arch if you are comfortable with writing Systemd service scripts. However, it is important that a distro which can be installed without a graphical installer is used, as most graphical installers either do not provide the low-level control necessary to make this work or make changes to the shared partitions which will cause conflicts with FreeBSD.

Partition layout

slice№ size type filesystem purpose
1 512MiB efi msdosfs EFI boot partition
2 20GiB freebsd-ufs ufs FreeBSD root
3 20GiB linux-data ext4 Linux root
4 remaining zfs zfs Home partition

I decided to use ZFS as the shared filesystem as it's the only filesystem both Linux and FreeBSD both support reading from and writing to, which also supports UNIX file attributes.

Remember that applications and such will still be installed to the root, not the home partition. So if you're expecting to install a lot of big software or something, one may wish to embiggen those 20GiB root partitions as space allows.

There are no SWAP partitions. Through preliminary research, I determined FreeBSD and Linux SWAP partitions are not compatible, so one would need separate a SWAP partition for each OS. Most computers nowadays have enough RAM that you can live without SWAP, but if you really need SWAP, I would suggest using SWAPfiles instead.

Procedure

I decided to install FreeBSD first, because it has the user-friendly TUI Partition Editor, sade. This could be done the other way around if you're comfortable with fdisk or gpart or whatever else your Linux distro's installer comes with, but this makes it much easier to make sure your partitions (which will become difficult to keep track of) are all sane.

I followed the normal installation process up until I reached the Partitioning step. Instead of any of the guided options, I selected Manual Disk Setup.

FreeBSD Installer on the Partitioning step with Manual highlighted

I Created a GPT partition scheme. Then I Created a new partition of the Type efi, Size 512MB (remember, what FreeBSD refers to as "MB" are actually MiB), and Mountpoint /boot/efi. I chose this size because I needed to fit FreeBSD's bootloader, Linux's bootloader, and rEFInd in there. I acknowledge that it may be much more than necessary, but since drives are measured in terabytes nowadays, I was not worried about having a few MiB of unused space.

Add Partiton. Type: efi. Size: 512MB. Mountpoint: /boot/efi. Label is left empty.

Then I Created the partition for the FreeBSD system of the Type freebsd-ufs, Size 20GB, and Mountpoint /.

Add Partiton. Type: freebsd-ufs. Size: 20GB. Mountpoint: /. Label: FreeBSD.

Then I Created the partition for the Linux system of the Type linux-data, Size 20GB, and no Mountpoint.

Add Partiton. Type: linux-data. Size: 20GB. Mountpoint is left empty. Label: Linux.

I did not bother to give the Linux partition a mountpoint. This will be an ext4 filesystem, and FreeBSD can't read ext4 natively (despite prevalent claims to the contrary), so trying to mount it would be futile.

Then I finally Created the home partition, with the Type freebsd-zfs and Mountpoint /home. FreeBSD automatically populated the Size field with the value required to fill the remainder of the drive, so I left it at its default value of 59GB.

I am unsure why FreeBSD calls it freebsd-zfs, but it is no cause for concern—Linux mounts it just fine with OpenZFS.

The FreeBSD Handbook asserts that /home/ is simply a softlink to /usr/home/, and that we should mount our home partition there. However, during preliminary experimentation I determined this assertion untrue, and new users' home directories are created directly at /home/.

Add Partiton. Type: freebsd-zfs. Size: 59GB. Mountpoint: /home. Label: Home.

See the following figure for reference of the final partition table:

Partition Editor: ada0, 100GB, GPT; ada0p1, 512 MB, efi, /boot/efi; ada0p2, 20 GB, freebsd-ufs, /; ada0p3, 20 GB, linux-data; ada0p4, 59 GB, freebsd-zfs, /home

After double-, triple-, and quadruple-checking that I got everything right, I Finished the partitioning.

FreeBSD installed all the base files, asked for a root password, asked to set up networking interfaces, and so on. I completed those steps as normal until it asked if want to Add User Accounts, to which I answered Yes.

FreeBSD starts counting UIDs from 1001, while Linux starts counting UIDs from 1000. This is important because file attributes on the home partition will be stored by UID. In other words, for the same user to be able to own the same home folder on both OSs, they both must have the same UID. I chose to manually specify the UID 1000 here in FreeBSD, if only so I don't have to remember when I'm installing Linux later.

FreeBSD Installer:
Add Users:
Username: Naln1
Full name: Lucy Osmond
Uid (Leave empty for default): 1000
Login group [naln1]:
Login group is naln1. Invite naln1 into other groups? []: operator, wheel
Group operator, does not exist!
Login group is naln1. Invite naln1 into other groups? []: operator wheel
Login class [default]:
Shell (sh csh tcsh nologin) [sh]: csh
Home directory [/home/naln1]:
Home directory permissions (Leave empty for default):
Use password-based authentication? [yes]:
Use an empty password? (yes/no) [no]:
Use a random password? (yes/no) [no]:
Enter password:
Enter password again:
Lock out the account after creation? [no]:
Username: naln1
Password: *****
Full Name: Lucy Osmond
Uid: 1000
Class:
Groups: naln1 operator wheel
Home: /home/naln1
Home Mode:
Shell: /bin/csh
Locked: no
OK? (yes/no) [yes]:

While I was here, I decided to create the other users for the system. FreeBSD is smart enough to set the default UIDs sequentially.

I proceeded to exit the installer, and replied Yes to Manual Configuration.

If you are following this method, this would be a good opportunity to install your preferred shell and text editor, because I spent a lot of time in this shell before rebooting. I installed fish (Finally, a command line shell for the 90s!) for my shell, and decided to use ee as my text editor. If you choose to use a different shell or editor you can substitute references to mine with your own without affecting the results of the project.

I then opened /etc/rc.conf in ee and added the line zfs_enable="YES". This makes the ZFS service script run on startup, to import ZFS pools. Without this, the FreeBSD system would not be able to access the ZFS partition.

/etc/rc.conf open in ee. Before the cursor is a line 'zfs_enable="YES"'

I also created and opened /boot/loader.conf in ee and added the line zfs_load="YES" . This loads the kernel module for ZFS during boot, which allows FreeBSD to be able to recognize ZFS partitions as being valid filesystems at all.

/boot/loader.conf open in ee. The only line contains 'zfs_load="YES"'

These are the same steps FreeBSD would perform automatically if we followed Guided setup with root on ZFS, but because the system doesn't know exactly what we did with the petitioning table, we have to do this manually. If not for both of these steps, the kernel would freak out during boot and throw me into single-user mode, and nobody wants to boot into single-user mode if it can be avoided!

Normally FreeBSD imports ZFS pools using a cache file, and that would be ✨wonderful✨ if I were a normal person doing normal things. Unfortunately, I am not a normal person doing normal things. In order to safely mount the same ZFS pool to Linux, I need to export it on shutdown. But if a ZFS pool is not imported when FreeBSD shuts down, it gets removed from the cache file. So I had to manually inform FreeBSD to both import the home partition on boot, and export the home partition on shutdown.

I perceived the contents of /etc/fstab using cat, to take careful note of the Device name that associates with the /home mount. For me it was simply called home, but that is likely to differ due to reasons beyond our control.

During preliminary experimentation, I determined the best way to export the zpool before shutdown on stock FreeBSD is by modifying /etc/rc.shutdown. This is because the zpool rc script does not actively run as a daemon. Near the bottom of the file, I found a comment which reads # Insert other shutdown procedures here, and—with a grimace—placed zpool export home on its own line beneath that comment.

I propose a potential experiment to implement a rc.shutdown.d/ folder. However, that is unnecessary for, and beyond the scope of this project. There is also the secondary completed experiment of replacing FreeBSD's init system with OpenRC like Artix, which will be appended to this project.

/etc/rc.shutdown open in ee. Beneath the comment "Insert other shutdown procedures here" is a new comment "export the ZFS pool containing our home partition", and beneath that comment is 'zpool export home'

I proceeded to edit /etc/rc.d/zpool. Inside the function zpool_start(), on a new line just above the line local cachefile, I added the line zpool import home.

/etc/rc.d/zpool open in ee. Beneath the { delimiting the oepning of the zpool_start() function, but above the line 'local cachefile' is a new line 'zpool import home'

I decided to check my work by rebooting the system to make sure FreeBSD actually boots without any hiccups. I exited the shell(s), and then selected the conveniently-presented Reboot option. In doing this, I neglected the very important step of exporting the zpool before exiting the shell. To get back on track, once I was placed into single-user mode, I simply force mounted it with zpool import -f home, exported it with zpool export home, and then rebooted.

I logged in to my user, and ensured that my home directly is sufficiently populated with default dotfiles by using ls -la. I then proceeded to reboot the system with shutdown -r now. It is important not to use the similar reboot, as reboot doesn't run rc.shutdown, therefore not exporting the home partition ZFS pool, which would cause Artix to refuse to mount the partition.

This time the computer booted up, I set the EFI to boot the Artix live media instead of FreeBSD. I logged in to the live user, and immediately ran su.

I made use of fdisk -l to determine what device name I need for the Linux partition, by searching for the device name whose Type is Linux filesystem, which turned out to be sda3. I then made the ext4 filesystem in that partition by running mkfs.ext4 /dev/sda3.

I proceeded to install OpenZFS. The Artix wiki suggests setting the live media up for SSH, but that adds more steps than necessary in an already complex project, so I decided to skip that. I acquired ArchZFS's PGP key using curl -O https://archzfs.com/archzfs.gpg, imported the key into pacman with pacman-key -a archzfs.gpg, and locally-signed the key with pacman-key --lsign-key DDF7DB817396A49B2A2723F7403BD972F75D9D76. I then proceeded to add the ArchZFS repository to /etc/pacman.conf by appending the following snippet:

[archzfs]
Server = https://zxcvfdsa.com/archzfs/$repo/$arch

I chose the zxcvfdsa mirror over the upstream repository as it was geographically closer to me than the upstream, and because the upstream was hosted on Hetzner, whose published opinion is that drawings of family-friendly lesbian relationships constitutes pornography, while Linode—who hosts zxcvfdsa—did not have a published opinion concerning lesbian relationships at the time.
Revision 22/03/26: ArchZFS no longer hosts their own repository nor touts any mirrors. Instead the server URL should now be https://github.com/archzfs/archzfs/releases/download/experimental.

Finally I installed ArchZFS with pacman -Sy archzfs-dkms, and enabled the ZFS kernel module with modprobe zfs.

Then I proceeded to mount all of the partitions. I mounted the Linux root partition to /mnt with mount /dev/sda3 /mnt. I created /mnt/boot for the EFI boot partition using mkdir /mnt/boot and mounted it with mount /dev/sda1 /mnt/boot. I created /mnt/home for the home partition with mkdir /mnt/home, imported the zpool with zpool import home, set the ZFS mount point to legacy with zfs set mountpoint=legacy home so it behaves the same way as in FreeBSD, and finally mounted the home partition with mount -t zfs home /mnt/home.

Then I finally installed Artix to the disk. Artix uses basestrap, which is practically the same as Arch's pacstrap. The excellent thing about both of these tools is that it's basically a full pacman for alternative roots. I installed the Artix base system and all necessary extensions to the disk with one command, basestrap /mnt base openrc elogind-openrc linux linux-firmware linux-headers dkms archzfs-dkms, deciding to hold off on installing convenience packages until chrooting into the new system. Then I generated the FileSystem TABle with fstabgen -U /mnt >> /mnt/etc/fstab, and copied pacman.conf from the live media to the new Artix installation so the ArchZFS repo is available to the new system with cp /etc/pacman.conf /mnt/etc/pacman.conf.

I proceeded to chroot into my new Artix installation with artix-chroot /mnt, install convenience packages fish and nano, set up Time Zone & Locale, networking, and a root password. I created a user with useradd -m naln1, where it warned me that user's home directory already exists. This was fine because FreeBSD already created that home directly, and the whole point of this project is for both systems to have the same home directory! Then I set that user's password using passwd naln1.

root@localhost /# useradd -m naln1
useradd: warning: the home directory /home/naln1 already exists.
useradd: Not copying any file from skel directory into it.
root@localhost /# passwd naln1
New password:
Retype new password:
passwd: password updates successfully
root@localhost /#

Then I created OpenRC service scripts to import and export the zpool on startup and shutdown, because—unlike FreeBSD—Artix doesn't automatically mount previously-mounted ZFS partitions in any way. I created two files in /etc/init.d/. I filled /etc/init.d/zfs-import-home with

#!/usr/bin/openrc-run

depend() {
    before localmount
}

start() {
    ebegin "Importing Zpool 'home'"
    /usr/bin/zpool import home
    eend $?
}
(this script can be downloaded from code.naln1.ca/OpenRC-scripts/raw/tip/Artix/init.d/zfs-import-home)
and I filled /etc/init.d/zfs-export-home with
#!/usr/bin/openrc-run

start() {
    ebegin "Exporting Zpool 'home'"
    /usr/bin/zpool export home
    eend $?
}
(this script can be downloaded from code.naln1.ca/OpenRC-scripts/raw/tip/Artix/init.d/zfs-export-home).
Then I made the scripts executable with chmod +x /etc/init.d/zfs-import-home & chmod +x /etc/init.d/zfs-export-home, and enabled the services with rc-update add zfs-import-home & rc-update add zfs-export-home shutdown. I made sure to set zfs-export-home's runlevel to shutdown so that it runs on shutdown instead of during bootup, which would undo the zfs-import-home service's hard work!

Then I used Artix as a vehicle to install the rEFInd boot manager. A bootloader is necessary to load the two OSs. Although FreeBSD can bootload itself, Linux unfortunately cannot and needs something else—most commonly GRUB —to get it to boot. However, I do not generally have good results using GRUB, and therefore prefer to use rEFInd, which also comes with the benefit of being a little prettier. First I installed the rEFInd package with pacman -S refind. In contrast to what you may expect, the rEFInd package doesn't actually install rEFInd—it just installs the ability to install rEFInd, so I actually installed rEFInd with the aptly-named command refind-install. Part of its output contained the line Creating //boot/refind_linux.conf; edit it to adjust kernel options., and I did proceed to edit //boot/refind_linux.conf as rEFInd incorrectly tries to boot from the label of the live media I was currently using instead of the drive Artix was installed on (silly rEFInd 😅). The last line in refind_linux.conf ("Boot with minimal options") does however contain the correct UUID, so I copied the root=UUID=f61c7459-4796-4086-824a-adc346679023 to the beginnings of the first two lines, and removed the label=ARTIX_202308s from those same lines.

"Boot with standard options" "lang=en_US keytable=us tz=UTC label=ARTIX_202308"
"Boot to single-user mode" "lang=en_US keytable=us tz=UTC label=ARTIX_202308 single"
"Boot with minimal options" "ro root=UUID=f61c7459-4796-4086-824a-adc346679023" "Boot with standard options" "root=UUID=f61c7459-4796-4086-824a-adc346679023 lang=en_US keytable=us tz=UTC"
"Boot to single-user mode" "root=UUID=f61c7459-4796-4086-824a-adc346679023 lang=en_US keytable=us tz=UTC single"
"Boot with minimal options" "ro root=UUID=f61c7459-4796-4086-824a-adc346679023"

After that lengthy and tiring process, I was excited to be done! However, I had to reboot into Artix and confirm everything works as expected before I could declare success. I remembered to run zpool export home before using shutdown -r now to reboot the system. Thankfully I was met with a—admittedly kitsch—rEFInd boot screen!

rEFInd graphical boot manager displaying Linux and FreeBSD as boot options

I then booted into Artix, and once again made sure the same dotfiles from FreeBSD were there with ls -la, as well as confirming the user and group owners match my username (and not another user's, or an unassigned UID!). I then proceeded to reboot back and forth between FreeBSD and Artix to confirm the ZFS home partition is being dismounted and remounted correctly for each boot.

Conclusion

I was able to effectively boot two UNIX-like operating systems, Artix Linux and FreeBSD, with one home partition shared between them. It is a long and complicated process, and requires using some bespoke methodology, but it seems acceptably stable. However, this experiment is inconclusive about long-term issues that may surface as a result of continuing use of the system with the shared home partition.

Installing OpenRC on FreeBSD

Abstract

This experiment investigated the feasibility of deploying the OpenRC init system on FreeBSD. OpenRC was built from source due to the absence of a precompiled package. Custom service scripts were adapted to handle ZFS pool management during system startup and shutdown, alongside additional configuration of hostname, networking, and urandom services to ensure compatibility with FreeBSD.

The system successfully booted into the OpenRC-managed environment, demonstrating stable operation and correct execution of configured services. Minor, non-critical warnings were observed during shutdown but did not affect functionality. The results indicate that OpenRC can be effectively integrated into FreeBSD with modest adjustments.

Introduction

Editing rc.shutdown directly to export the zpool on shutdown is not preferable. OpenRC is actually tested against FreeBSD during development, and we already have some OpenRC service files we wrote for the zpools earlier, so why not install OpenRC on FreeBSD to solve that problem, and for consistency between both OSs?

Method

As OpenRC was not available as a package for FreeBSD, I determined that I must build it from source. By reading the README file for OpenRC, I determined that I would need Git to access the code, and Meson to build the software. So, I installed Git & Meson in one command with pkg install git meson. I then cloned OpenRC's Git repository with https://github.com/OpenRC/openrc.git and descended into the source with cd openrc.

I determined, for the purpose of stability, that it would be best to switch away from bleeding edge, to the most recent stable tag, which was 0.54, with git checkout tags/0.54.

OpenRC's README.md states "OpenRC uses the meson build system, so use the usual methods for this build system to build and install.", however, I didn't have any understanding of the Meson build system, so had to do much research to learn this secret skill. I then carried out the following process: create a directory for building in with mkdir build, configure the build process with meson setup build, build OpenRC with ninja -C build, then finally install OpenRC with DESTDIR=/ ninja -C build install.

Relieved that the build and installation was a relatively simple process, I was excited to complete the NeXTSTEP: configuring zpool import & export. Using the same method as described in Two OSs, One Home Partition, although with slightly tweaked scripts, I created and enabled OpenRC service scripts for importing and exporting the zpools on startup and shutdown. The modified scripts may be viewed/downloaded at code.naln1.ca/OpenRC-scripts/raw/tip/FreeBSD/init.d/zfs-import-home, code.naln1.ca/OpenRC-scripts/raw/tip/FreeBSD/init.d/zfs-export-home.

I then reconfigured my hostname at /etc/conf.d/hostname, and my networking by appending ifconfig_em0="!dhclient \$int" to /etc/conf.d/network for DHCP IP configuration.

I also made tweaks to the urandom initialisation service so it functions correctly on FreeBSD. I edited /etc/conf.d/urandom, setting the key urandom_seed to /usr/lib/misc/random-seed, and then created the misc directory with mkdir /usr/lib/misc/.

Then, I finally rebooted the system with shutdown -r now to test the newly-installed OpenRC boot process.

Result

The system booted effectively and without any complaints!

On shutdown I noticed some complaints from OpenRC that aren't matched by the startup services, but they do not seem to impact the function of the system.

Conclusion

OpenRC runs excellently on FreeBSD, with a little bit of tweaking. All system resources seemingly function as intended. However, there are no official OpenRC service scripts for FreeBSD, so any new services added to FreeBSD would require either writing a new service script, or scrounging and modifying OpenRC scripts from Artix & Gentoo.