Implementing AppVMs in FreeBSD with Overlord, AppJail and Xpra
Apr 27, 2026 - ⧖ 101 min
Security is no longer just an add-on, as it once was, and modern operating systems tend to be increasingly complex. We must not only be concerned about the security of our operating systems but also the security of the applications we use, such as a web browser, which is itself a complex system. But web browsers aren't the only applications primarily used to connect to Internet: an instant messaging app, which in some cases may implement a web engine, or even the application itself is a web browser (e.g.: Electron). However, anything that processes untrusted input (e.g.: an office suite) should be a cause for concern. There are three approaches to implement security 1: Security by Correctness, Security by Isolation, and Security by Obscurity. Rather than competing with each other, they mostly complement each other, although Security by Obscurity only difficult instead of implementing real security; Security by Correctness is impossible to fully implement but is even necessary to implement all other types of security; and Security by Isolation is the most robust when implemented correctly. Security by isolation is possible in the modern era thanks to VT-X and VT-D, and even through OS-level virtualization, such as FreeBSD jails. With security by isolation, we could implement a ZTA-based environment to run applications, and in the case of this article, X11 applications.
Zero Trust Architecture: https://csrc.nist.gov/pubs/sp/800/207/final
The X Window System is a mature and powerful windowing system, but too powerful for current security standards 2. Even worse, in FreeBSD, web browsers doesn't have a sandbox implemented. Although there is a work in progress 3 to implement Capsicum in chromium, not all applications are or will be implemented to use it, and even if they were, we shouldn't assume that applications will use it correctly.
Implementing Security by Isolation in X11
Security by Isolation is a bit hard to implement, not only because it adds significant complexity, but also because applications aren't designed with that in mind. However, using FreeBSD jails and bhyve, we can implement it, and applications don't even need to be aware they're in an isolated environment, although this doesn't necessarily mean it will be any easier...
Alternatives
This is nothing new. Some people dedicate a significant part of their lives to learning the black magic we'll be using, and you probably know or at least remember some of the projects I'll mention.
QubeOS: This is the king. They implemented a linux-based distribution that relies on Xen to implement Security by Isolation. A lot of stuff we can learn from there. However, a bit overkill for me and probably for many people that are just... not so paranoid...
SpectrumOS: It could be described as the rival of QubeOS, whose goal is to be simpler and lighter, and which is based on KVM instead of Xen.
XLibre/XACE: XACE isn't yet production-ready, but if it is, it will likely become a strong competitor to Wayland. However, both Wayland and XLibre/XACE only isolate the application view, not the process level, which is the focus of this article.
QuBSD: It’s probably the one that comes closest to my goals. However, I prefer applications to adapt to my DE rather than my DE adapting to the applications (which is why I’ll be using Xpra, as you’ll see). On the other hand, Overlord is so powerful that I can implement, if not exactly the same thing, at least something similar.
Motivation
Overlord is a project that uses AppJail, Director, and vm-bhyve to implement all the black magic. All of these projects use FreeBSD technologies, which is why I chose it over the previous alternative. Another reason is that I prefer to isolate only the applications and privileged operating system services they use. I’ll explain this in more detail later.
Implementing AppVMs
In AppJail, the appjail-x11(1) command has been implemented to run X11 applications within a jail 4, but using an X server created by Xephyr(1) and authenticated via MIT-MAGIC-COOKIE-1. This is a good thing. However, a bit more automation is needed to deploy X11 applications that take advantage of this command, which is why I created x11appjail, allowing me to test and improve the appjail-x11(1) command, and I now use it to run my applications. The x11appjail project isn’t just a bunch of opinionated scripts; it also uses AppScript to build binaries with those scripts, which further improves the user experience. However, I won't go into any more detail than what's in that project's README.
Our "threat model" is as follows: applications often request more information than they actually need, either intentionally (e.g., marketing companies) or unintentionally (e.g., when they contain backdoors), or they may contain vulnerabilities that allow bad actors to steal information not only about what the application generates, but also about other applications. We can use x11appjail (or appjail-x11(1) if you need that flexibility), and that will solve many of the problems I've mentioned here, but there's a catch to this approach: applications often need OS services that are usually privileged, primarily accessed through devices in /dev, so these devices need to be exposed for the application to be useful. If the device contains vulnerabilities, you've lost the game. A vulnerability in such a device poses a risk to the host, and the jail won't protect you from it. However, the same doesn't apply to a VM. A VM can protect the host after a "jailbreak", thus significantly reducing the security gap. Of course, the problem with VMs is that they add a lot of overhead, so we can combine the best of both worlds: deploy a VM to run X11 applications (sys-x11) and use x11appjail to run those applications as a second layer of defense. Applications need access to the sound system, and while we could let bhyve(8) use hda to implement this, it's more robust 5 to PCI passthrough our device and configure the applications to use the sound remotely (from sys-x11 to sys-sound). The same applies to sys-net, which we'll PCI passthrough our NIC. This VM is the most important, not only because it allows us to get internet connection, but also because it isolates the driver and all untrusted input we receive. Unfortunately, I don't have a second GPU to PCI passthrough it to sys-x11, so applications will use software rendering. VirtualGL is one option, but remember that the goal of this article is to isolate privileges, not create a new path to the same problem.
Of course, you've probably already realized that if sys-net is vulnerable, traffic generated by the host and jails can be easily sniffed. That's true! But the same thing happens if you use your NIC on the host, and it's even worse.
Okay... Here we go. sys-net requires our NIC, but we need to deploy a VM, and this might require an internet connection (e.g.: to install packages or include Makejails)... The solution is to use AppJail images. We'll create two AppJail images: the first for the jail (remember, this is a vmjail) and the second for the VM. I'll use appjail-reproduce.
$ appjail-reproduce -fdb sys-jail sys-sound sys-vm sys-x11
[ debug ] Loading configuration file '/home/user/.config/appjail-reproduce/config.conf'
[ debug ] Projects defined from positional arguments: sys-jail sys-sound sys-vm sys-x11
[ info ] Started at Tue Apr 28 21:10:47 -04 2026
[ info ] [sys-jail] (1/4):
[ debug ] Locking 'sys-jail'
[ debug ] Executing 'touch -- /home/user/.reproduce/locks/sys-jail/lock'
[ debug ] Jail name for sys-jail is reproduce_507d4355-0ef0-4177-8918-f2de6141237d
[ info ] Logs: /home/user/.reproduce/logs/sys-jail/2026-04-28_21h10m47s.log
[ info ] > [sys-jail] (osarch:amd64, osversion:15, tag:latest):
[ info ] Executing Makejail: jail:reproduce_507d4355-0ef0-4177-8918-f2de6141237d, release:default, args:
[ debug ] Trying to stop and destroy 'reproduce_507d4355-0ef0-4177-8918-f2de6141237d'
[ debug ] Jail 'reproduce_507d4355-0ef0-4177-8918-f2de6141237d' not found.
[ info ] Execution time: 00:01:53
[ debug ] Stopping jail reproduce_507d4355-0ef0-4177-8918-f2de6141237d
[ info ] Exporting sys-jail
[ info ] Export time: 00:00:26
[ debug ] Trying to stop and destroy 'reproduce_507d4355-0ef0-4177-8918-f2de6141237d'
[ debug ] Destroying jail reproduce_507d4355-0ef0-4177-8918-f2de6141237d
[ debug ] Executing 'touch -- /home/user/.reproduce/run/sys-jail/done'
[ info ] [sys-sound] (2/4):
[ debug ] Locking 'sys-sound'
[ debug ] Executing 'touch -- /home/user/.reproduce/locks/sys-sound/lock'
[ debug ] Removing 'done' file
[ debug ] Executing 'rm -f -- /home/user/.reproduce/run/sys-sound/done'
[ debug ] Jail name for sys-sound is reproduce_9e244616-6832-4393-8448-c886b13f2496
[ debug ] Removing image 'sys-sound'
[ info ] Logs: /home/user/.reproduce/logs/sys-sound/2026-04-28_21h13m10s.log
[ info ] > [sys-sound] (osarch:amd64, osversion:15, tag:latest):
[ info ] Executing Makejail: jail:reproduce_9e244616-6832-4393-8448-c886b13f2496, release:default, args:
[ debug ] Trying to stop and destroy 'reproduce_9e244616-6832-4393-8448-c886b13f2496'
[ debug ] Jail 'reproduce_9e244616-6832-4393-8448-c886b13f2496' not found.
[ info ] Execution time: 00:01:47
[ debug ] Stopping jail reproduce_9e244616-6832-4393-8448-c886b13f2496
[ info ] Exporting sys-sound
[ info ] Export time: 00:00:39
[ debug ] Trying to stop and destroy 'reproduce_9e244616-6832-4393-8448-c886b13f2496'
[ debug ] Destroying jail reproduce_9e244616-6832-4393-8448-c886b13f2496
[ debug ] Executing 'touch -- /home/user/.reproduce/run/sys-sound/done'
[ info ] [sys-vm] (3/4):
[ debug ] Locking 'sys-vm'
[ debug ] Executing 'touch -- /home/user/.reproduce/locks/sys-vm/lock'
[ debug ] Removing 'done' file
[ debug ] Executing 'rm -f -- /home/user/.reproduce/run/sys-vm/done'
[ debug ] Jail name for sys-vm is reproduce_75f63b5a-05b1-42e3-9051-da061bf79fee
[ debug ] Removing image 'sys-vm'
[ info ] Logs: /home/user/.reproduce/logs/sys-vm/2026-04-28_21h15m41s.log
[ info ] > [sys-vm] (osarch:amd64, osversion:15, tag:latest):
[ info ] Executing Makejail: jail:reproduce_75f63b5a-05b1-42e3-9051-da061bf79fee, release:default, args:
[ debug ] Trying to stop and destroy 'reproduce_75f63b5a-05b1-42e3-9051-da061bf79fee'
[ debug ] Jail 'reproduce_75f63b5a-05b1-42e3-9051-da061bf79fee' not found.
[ info ] Execution time: 00:01:51
[ debug ] Stopping jail reproduce_75f63b5a-05b1-42e3-9051-da061bf79fee
[ info ] Exporting sys-vm
[ info ] Export time: 00:00:33
[ debug ] Trying to stop and destroy 'reproduce_75f63b5a-05b1-42e3-9051-da061bf79fee'
[ debug ] Destroying jail reproduce_75f63b5a-05b1-42e3-9051-da061bf79fee
[ debug ] Executing 'touch -- /home/user/.reproduce/run/sys-vm/done'
[ info ] [sys-x11] (4/4):
[ debug ] Locking 'sys-x11'
[ debug ] Executing 'touch -- /home/user/.reproduce/locks/sys-x11/lock'
[ debug ] Removing 'done' file
[ debug ] Executing 'rm -f -- /home/user/.reproduce/run/sys-x11/done'
[ debug ] Jail name for sys-x11 is reproduce_4f9859ba-40fa-4cd2-b688-21c53a9f4126
[ debug ] Removing image 'sys-x11'
[ info ] Logs: /home/user/.reproduce/logs/sys-x11/2026-04-28_21h18m08s.log
[ info ] > [sys-x11] (osarch:amd64, osversion:15, tag:latest):
[ info ] Executing Makejail: jail:reproduce_4f9859ba-40fa-4cd2-b688-21c53a9f4126, release:default, args:
[ debug ] Trying to stop and destroy 'reproduce_4f9859ba-40fa-4cd2-b688-21c53a9f4126'
[ debug ] Jail 'reproduce_4f9859ba-40fa-4cd2-b688-21c53a9f4126' not found.
[ info ] Execution time: 00:10:06
[ debug ] Stopping jail reproduce_4f9859ba-40fa-4cd2-b688-21c53a9f4126
[ info ] Exporting sys-x11
[ info ] Export time: 00:02:23
[ debug ] Trying to stop and destroy 'reproduce_4f9859ba-40fa-4cd2-b688-21c53a9f4126'
[ debug ] Destroying jail reproduce_4f9859ba-40fa-4cd2-b688-21c53a9f4126
[ debug ] Executing 'touch -- /home/user/.reproduce/run/sys-x11/done'
[ info ] ---
[ info ] Ended at Tue Apr 28 21:30:42 -04 2026
[ info ] Build time: 00:19:55
[ info ] Hits: 4
[ info ] Errors: 0
[ debug ] Trying to stop and destroy 'reproduce_4f9859ba-40fa-4cd2-b688-21c53a9f4126'
[ debug ] Jail 'reproduce_4f9859ba-40fa-4cd2-b688-21c53a9f4126' not found.
[ debug ] Unlocking 'sys-jail' ...
[ debug ] Unlocking 'sys-sound' ...
[ debug ] Unlocking 'sys-vm' ...
[ debug ] Unlocking 'sys-x11' ...
$ appjail image metadata info
Name : sys-jail (latest)
Build on :
- amd64
Image : amd64
- SHA256 = 694f025916e9178c1a1510dc46937fe82a3eb49af0026ce27d561d7bcd21b50a
- SIZE = 1813363566 (1.68GiB)
- TIMESTAMP = mar. 28 abr. 21:12:59 2026
Installed :
- /usr/local/appjail/cache/images/sys-jail/latest-amd64-image.appjail
Name : sys-sound (latest)
Build on :
- amd64
Image : amd64
- SHA256 = dcd6bafcd3f4d0d9baba927801bc8548f30a6fcdd8630d2f17b65fb72bbd52c4
- SIZE = 1672497354 (1.55GiB)
- TIMESTAMP = mar. 28 abr. 21:15:30 2026
Installed :
- /usr/local/appjail/cache/images/sys-sound/latest-amd64-image.appjail
Name : sys-vm (latest)
Build on :
- amd64
Image : amd64
- SHA256 = 20d80ffca22b2dbe43829133ea927e9ae33311c1cf52b956cf0af646178b850c
- SIZE = 1671923613 (1.55GiB)
- TIMESTAMP = mar. 28 abr. 21:17:58 2026
Installed :
- /usr/local/appjail/cache/images/sys-vm/latest-amd64-image.appjail
Name : sys-x11 (latest)
Build on :
- amd64
Image : amd64
- SHA256 = cf7d80e04f2eb4f71865b1f6f944ea45e31ed379851851b81dec3a05de1879ca
- SIZE = 3697619234 (3.44GiB)
- TIMESTAMP = mar. 28 abr. 21:30:18 2026
Installed :
- /usr/local/appjail/cache/images/sys-x11/latest-amd64-image.appjail
Don't worry about the result above; I'll explain everything byte by byte. Let's start with the first image.
~/.reproduce/projects/sys-jail/Makejail:
OPTION start
OPTION overwrite=force
OPTION alias
OPTION ip4_inherit
OPTION type=thick
CMD mkdir -p /usr/local/etc/pkg/repos
COPY PKG.conf /usr/local/etc/pkg/repos/PKG.conf
PKG FreeBSD-acpi vm-bhyve-devel bhyve-firmware tmux
~/.reproduce/projects/sys-jail/reproduce.conf:
tags: latest/15
arch: amd64
~/.reproduce/projects/sys-jail/PKG.conf:
FreeBSD-ports: {
url: "pkg+https://pkg.FreeBSD.org/${ABI}/latest",
mirror_type: "srv",
signature_type: "fingerprints",
fingerprints: "/usr/share/keys/pkg",
enabled: yes
}
FreeBSD-ports-kmods: {
enabled: no
}
FreeBSD-base: {
enabled: yes
}
Custom: {
url: "http://pkg.dyn.dc-air.home.arpa:4080/150amd64-local",
mirror_type: "http",
priority: 1,
enabled: yes
}
Oh, I forgot to mention that I'll be using a custom repository. You can omit this if you prefer. The deployment files for deploying NGINX in a jail with access to the repository created by poudriere are as follows:
poudriere-expose-repo/app.yml:
kind: directorProject
datacenters:
main:
entrypoint: !ENV '${ENTRYPOINT}'
access_token: !ENV '${TOKEN}'
deployIn:
labels:
- desktop
projectName: poudriere-expose-repo
projectFile: |
options:
- alias:
- ip4_inherit:
services:
static-www-server:
serial: 99
name: poudriere-expose-repo
makejail: !ENV '${OVERLORD_METADATA}/poudriere-expose-repo.makejail'
options:
- label: 'dyndns:1'
- label: !ENV 'dyndns.dyn-domain:${DYN_DOMAIN}'
- label: !ENV 'dyndns.hostname:${DYN_HOSTNAME}'
- label: 'dyndns.interface:tailscale0'
- label: 'dyndns.username:overlord'
- label: 'dyndns.password:/root/overlord/token'
- label: 'security-group:1'
- label: 'security-group.rules.allow:pass in on tailscale0 proto tcp to (tailscale0:0) port 4080'
volumes:
- poudriere-pkg: '/usr/local/www/pkg'
volumes:
poudriere-pkg:
device: '/usr/local/poudriere/data/packages'
type: 'nullfs'
options: 'ro'
environment:
DYN_DOMAIN: 'air-dyn-a.namespace.lan air-dyn-b.namespace.lan'
DYN_HOSTNAME: 'pkg.dyn.dc-air.home.arpa'
poudriere-expose-repo/metadata.yml:
kind: metadata
datacenters:
main:
entrypoint: !ENV '${ENTRYPOINT}'
access_token: !ENV '${TOKEN}'
deployIn:
labels:
- desktop
metadataPrefix: poudriere-expose-repo
metadata:
makejail: |
OPTION start
OPTION overwrite=force
INCLUDE gh+DtxdF/efficient-makejail
CMD mkdir -p /usr/local/etc/pkg/repos
COPY ${OVERLORD_METADATA}/poudriere-expose-repo.pkg.conf /usr/local/etc/pkg/repos/Latest.conf
PKG freenginx
COPY ${OVERLORD_METADATA}/poudriere-expose-repo.nginx.conf /usr/local/etc/freenginx/nginx.conf
SYSRC nginx_enable=YES
SERVICE nginx start
nginx.conf: |
user www;
worker_processes auto;
events {
worker_connections 1024;
}
http {
include /usr/local/etc/freenginx/mime.types;
default_type application/octet-stream;
sendfile on;
tcp_nopush on;
gzip on;
root /usr/local/www/pkg;
server {
listen 4080;
server_name pkg.dyn.dc-air.home.arpa "";
location / {
autoindex on;
}
}
}
pkg.conf: |
FreeBSD-ports: {
url: "file:///usr/local/www/pkg/150amd64-local",
signature_type: "none",
enabled: yes
}
FreeBSD-ports-kmods: {
enabled: no
}
Keep in mind that I'm assuming you've read this and installed the security-group hooks. Always update them after updating AppJail.
$ grep ^HOOKSDIR /usr/local/etc/appjail/appjail.conf
HOOKSDIR=/usr/local/etc/appjail/hooks
$ doas mkdir -p /usr/local/etc/appjail/hooks/pre.d
$ doas cp -a /usr/local/share/appjail/examples/hooks/pre.d/security-*.sh \
/usr/local/etc/appjail/hooks/pre.d/
My pf.conf(5) is quite based on what I described in that article.
/etc/pf.conf:
ext_if = "em0"
tailscale_if = "tailscale0"
tailscale_network = "100.64.0.0/10"
tailscale_port = "41641"
appjail_bridge = "ajnet"
appjail_network = "10.0.0.0/10"
overlord_port = "8888"
table <private-jails> persist
table <rfc6890> const { 0.0.0.0/8 10.0.0.0/8 100.64.0.0/10 127.0.0.0/8 169.254.0.0/16 \
172.16.0.0/12 192.0.0.0/24 192.0.0.0/29 192.0.2.0/24 192.88.99.0/24 \
192.168.0.0/16 198.18.0.0/15 198.51.100.0/24 203.0.113.0/24 \
240.0.0.0/4 255.255.255.255/32 }
set skip on lo
set block-policy return
set fail-policy return
set loginterface pflog0
scrub in
# Allow jails in this virtual network to send/receive packets from any tailnet.
nat on $tailscale_if from $appjail_network to $tailscale_network -> ($tailscale_if:0)
# AppJail
nat-anchor "appjail-nat/jail/*"
nat-anchor "appjail-nat/network/*"
rdr-anchor "appjail-rdr/*"
antispoof for lo0
# Policy
block in
pass out
# Security group
block in on $appjail_bridge from <rfc6890>
pass in on $appjail_bridge from $appjail_network to ! <rfc6890>
block in on $appjail_bridge from <private-jails> to ! <rfc6890>
anchor "appjail-filter/jail/*"
# ICMP
pass in proto icmp
# Enable some services
pass in on $tailscale_if proto tcp from $tailscale_network to ($tailscale_if:0) port { ssh, $overlord_port }
# Enable tailscale direct connection
pass in on $ext_if proto udp to port $tailscale_port
The second image corresponds to the VM itself.
~/.reproduce/projects/sys-vm/Makejail:
OPTION start
OPTION overwrite=force
OPTION alias
OPTION ip4_inherit
OPTION type=thick
CMD mkdir -p /usr/local/etc/pkg/repos
COPY PKG.conf /usr/local/etc/pkg/repos/PKG.conf
PKG FreeBSD-set-base FreeBSD-kernel-generic
CMD rm -f /usr/local/etc/pkg/repos/PKG.conf
~/.reproduce/projects/sys-vm/PKG.conf:
FreeBSD-ports: {
url: "pkg+https://pkg.FreeBSD.org/${ABI}/latest",
mirror_type: "srv",
signature_type: "fingerprints",
fingerprints: "/usr/share/keys/pkg",
enabled: yes
}
FreeBSD-ports-kmods: {
enabled: yes
}
FreeBSD-base: {
enabled: yes
}
Custom: {
url: "http://pkg.dyn.dc-air.home.arpa:4080/150amd64-local",
mirror_type: "http",
priority: 1,
enabled: yes
}
~/.reproduce/projects/sys-vm/reproduce.conf:
tags: latest/15
arch: amd64
The image is generic enough to be used for other purposes, but for now it will only be used on sys-net.
It's time to show the deployment files for sys-net.
xlayer/sys-net/app.yml:
kind: vmJail
datacenters:
main:
entrypoint: !ENV '${ENTRYPOINT}'
access_token: !ENV '${TOKEN}'
deployIn:
labels:
- desktop
vmName: sys-net
makejail: '/home/user/.reproduce/projects/sys-net/Makejail'
overwrite: true
template:
loader: 'bhyveload'
cpu: '4'
memory: '768'
network0_type: 'virtio-net'
network0_switch: 'public'
wired_memory: 'YES'
virt_random: 'YES'
passthru0: '0/31/6/0=2:0'
diskLayout:
driver: 'nvme'
size: '10G'
from:
type: appjailImage
entrypoint: 'none'
imageName: sys-vm
imageArch: amd64
imageTag: latest
disk:
scheme: 'gpt'
partitions:
- type: 'freebsd-boot'
size: '512k'
alignment: '1m'
- type: 'freebsd-swap'
size: '1G'
alignment: '1m'
- type: 'freebsd-ufs'
alignment: '1m'
format:
flags: '-Uj'
bootcode:
bootcode: '/boot/pmbr'
partcode: '/boot/gptboot'
index: 1
fstab:
- device: '/dev/nda0p3'
mountpoint: '/'
type: 'ufs'
options: 'rw,sync'
dump: 1
pass: 1
- device: '/dev/nda0p2'
mountpoint: 'none'
type: 'swap'
options: 'sw'
dump: 0
pass: 0
script-environment:
- HOSTNAME: 'sys-net'
- TIMEZONE: 'America/Caracas'
script: |
set -xe
set -o pipefail
. "/metadata/environment"
sysrc -f /mnt/etc/rc.conf fsck_y_enable="YES"
sysrc -f /mnt/etc/rc.conf clear_tmp_enable="YES"
sysrc -f /mnt/etc/rc.conf dumpdev="NO"
sysrc -f /mnt/etc/rc.conf moused_nondefault_enable="NO"
sysrc -f /mnt/etc/rc.conf hostname="${HOSTNAME}"
sysrc -f /mnt/etc/rc.conf cloned_interfaces="bridge0"
sysrc -f /mnt/etc/rc.conf ifconfig_bridge0="addm em0 addm vtnet0 up"
sysrc -f /mnt/etc/rc.conf ifconfig_vtnet0="up promisc"
sysrc -f /mnt/etc/rc.conf ifconfig_em0="up promisc"
cp -a /etc/resolv.conf /mnt/etc/resolv.conf
cp /metadata/sys-net.loader.conf /mnt/boot/loader.conf
ln -fs "/usr/share/zoneinfo/${TIMEZONE}" /mnt/etc/localtime
cp /metadata/sys-net.sysctl.conf /mnt/etc/sysctl.conf
mkdir -p /mnt/usr/local/etc/pkg/repos
cp /metadata/sys-net.FreeBSD.conf /mnt/usr/local/etc/pkg/repos/FreeBSD.conf
cp /metadata/sys-net.FreeBSD-base.conf /mnt/usr/local/etc/pkg/repos/FreeBSD-base.conf
metadata:
- sys-net.loader.conf
- sys-net.sysctl.conf
- sys-net.FreeBSD.conf
- sys-net.FreeBSD-base.conf
xlayer/sys-net/metadata.yml:
kind: metadata
datacenters:
main:
entrypoint: !ENV '${ENTRYPOINT}'
access_token: !ENV '${TOKEN}'
deployIn:
labels:
- desktop
metadataPrefix: sys-net
metadata:
loader.conf: |
kern.geom.label.disk_ident.enable="0"
kern.geom.label.gptid.enable="0"
cryptodev_load="YES"
nvme_load="YES"
if_bridge_load="YES"
bridgestp_load="YES"
kern.racct.enable=1
net.isr.maxthreads="-1"
net.isr.bindthreads="1"
net.isr.dispatch="hybrid"
net.isr.defaultqlimit="2048"
net.isr.maxqlimit="10240"
sshd_config: |
# Ports
Port 22
# Authentication
PubkeyAuthentication yes
AuthenticationMethods publickey
PermitRootLogin prohibit-password
PrintMotd no
# Forwarding
X11Forwarding no
AllowAgentForwarding yes
# Connection checks
ClientAliveCountMax 3
ClientAliveInterval 15
# Limits
LoginGraceTime 40
# Public keys
AuthorizedKeysFile /etc/ssh/authorized_keys
# SFTP
Subsystem sftp internal-sftp
sysctl.conf: |
# A bit of hardening
security.bsd.see_other_uids=0
security.bsd.see_other_gids=0
security.bsd.see_jail_proc=0
kern.randompid=1
# Allow packet filtering in if_bridge(4)
net.link.bridge.pfil_member=1
net.link.bridge.pfil_bridge=1
FreeBSD.conf: |
FreeBSD-ports: {
url: "pkg+https://pkg.FreeBSD.org/${ABI}/latest",
mirror_type: "srv",
signature_type: "fingerprints",
fingerprints: "/usr/share/keys/pkg",
enabled: yes
}
FreeBSD-ports-kmods: {
url: "pkg+https://pkg.FreeBSD.org/${ABI}/kmods_latest",
mirror_type: "srv",
signature_type: "fingerprints",
fingerprints: "/usr/share/keys/pkg",
enabled: yes
}
FreeBSD-base.conf: |
FreeBSD-base {
enabled: yes
}
Pay attention to the loader.conf metadata. Some network-related tunables are somewhat experimental 6 and must be entered in both the host's loader.conf(5) and in VM's loader.conf(5), but I'll explain this in more detail later. For now, the most important parameter in the template is passthru0. If you have sysutils/vm-bhyve installed on your host, you can run vm passthru to see all the PCI devices and whether they are ready for use by a VM; or, if you prefer, you can simply use pciconf(8).
$ doas vm passthru
DEVICE BHYVE ID READY DESCRIPTION
hostb0 0/0/0 No Xeon E3-1200 v6/7th Gen Core Processor Host Bridge/DRAM Registers
vgapci0 0/2/0 No Kaby Lake-S GT2 [HD Graphics 630]
xhci0 0/20/0 No 200 Series/Z370 Chipset Family USB 3.0 xHCI Controller
none0 0/20/2 No 200 Series PCH Thermal Subsystem
none1 0/22/0 No 200 Series PCH CSME HECI
none2 0/22/3 No 200 Series Chipset Family KT Redirection
ahci0 0/23/0 No 200 Series PCH SATA controller [AHCI mode]
pcib1 0/28/0 No 200 Series PCH PCI Express Root Port
isab0 0/31/0 No 200 Series PCH LPC Controller (Q270)
none3 0/31/2 No 200 Series/Z370 Chipset Family Power Management Controller
ppt0 0/31/3 Yes 200 Series PCH HD Audio
ichsmb0 0/31/4 No 200 Series/Z370 Chipset Family SMBus Controller
ppt1 0/31/6 Yes Ethernet Connection (5) I219-LM
iwm0 1/0/0 No Wireless 7265
$ grep ^pptdevs= /boot/loader.conf
pptdevs="0/31/3 0/31/6"
I've already configured my loader.conf(5), but don't forget to reboot after doing so.
The Makejail file and related files used to create the sys-net jail are as follows.
~/.reproduce/projects/sys-net/Makejail:
FROM --entrypoint none sys-jail
OPTION start
OPTION overwrite=force
OPTION bridge=sys_net bridge:sys_net
OPTION mount_devfs
OPTION device=include \$devfsrules_hide_all
OPTION device=include \$devfsrules_unhide_basic
OPTION device=include \$devfsrules_unhide_login
OPTION device=path pf unhide
OPTION device=path vmm unhide
OPTION device=path 'vmm/*' unhide
OPTION device=path vmm.io unhide
OPTION device=path 'vmm.io/*' unhide
OPTION device=path vmmctl unhide
OPTION device=path 'nmdm*' unhide
OPTION device=path 'tap*' unhide
OPTION device=path mem unhide
OPTION device=path kmem unhide
OPTION device=path pci unhide
OPTION device=path io unhide
OPTION template=template.conf
OPTION type=thick
RAW if [ -c "/dev/vmm/${APPJAIL_JAILNAME}" ]; then
CMD --local bhyvectl "--vm=${APPJAIL_JAILNAME}" --destroy
RAW fi
SYSRC vm_enable=YES
SYSRC vm_dir=/vm
RAW if appjail cmd local "${APPJAIL_JAILNAME}" [ ! -f vm/.done ]; then
CMD mkdir -p /vm && \
find /vm -depth 1 -not -name '.img' -and -not -name '.iso' -exec rm -rf {} + && \
vm init && \
cp /usr/local/share/examples/vm-bhyve/*.conf /vm/.templates && \
sysrc -f /vm/.config/system.conf console=tmux
RAW fi
SYSRC "cloned_interfaces+=bridge0"
SYSRC ifconfig_bridge0_name=vmbridge
SYSRC "ifconfig_vmbridge=addm sb_sys_net up"
SYSRC "ifconfig_sb_sys_net=up"
CMD service netif cloneup bridge0 && \
service netif start bridge0 && \
service netif start vmbridge
CMD ifconfig sb_sys_net up
RAW if appjail cmd local "${APPJAIL_JAILNAME}" [ ! -f vm/.done ]; then
CMD vm switch create -t manual -b vmbridge public
RAW fi
SYSRC syslogd_flags=-ss
SERVICE syslogd restart
STAGE create
RAW if [ -c "/dev/vmm/${APPJAIL_JAILNAME}" ]; then
CMD --local bhyvectl "--vm=${APPJAIL_JAILNAME}" --destroy
RAW fi
STAGE stop
CMD rm -f /vm/*/run.lock
~/.reproduce/projects/sys-net/template.conf:
exec.start: "/bin/sh /etc/rc"
exec.stop: "/bin/sh /etc/rc.shutdown jail"
mount.devfs
persist
allow.vmm
allow.vmm_ppt
allow.chflags
stop.timeout: 30
exec.clean
The Makejail is based on gh+DtxdF/vm-makejail. The only two relevant things I need to mention are allow.vmm_ppt 7, which allows us to PCI passthrough our PCI devices within a jail, and OPTION bridge=sys_net bridge:sys_net, to instruct AppJail to create a bridge named sys_net and add sa_sys_net to it (on the host). Pay attention to the below lines, where the other part of the if_epair(4) interface, sb_sys_net, is added to the vmbridge bridge. With all these tricks, we can use the sys_net bridge as our interface. Of course, on the VM side there are more tricks: a bridge is created to add both vtnet0 and em0, both configured in promiscuous mode.
Interfaces:
host(sys_net <-> sa_sys_net) <-> jail(sb_sys_net <-> vmbridge) <-> VM(vtnet0 <-> bridge0 <-> em0)
Since everything is ok, it's time to deploy the sys-net VM.
$ overlord apply -f xlayer/sys-net/metadata.yml
$ overlord apply -f xlayer/sys-net/app.yml
$ overlord get-info -f xlayer/sys-net/app.yml -t projects --filter-per-project
datacenter: http://127.0.0.1:8888
entrypoint: main
chain: None
labels:
- all
- desktop
- services
- vm-only
- dc-air
projects:
sys-net:
state: UNFINISHED
last_log: 2026-04-28_21h37m24s
locked: True
services:
- {'name': 'vm', 'status': 1, 'jail': 'sys-net'}
up:
operation: RUNNING
last_update: 37.84 seconds
job_id: 1
$ overlord get-info -f xlayer/sys-net/app.yml -t projects --filter-per-project
datacenter: http://127.0.0.1:8888
entrypoint: main
chain: None
labels:
- all
- desktop
- services
- vm-only
- dc-air
projects:
sys-net:
state: DONE
last_log: 2026-04-28_21h37m24s
locked: False
services:
- {'name': 'vm', 'status': 0, 'jail': 'sys-net'}
up:
operation: COMPLETED
output:
rc: 0
stdout: {'errlevel': 0, 'message': None, 'failed': []}
last_update: 19.44 seconds
job_id: 1
restarted: False
$ overlord get-info -f xlayer/sys-net/app.yml -t vm --filter-per-project
datacenter: http://127.0.0.1:8888
entrypoint: main
chain: None
labels:
- all
- desktop
- services
- vm-only
- dc-air
projects:
sys-net:
virtual-machines:
operation: COMPLETED
output: |
md0 created
md0p1 added
md0p2 added
md0p3 added
/dev/md0p3: 9214.0MB (18870272 sectors) block size 32768, fragment size 4096
using 15 cylinder groups of 625.22MB, 20007 blks, 80128 inodes.
with soft updates
super-block backups (for fsck_ffs -b #) at:
192, 1280640, 2561088, 3841536, 5121984, 6402432, 7682880, 8963328, 10243776,
11524224, 12804672, 14085120, 15365568, 16646016, 17926464
newfs: soft updates journaling set
Using inode 4 in cg 0 for 75497472 byte journal
bootcode written to md0
partcode written to md0p1
+ set -o pipefail
+ . /metadata/environment
+ export 'HOSTNAME=sys-net'
+ export 'TIMEZONE=America/Caracas'
+ sysrc -f /mnt/etc/rc.conf 'fsck_y_enable=YES'
fsck_y_enable: NO -> YES
+ sysrc -f /mnt/etc/rc.conf 'clear_tmp_enable=YES'
clear_tmp_enable: NO -> YES
+ sysrc -f /mnt/etc/rc.conf 'dumpdev=NO'
dumpdev: NO -> NO
+ sysrc -f /mnt/etc/rc.conf 'moused_nondefault_enable=NO'
moused_nondefault_enable: YES -> NO
+ sysrc -f /mnt/etc/rc.conf 'hostname=sys-net'
hostname: -> sys-net
+ sysrc -f /mnt/etc/rc.conf 'cloned_interfaces=bridge0'
cloned_interfaces: -> bridge0
+ sysrc -f /mnt/etc/rc.conf 'ifconfig_bridge0=addm em0 addm vtnet0 up'
ifconfig_bridge0: -> addm em0 addm vtnet0 up
+ sysrc -f /mnt/etc/rc.conf 'ifconfig_vtnet0=up promisc'
ifconfig_vtnet0: -> up promisc
+ sysrc -f /mnt/etc/rc.conf 'ifconfig_em0=up promisc'
ifconfig_em0: -> up promisc
+ cp -a /etc/resolv.conf /mnt/etc/resolv.conf
+ cp /metadata/sys-net.loader.conf /mnt/boot/loader.conf
+ ln -fs /usr/share/zoneinfo/America/Caracas /mnt/etc/localtime
+ cp /metadata/sys-net.sysctl.conf /mnt/etc/sysctl.conf
+ mkdir -p /mnt/usr/local/etc/pkg/repos
+ cp /metadata/sys-net.FreeBSD.conf /mnt/usr/local/etc/pkg/repos/FreeBSD.conf
+ cp /metadata/sys-net.FreeBSD-base.conf /mnt/usr/local/etc/pkg/repos/FreeBSD-base.conf
vm_list: -> sys-net
Starting sys-net
* found guest in /vm/sys-net
* booting...
last_update: 16.57 seconds
job_id: 1
$ appjail cmd jexec sys-net vm list sys-net
NAME DATASTORE LOADER CPU MEMORY VNC AUTO STATE TAGS
sys-net default bhyveload 4 768 - Yes [1] Running (48070) -
$ ifconfig sys_net
sys_net: flags=1008843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST,LOWER_UP> metric 0 mtu 1500
options=10<VLAN_HWTAGGING>
ether 58:9c:fc:10:6a:01
id 00:00:00:00:00:00 priority 32768 hellotime 2 fwddelay 15
maxage 20 holdcnt 6 proto rstp maxaddr 2000 timeout 1200
root id 00:00:00:00:00:00 priority 32768 ifcost 0 port 0
bridge flags=0<>
member: sa_sys_net flags=143<LEARNING,DISCOVER,AUTOEDGE,AUTOPTP>
port 10 priority 128 path cost 2000 vlan protocol 802.1q
groups: bridge
nd6 options=9<PERFORMNUD,IFDISABLED>
We're almost done; we just need an IP address. In my case, I'm using DHCP, which presents a new problem: the interface won't appear as quickly as it would under normal circumstances, so we need to create a custom rc(8) script to fix this.
/usr/local/etc/rc.d/sys-net:
#!/bin/sh
# PROVIDE: sys_net
# REQUIRE: appjail
. /etc/rc.subr
name="sys_net"
rcvar="${name}_enable"
load_rc_config "${name}"
: ${sys_net_enable:="NO"}
start_cmd="${name}_start"
sys_net_start()
{
{
until ifconfig sa_sys_net > /dev/null 2>&1; do
sleep 1 || exit $?
done
dhclient -d sys_net
} &
}
run_rc_command "$1"
Mark it executable, enable it in your rc.conf(5), and then run the start subcommand using service(8).
$ doas chmod +x /usr/local/etc/rc.d/sys-net
$ doas sysrc sys_net_enable=YES
sys_net_enable: -> YES
$ doas service sys-net start
DHCPREQUEST on sys_net to 255.255.255.255 port 67
DHCPACK from 192.168.0.1
bound to 192.168.0.172 -- renewal in 43200 seconds.
$ ifconfig sys_net
sys_net: flags=1008843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST,LOWER_UP> metric 0 mtu 1500
options=10<VLAN_HWTAGGING>
ether 58:9c:fc:10:6a:01
inet 192.168.0.172 netmask 0xffffff00 broadcast 192.168.0.255
id 00:00:00:00:00:00 priority 32768 hellotime 2 fwddelay 15
maxage 20 holdcnt 6 proto rstp maxaddr 2000 timeout 1200
root id 00:00:00:00:00:00 priority 32768 ifcost 0 port 0
bridge flags=0<>
member: sa_sys_net flags=143<LEARNING,DISCOVER,AUTOEDGE,AUTOPTP>
port 10 priority 128 path cost 2000 vlan protocol 802.1q
groups: bridge
nd6 options=9<PERFORMNUD,IFDISABLED>
$ ping -c4 1.1.1.1
PING 1.1.1.1 (1.1.1.1): 56 data bytes
64 bytes from 1.1.1.1: icmp_seq=0 ttl=51 time=36.874 ms
64 bytes from 1.1.1.1: icmp_seq=1 ttl=51 time=36.151 ms
64 bytes from 1.1.1.1: icmp_seq=2 ttl=51 time=36.936 ms
64 bytes from 1.1.1.1: icmp_seq=3 ttl=51 time=36.227 ms
--- 1.1.1.1 ping statistics ---
4 packets transmitted, 4 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 36.151/36.547/36.936/0.360 ms
$ host example.org
example.org has address 104.20.26.136
example.org has address 172.66.157.237
example.org mail is handled by 0 .
Everything seems to be fine. Do you remember the netisr-related tunables I included in the sys-net's loader.conf(5)? We should include the same on the host:
/boot/loader.conf:
net.isr.maxthreads="-1"
net.isr.bindthreads="1"
net.isr.dispatch="hybrid"
net.isr.defaultqlimit="2048"
net.isr.maxqlimit="10240"
Another change we need to make on the server involves the pf.conf(5) and appjail.conf(5) files. Since they use em0, we need to configure them to use sys_net instead.
$ grep ^ext_if /etc/pf.conf
ext_if = "sys_net"
$ grep ^EXT_IF /usr/local/etc/appjail/appjail.conf
EXT_IF=sys_net
Note: Jails created before sys-net will continue to use the old interface, em0, so they must be recreated. This applies only when using Virtual Networks and NAT.
We're done with sys-net. Now it's time for sys-sound.
xlayer/sys-sound/app.yml:
kind: vmJail
datacenters:
main:
entrypoint: !ENV '${ENTRYPOINT}'
access_token: !ENV '${TOKEN}'
deployIn:
labels:
- desktop
vmName: sys-sound
makejail: '/home/user/.reproduce/projects/sys-jail/runtime/Makejail'
build-arguments:
- is_public_vm: '1'
options:
- virtualnet: 'ajnet:<random> address:10.0.0.63 default'
- label: 'security-group:1'
- label: 'security-group.rules.allow-pkg:pass on ajnet proto tcp from %i to 100.65.139.52/32 port 4080'
overwrite: true
template:
loader: 'bhyveload'
cpu: '4'
memory: '768'
network0_type: 'virtio-net'
network0_switch: 'public'
wired_memory: 'YES'
virt_random: 'YES'
passthru0: '0/31/3=2:0'
diskLayout:
driver: 'nvme'
size: '10G'
from:
type: appjailImage
entrypoint: 'none'
imageName: sys-sound
imageArch: amd64
imageTag: latest
disk:
scheme: 'gpt'
partitions:
- type: 'freebsd-boot'
size: '512k'
alignment: '1m'
- type: 'freebsd-swap'
size: '1G'
alignment: '1m'
- type: 'freebsd-ufs'
alignment: '1m'
format:
flags: '-Uj'
bootcode:
bootcode: '/boot/pmbr'
partcode: '/boot/gptboot'
index: 1
fstab:
- device: '/dev/nda0p3'
mountpoint: '/'
type: 'ufs'
options: 'rw,sync'
dump: 1
pass: 1
- device: '/dev/nda0p2'
mountpoint: 'none'
type: 'swap'
options: 'sw'
dump: 0
pass: 0
script-environment:
- HOSTNAME: 'sys-sound'
- TIMEZONE: 'America/Caracas'
- SSH_KEY: !ENV '${SSH_KEY}'
script: |
set -xe
set -o pipefail
. "/metadata/environment"
sysrc -f /mnt/etc/rc.conf ifconfig_vtnet0="inet 192.168.8.2/24"
sysrc -f /mnt/etc/rc.conf defaultrouter="192.168.8.1"
sysrc -f /mnt/etc/rc.conf fsck_y_enable="YES"
sysrc -f /mnt/etc/rc.conf clear_tmp_enable="YES"
sysrc -f /mnt/etc/rc.conf dumpdev="NO"
sysrc -f /mnt/etc/rc.conf moused_nondefault_enable="NO"
sysrc -f /mnt/etc/rc.conf hostname="${HOSTNAME}"
sysrc -f /mnt/etc/rc.conf pf_enable="YES"
sysrc -f /mnt/etc/rc.conf pflog_enable="YES"
sysrc -f /mnt/etc/rc.conf sndiod_enable="YES"
sysrc -f /mnt/etc/rc.conf sndiod_flags="-L 0.0.0.0"
cp -a /etc/resolv.conf /mnt/etc/resolv.conf
printf "%s\n" "${SSH_KEY}" > /mnt/etc/ssh/authorized_keys
cp /metadata/sys-sound.loader.conf /mnt/boot/loader.conf
ln -fs "/usr/share/zoneinfo/${TIMEZONE}" /mnt/etc/localtime
sysrc -f /mnt/etc/rc.conf sshd_enable="YES"
cp /metadata/sys-sound.sshd_config /mnt/etc/ssh/sshd_config
cp /metadata/sys-sound.sysctl.conf /mnt/etc/sysctl.conf
mkdir -p /mnt/usr/local/etc/pkg/repos
cp /metadata/sys-sound.FreeBSD.conf /mnt/usr/local/etc/pkg/repos/FreeBSD.conf
cp /metadata/sys-sound.FreeBSD-base.conf /mnt/usr/local/etc/pkg/repos/FreeBSD-base.conf
cp /metadata/sys-sound.FreeBSD-Custom.conf /mnt/usr/local/etc/pkg/repos/FreeBSD-Custom.conf
mkdir -p /mnt/etc
cp /metadata/sys-sound.pf.conf /mnt/etc/pf.conf
metadata:
- sys-sound.loader.conf
- sys-sound.sshd_config
- sys-sound.sysctl.conf
- sys-sound.FreeBSD.conf
- sys-sound.FreeBSD-base.conf
- sys-sound.FreeBSD-Custom.conf
- sys-sound.pf.conf
xlayer/sys-sound/metadata.yml:
kind: metadata
datacenters:
main:
entrypoint: !ENV '${ENTRYPOINT}'
access_token: !ENV '${TOKEN}'
deployIn:
labels:
- vm-only
metadataPrefix: sys-sound
metadata:
loader.conf: |
kern.geom.label.disk_ident.enable="0"
kern.geom.label.gptid.enable="0"
cryptodev_load="YES"
nvme_load="YES"
if_bridge_load="YES"
bridgestp_load="YES"
kern.racct.enable=1
sysctlinfo_load="YES"
sysctlbyname_improved_load="YES"
sshd_config: |
# Ports
Port 22
# Authentication
PubkeyAuthentication yes
AuthenticationMethods publickey
PermitRootLogin prohibit-password
PrintMotd no
# Forwarding
X11Forwarding no
AllowAgentForwarding yes
# Connection checks
ClientAliveCountMax 3
ClientAliveInterval 15
# Limits
LoginGraceTime 40
# Public keys
AuthorizedKeysFile /etc/ssh/authorized_keys
# SFTP
Subsystem sftp internal-sftp
sysctl.conf: |
# A bit of hardening
security.bsd.see_other_uids=0
security.bsd.see_other_gids=0
security.bsd.see_jail_proc=0
kern.randompid=1
# Allow packet filtering in if_bridge(4)
net.link.bridge.pfil_member=1
net.link.bridge.pfil_bridge=1
FreeBSD.conf: |
FreeBSD-ports: {
url: "pkg+https://pkg.FreeBSD.org/${ABI}/latest",
mirror_type: "srv",
signature_type: "fingerprints",
fingerprints: "/usr/share/keys/pkg",
enabled: yes
}
FreeBSD-ports-kmods: {
url: "pkg+https://pkg.FreeBSD.org/${ABI}/kmods_latest",
mirror_type: "srv",
signature_type: "fingerprints",
fingerprints: "/usr/share/keys/pkg",
enabled: yes
}
FreeBSD-base.conf: |
FreeBSD-base {
enabled: yes
}
FreeBSD-Custom.conf: |
Custom: {
url: "http://pkg.dyn.dc-air.home.arpa:4080/150amd64-local",
mirror_type: "http",
priority: 1,
enabled: yes
}
pf.conf: |
ext_if = "vtnet0"
set skip on lo
set block-policy return
set fail-policy return
set loginterface pflog0
scrub in
antispoof for lo0
block in
pass out
pass in proto { icmp, icmp6 }
pass in on $ext_if proto tcp to ($ext_if:0) port { ssh, 11025 }
There’s nothing particularly special here, but there are a few key points worth noting: with is_public_vm: 1, all connections made to the jail will be redirected to the VM, and there are two new Makejails. The first is the Makejail used to create the jail (sys-jail/runtime/Makejail), and the second is used to create the sys-sound image.
~/.reproduce/projects/sys-jail/runtime/Makejail:
ARG is_public_vm=0
ARG exclude_ports?
ARG pf_stricter=1
FROM --entrypoint none sys-jail
OPTION start
OPTION overwrite=force
OPTION virtualnet=:<random> default
OPTION nat
OPTION mount_devfs
OPTION device=include \$devfsrules_hide_all
OPTION device=include \$devfsrules_unhide_basic
OPTION device=include \$devfsrules_unhide_login
OPTION device=path pf unhide
OPTION device=path vmm unhide
OPTION device=path 'vmm/*' unhide
OPTION device=path vmm.io unhide
OPTION device=path 'vmm.io/*' unhide
OPTION device=path vmmctl unhide
OPTION device=path 'nmdm*' unhide
OPTION device=path 'tap*' unhide
OPTION device=path mem unhide
OPTION device=path kmem unhide
OPTION device=path pci unhide
OPTION device=path io unhide
OPTION template=template.conf
OPTION volume=vm-data mountpoint:/vm
OPTION type=thick
RAW default_interface=`appjail cmd jexec "${APPJAIL_JAILNAME}" route -n4 get default 2> /dev/null | grep 'interface:' | cut -d' ' -f4-`
RAW if [ -z "${default_interface}" ]; then
RAW echo "No default interface found. Cannot continue ..."
RAW exit 1
RAW fi
VAR --make-arg-env DEFAULT_INTERFACE=${default_interface}
VAR --make-arg-env JAIL_NAME=${APPJAIL_JAILNAME}
RAW if [ -c "/dev/vmm/${APPJAIL_JAILNAME}" ]; then
CMD --local bhyvectl "--vm=${APPJAIL_JAILNAME}" --destroy
RAW fi
SYSRC vm_enable=YES
SYSRC vm_dir=/vm
RAW if appjail cmd local "${APPJAIL_JAILNAME}" [ ! -f vm/.done ]; then
CMD mkdir -p /vm && \
find /vm -depth 1 -not -name '.img' -and -not -name '.iso' -exec rm -rf {} + && \
vm init && \
cp /usr/local/share/examples/vm-bhyve/*.conf /vm/.templates && \
sysrc -f /vm/.config/system.conf console=tmux
RAW fi
CMD echo "nat on ${DEFAULT_INTERFACE} from {192.168.8.0/24} to any -> (${DEFAULT_INTERFACE})" > /etc/pf.conf
RAW if [ -n "${exclude_ports}" ]; then
CMD echo "no rdr on ${DEFAULT_INTERFACE} inet proto { tcp, udp } from any to (${DEFAULT_INTERFACE}:0) port ${exclude_ports}" >> /etc/pf.conf
RAW fi
RAW if [ "${is_public_vm}" != 0 ]; then
CMD echo "rdr pass on ${DEFAULT_INTERFACE} inet proto { tcp, udp } from any to (${DEFAULT_INTERFACE}:0) port 1:65535 -> 192.168.8.2 port 1:65535" >> /etc/pf.conf
RAW fi
RAW if [ "${pf_stricter}" != 0 ]; then
CMD echo "block in on vmbridge from ! 192.168.8.0/24" >> /etc/pf.conf
CMD echo "block in on vmbridge from 192.168.8.0/24 to (vmbridge:0)" >> /etc/pf.conf
RAW fi
CMD echo net.inet.ip.forwarding=1 >> /etc/sysctl.conf
CMD sysctl net.inet.ip.forwarding=1
SERVICE pf oneenable
SERVICE pf start
SYSRC "cloned_interfaces+=bridge0"
SYSRC ifconfig_bridge0_name=vmbridge
SYSRC "ifconfig_vmbridge=inet 192.168.8.1/24"
CMD service netif cloneup bridge0 && \
service netif start bridge0 && \
service netif start vmbridge
RAW if appjail cmd local "${APPJAIL_JAILNAME}" [ ! -f vm/.done ]; then
CMD vm switch create -t manual -b vmbridge public
RAW fi
SYSRC syslogd_flags=-ss
SERVICE syslogd restart
STAGE create
RAW if [ -c "/dev/vmm/${APPJAIL_JAILNAME}" ]; then
CMD --local bhyvectl "--vm=${APPJAIL_JAILNAME}" --destroy
RAW fi
STAGE stop
CMD rm -f /vm/*/run.lock
~/.reproduce/projects/sys-jail/runtime/template.conf:
exec.start: "/bin/sh /etc/rc"
exec.stop: "/bin/sh /etc/rc.shutdown jail"
mount.devfs
persist
allow.vmm
allow.vmm_ppt
allow.chflags
stop.timeout: 30
exec.clean
~/.reproduce/projects/sys-sound/Makejail:
OPTION start
OPTION overwrite=force
OPTION alias
OPTION ip4_inherit
OPTION type=thick
CMD mkdir -p /usr/local/etc/pkg/repos
COPY PKG.conf /usr/local/etc/pkg/repos/PKG.conf
PKG FreeBSD-set-base FreeBSD-kernel-generic sndio mixertui
CMD rm -f /usr/local/etc/pkg/repos/PKG.conf
~/.reproduce/projects/sys-sound/PKG.conf:
FreeBSD-ports: {
url: "pkg+https://pkg.FreeBSD.org/${ABI}/latest",
mirror_type: "srv",
signature_type: "fingerprints",
fingerprints: "/usr/share/keys/pkg",
enabled: yes
}
FreeBSD-ports-kmods: {
enabled: yes
}
FreeBSD-base: {
enabled: yes
}
Custom: {
url: "http://pkg.dyn.dc-air.home.arpa:4080/150amd64-local",
mirror_type: "http",
priority: 1,
enabled: yes
}
~/.reproduce/projects/sys-sound/reproduce.conf:
tags: latest/15
arch: amd64
Let's deploy sys-sound.
$ overlord apply -f xlayer/sys-sound/metadata.yml
$ overlord apply -f xlayer/sys-sound/app.yml
$ overlord get-info -f xlayer/sys-sound/app.yml -t projects --filter-per-project
datacenter: http://127.0.0.1:8888
entrypoint: main
chain: None
labels:
- all
- desktop
- services
- vm-only
- dc-air
projects:
sys-sound:
state: UNFINISHED
last_log: 2026-04-29_01h08m46s
locked: True
services:
- {'name': 'vm', 'status': 1, 'jail': 'sys-sound'}
up:
operation: RUNNING
last_update: 27.23 seconds
job_id: 7
$ overlord get-info -f xlayer/sys-sound/app.yml -t projects --filter-per-project
datacenter: http://127.0.0.1:8888
entrypoint: main
chain: None
labels:
- all
- desktop
- services
- vm-only
- dc-air
projects:
sys-sound:
state: DONE
last_log: 2026-04-29_01h08m46s
locked: False
services:
- {'name': 'vm', 'status': 0, 'jail': 'sys-sound'}
up:
operation: COMPLETED
output:
rc: 0
stdout: {'errlevel': 0, 'message': None, 'failed': []}
last_update: 22.54 seconds
job_id: 7
restarted: False
$ overlord get-info -f xlayer/sys-sound.yml -t vm --filter-per-project
datacenter: http://127.0.0.1:8888
entrypoint: main
chain: None
labels:
- all
- desktop
- services
- vm-only
- dc-air
projects:
sys-sound:
virtual-machines:
operation: COMPLETED
output: |
md0 created
md0p1 added
md0p2 added
md0p3 added
/dev/md0p3: 9214.0MB (18870272 sectors) block size 32768, fragment size 4096
using 15 cylinder groups of 625.22MB, 20007 blks, 80128 inodes.
with soft updates
super-block backups (for fsck_ffs -b #) at:
192, 1280640, 2561088, 3841536, 5121984, 6402432, 7682880, 8963328, 10243776,
11524224, 12804672, 14085120, 15365568, 16646016, 17926464
newfs: soft updates journaling set
Using inode 4 in cg 0 for 75497472 byte journal
bootcode written to md0
partcode written to md0p1
+ set -o pipefail
+ . /metadata/environment
+ export 'HOSTNAME=sys-sound'
+ export 'TIMEZONE=America/Caracas'
+ export 'SSH_KEY=[REDACTED]'
+ sysrc -f /mnt/etc/rc.conf 'ifconfig_vtnet0=inet 192.168.8.2/24'
ifconfig_vtnet0: -> inet 192.168.8.2/24
+ sysrc -f /mnt/etc/rc.conf 'defaultrouter=192.168.8.1'
defaultrouter: NO -> 192.168.8.1
+ sysrc -f /mnt/etc/rc.conf 'fsck_y_enable=YES'
fsck_y_enable: NO -> YES
+ sysrc -f /mnt/etc/rc.conf 'clear_tmp_enable=YES'
clear_tmp_enable: NO -> YES
+ sysrc -f /mnt/etc/rc.conf 'dumpdev=NO'
dumpdev: NO -> NO
+ sysrc -f /mnt/etc/rc.conf 'moused_nondefault_enable=NO'
moused_nondefault_enable: YES -> NO
+ sysrc -f /mnt/etc/rc.conf 'hostname=sys-sound'
hostname: -> sys-sound
+ sysrc -f /mnt/etc/rc.conf 'pf_enable=YES'
pf_enable: NO -> YES
+ sysrc -f /mnt/etc/rc.conf 'pflog_enable=YES'
pflog_enable: NO -> YES
+ sysrc -f /mnt/etc/rc.conf 'sndiod_enable=YES'
sndiod_enable: -> YES
+ sysrc -f /mnt/etc/rc.conf 'sndiod_flags=-L 0.0.0.0'
sndiod_flags: -> -L 0.0.0.0
+ cp -a /etc/resolv.conf /mnt/etc/resolv.conf
+ printf '%s\n' '[REDACTED]'
+ cp /metadata/sys-sound.loader.conf /mnt/boot/loader.conf
+ ln -fs /usr/share/zoneinfo/America/Caracas /mnt/etc/localtime
+ sysrc -f /mnt/etc/rc.conf 'sshd_enable=YES'
sshd_enable: NO -> YES
+ cp /metadata/sys-sound.sshd_config /mnt/etc/ssh/sshd_config
+ cp /metadata/sys-sound.sysctl.conf /mnt/etc/sysctl.conf
+ mkdir -p /mnt/usr/local/etc/pkg/repos
+ cp /metadata/sys-sound.FreeBSD.conf /mnt/usr/local/etc/pkg/repos/FreeBSD.conf
+ cp /metadata/sys-sound.FreeBSD-base.conf /mnt/usr/local/etc/pkg/repos/FreeBSD-base.conf
+ cp /metadata/sys-sound.FreeBSD-Custom.conf /mnt/usr/local/etc/pkg/repos/FreeBSD-Custom.conf
+ mkdir -p /mnt/etc
+ cp /metadata/sys-sound.pf.conf /mnt/etc/pf.conf
vm_list: -> sys-sound
Starting sys-sound
* found guest in /vm/sys-sound
* booting...
last_update: 15.36 seconds
job_id: 7
$ appjail cmd jexec sys-sound vm list sys-sound
NAME DATASTORE LOADER CPU MEMORY VNC AUTO STATE TAGS
sys-sound default bhyveload 4 768 - Yes [1] Running (33618) -
$ cat ~/.ssh/config
...
Host sys-sound
User root
StrictHostKeyChecking=no
UserKnownHostsFile=/dev/null
$ ssh sys-sound
Warning: Permanently added 'sys-sound' (ED25519) to the list of known hosts.
root@sys-sound:~ # ^D
Shared connection to sys-sound closed.
It's time to try sys-x11. However, unlike the other VMs, it requires persistent storage. Both sys-net and sys-sound lose all their data when they are destroyed, which isn't a problem in this case. The same does not apply to sys-x11. Fortunately, there is a solution: use net/nbdkit and filesystems/nbd-client-kmod to essentially implement The Ephemeral Concept at the VM level 8.
nbdkit/metadata.yml:
kind: metadata
datacenters:
main:
entrypoint: !ENV '${ENTRYPOINT}'
access_token: !ENV '${TOKEN}'
deployIn:
labels:
- vm-only
metadataPrefix: nbdkit
metadata:
appConfig: |
<%page args="nbdkit_log='/dev/null',nbdkit_serial='0',nbdkit_size='10G',nbdkit_fadvise='sequential'"/>
kind: directorProject
projectName: ${appName}
projectFile: |
services:
nbd-server:
<%text filter="n">makejail: !ENV '${OVERLORD_METADATA}/nbdkit.makejail</%text>'
serial: ${nbdkit_serial}
name: '${appName}'
arguments:
- nbdkit_volume: ${appName}
- nbdkit_address: ${nbdkit_address}
- nbdkit_size: ${nbdkit_size}
- nbdkit_fadvise: ${nbdkit_fadvise}
start-environment:
- NBDKIT_LOG: ${nbdkit_log}
options:
- label: 'security-group:1'
- label: 'security-group.rules.allow-pkg-custom:pass on ajnet proto tcp from %i to 100.65.139.52 port 4080'
volumes:
- data: nbdkit-data
default_volume_type: '<volumefs>'
volumes:
data:
device: /var/appjail-nbd/${appName}
makejail: |
ARG nbdkit_volume
ARG nbdkit_address
ARG nbdkit_size=10G
ARG nbdkit_fadvise=sequential
OPTION start
OPTION overwrite=force
OPTION volume=nbdkit-data mountpoint:/data owner:0 group:0
OPTION virtualnet=:<random> address:${nbdkit_address} default
OPTION nat
INCLUDE gh+DtxdF/efficient-makejail
CMD mkdir -p /usr/local/etc/pkg/repos
COPY ${OVERLORD_METADATA}/nbdkit.pkg.conf /usr/local/etc/pkg/repos/Latest.conf
PKG nbdkit
RAW if appjail cmd jexec "${APPJAIL_JAILNAME}" [ ! -f "/data/${nbdkit_volume}.img" ]; then
CMD truncate -s "${nbdkit_size}" "/data/${nbdkit_volume}.img"
CMD chown nobody:nobody "/data/${nbdkit_volume}.img"
CMD chmod 600 "/data/${nbdkit_volume}.img"
RAW fi
COPY ${OVERLORD_METADATA}/nbdkit.sh /nbdkit.sh
CMD chmod +x /nbdkit.sh
CMD mkdir -p /data/.config
CMD echo "${nbdkit_volume}" > /data/.config/volume
CMD echo "${nbdkit_fadvise}" > /data/.config/fadvise
STOP
STAGE start
RUN /nbdkit.sh
sh: |
#!/bin/sh
set -xe
set -o pipefail
nbdkit_volume=`head -1 -- /data/.config/volume`
nbdkit_fadvise=`head -1 -- /data/.config/fadvise`
daemon \
-f \
-t "nbdkit (volume:${nbdkit_volume})" \
-u nobody \
-p /var/run/nbdkit.pid \
-o "${NBDKIT_LOG:-/var/log/nbdkit.log}" \
nbdkit -t $(nproc) -f file "file=/data/${nbdkit_volume}.img" "fadvise=${nbdkit_fadvise}"
pkg.conf: |
FreeBSD-ports: {
url: "pkg+https://pkg.FreeBSD.org/${ABI}/latest",
mirror_type: "srv",
signature_type: "fingerprints",
fingerprints: "/usr/share/keys/pkg",
enabled: yes
}
FreeBSD-kmods: {
enabled: no
}
nbdkit/sys-storage.yml:
kind: appConfig
datacenters:
main:
entrypoint: !ENV '${ENTRYPOINT}'
access_token: !ENV '${TOKEN}'
deployIn:
labels:
- desktop
appName: sys-storage
appFrom: 'nbdkit.appConfig'
appConfig:
nbdkit_address: 10.0.0.62
nbdkit_size: '30G'
Unlike other deployment files, those in nbdkit are Overlord templates (or, technically speaking, Mako templates). The reason for this is to reuse code, so that creating new "storage profiles” is as simple as writing a file of just a few lines. This will become more relevant when we look at the deployment files for sys-x11.
After deploying nbdkit, or sys-storage, we need to partition and format the disk. As you can see in the deployment files above, I’m going to use ZFS. You can use UFS, and the Overlord wiki 9 contains many examples of its use, but in this article I’ll assume that ZFS will be used. You need to create three additional datasets (actually four, but the intention is to create three): the first for /var/tmp and the second for /var/db/containers. Both will be used primarily by sysutils/buildah. The last dataset will be used by the noroot user as their home directory.
$ overlord apply -f nbdkit/metadata.yml
$ overlord apply -f nbdkit/sys-storage.yml
$ doas gnbd connect sys-storage
nbd0
$ doas gpart create -s gpt nbd0
nbd0 created
$ doas gpart add -t freebsd-zfs -a 1m nbd0
nbd0p1 added
$ doas zpool create -f -O compress=lz4 -o altroot=/mnt -t zdata zdata /dev/nbd0p1
$ zpool list zdata
NAME SIZE ALLOC FREE CKPOINT EXPANDSZ FRAG CAP DEDUP HEALTH ALTROOT
zdata 9.50G 528K 9.50G - - 0% 0% 1.00x ONLINE /mnt
$ doas zfs create -o mountpoint=/var/db/containers zdata/containers
$ doas zfs create -o mountpoint=/var -o canmount=off zdata/var
$ doas zfs create -o setuid=off zdata/var/tmp
$ doas chmod 1777 /mnt/var/tmp
$ doas zfs create zdata/noroot
$ doas chown -f 15000:15000 /mnt/zdata/noroot
$ doas zpool export zdata
$ doas gnbd disconnect nbd0
Done. Now it's sys-x11s turn.
xlayer/sys-x11/metadata.yml:
kind: metadata
datacenters:
main:
entrypoint: !ENV '${ENTRYPOINT}'
access_token: !ENV '${TOKEN}'
deployIn:
labels:
- vm-only
metadataPrefix: sys-x11
metadata:
loader.conf: |
kern.geom.label.disk_ident.enable="0"
kern.geom.label.gptid.enable="0"
cryptodev_load="YES"
nvme_load="YES"
if_bridge_load="YES"
bridgestp_load="YES"
kern.racct.enable=1
zfs_load="YES"
sshd_config: |
# Ports
Port 22
# Authentication
PubkeyAuthentication yes
AuthenticationMethods publickey
PermitRootLogin prohibit-password
PrintMotd no
# Forwarding
X11Forwarding no
AllowAgentForwarding yes
# Connection checks
ClientAliveCountMax 3
ClientAliveInterval 15
# Limits
LoginGraceTime 40
# Public keys
AuthorizedKeysFile /etc/ssh/authorized_keys
# SFTP
Subsystem sftp internal-sftp
mountdata.rc: |
#!/bin/sh
# PROVIDE: mountdata
# REQUIRE: zpool zfs gnbd
. /etc/rc.subr
name="mountdata"
rcvar="${name}_enable"
start_cmd="mountdata_start"
load_rc_config $name
: ${mountdata_enable:="NO"}
: ${mountdata_pool:="zdata"}
mountdata_start()
{
zpool import -f -o cachefile=none "${mountdata_pool}" || return $?
if [ -f "${firstboot_sentinel}" ]; then
zpool upgrade "${mountdata_pool}" || return $?
fi
}
run_rc_command "$1"
sysctl.conf: |
# A bit of hardening
security.bsd.see_other_uids=0
security.bsd.see_other_gids=0
security.bsd.see_jail_proc=0
kern.randompid=1
# Allow packet filtering in if_bridge(4)
net.link.bridge.pfil_member=1
net.link.bridge.pfil_bridge=1
FreeBSD.conf: |
FreeBSD-ports: {
url: "pkg+https://pkg.FreeBSD.org/${ABI}/latest",
mirror_type: "srv",
signature_type: "fingerprints",
fingerprints: "/usr/share/keys/pkg",
enabled: yes
}
FreeBSD-ports-kmods: {
url: "pkg+https://pkg.FreeBSD.org/${ABI}/kmods_latest",
mirror_type: "srv",
signature_type: "fingerprints",
fingerprints: "/usr/share/keys/pkg",
enabled: yes
}
FreeBSD-base.conf: |
FreeBSD-base {
enabled: yes
}
FreeBSD-Custom.conf: |
Custom: {
url: "http://pkg.dyn.dc-air.home.arpa:4080/150amd64-local",
mirror_type: "http",
priority: 1,
enabled: yes
}
appjail.conf: |
EXT_IF=vtnet0
SHORTEN_DOMAIN_NAMES=1
AUTO_NETWORK_ADDR="192.168.16.0/24"
ENABLE_ZFS=1
ZPOOL=zdata
ZROOTFS=appjail
HOOKSDIR=/usr/local/etc/appjail/hooks
TAR_XZ_ARGS="--xz --options xz:threads=0"
TAR_ZSTD_ARGS="--zstd --options zstd:threads=0"
pf.conf: |
ext_if = "vtnet0"
appjail_bridge = "ajnet"
appjail_network = "192.168.16.0/24"
table <allow-dns> persist
table <rfc6890> const { 0.0.0.0/8 10.0.0.0/8 100.64.0.0/10 127.0.0.0/8 169.254.0.0/16 \
172.16.0.0/12 192.0.0.0/24 192.0.0.0/29 192.0.2.0/24 192.88.99.0/24 \
192.168.0.0/16 198.18.0.0/15 198.51.100.0/24 203.0.113.0/24 \
240.0.0.0/4 255.255.255.255/32 }
set skip on lo
set block-policy return
set fail-policy return
set loginterface pflog0
scrub in
nat-anchor "appjail-nat/jail/*"
nat-anchor "appjail-nat/network/*"
rdr-anchor "appjail-rdr/*"
antispoof for lo0
block in
pass out
pass in proto { icmp, icmp6 }
block in on $appjail_bridge from <rfc6890>
pass in on $appjail_bridge from $appjail_network to ! <rfc6890>
pass on ajnet proto { tcp, udp } from <allow-dns> to 172.16.0.1/32 port 53
anchor "appjail-filter/jail/*"
pass in on $ext_if proto tcp to ($ext_if:0) port ssh
doas.conf: |
permit nopass root
permit nopass keepenv noroot
appConfig: |
<%page args="timezone='UTC',ncpu='2',memory='1G',swap='1G',disk='10G',sound='10.0.0.63'"/>
kind: vmJail
vmName: '${appName}'
makejail: '/home/user/.reproduce/projects/sys-jail/runtime/Makejail'
build-arguments:
- is_public_vm: '1'
options:
- label: 'security-group:1'
- label: 'security-group.rules.allow-sys-storage:pass on appjail_epair proto tcp from %i to ${volume}/32 port 10809'
- label: 'security-group.rules.allow-sys-sound:pass on appjail_epair proto tcp from %i to ${sound}/32 port 11025'
- label: 'security-group.rules.allow-pkg:pass on ajnet proto tcp from %i to 100.65.139.52/32 port 4080'
overwrite: true
template:
loader: 'bhyveload'
cpu: '${ncpu}'
memory: '${memory}'
network0_type: 'virtio-net'
network0_switch: 'public'
virt_random: 'YES'
diskLayout:
driver: 'nvme'
size: '${disk}'
from:
type: appjailImage
entrypoint: 'none'
imageName: sys-x11
imageArch: amd64
imageTag: latest
disk:
scheme: 'gpt'
partitions:
- type: 'freebsd-boot'
size: '512k'
alignment: '1m'
- type: 'freebsd-swap'
size: '${swap}'
alignment: '1m'
- type: 'freebsd-ufs'
alignment: '1m'
format:
flags: '-Uj'
bootcode:
bootcode: '/boot/pmbr'
partcode: '/boot/gptboot'
index: 1
fstab:
- device: '/dev/nda0p3'
mountpoint: '/'
type: 'ufs'
options: 'rw,sync'
dump: 1
pass: 1
- device: '/dev/nda0p2'
mountpoint: 'none'
type: 'swap'
options: 'sw'
dump: 0
pass: 0
script-environment:
- HOSTNAME: '${hostname}'
- TIMEZONE: '${timezone}'
- SSH_KEY: ${ssh_key}'
script: |
<%text filter="n">
set -xe
set -o pipefail
. "/metadata/environment"
sysrc -f /mnt/etc/rc.conf ifconfig_vtnet0="inet 192.168.8.2/24"
sysrc -f /mnt/etc/rc.conf defaultrouter="192.168.8.1"
sysrc -f /mnt/etc/rc.conf fsck_y_enable="YES"
sysrc -f /mnt/etc/rc.conf clear_tmp_enable="YES"
sysrc -f /mnt/etc/rc.conf dumpdev="NO"
sysrc -f /mnt/etc/rc.conf moused_nondefault_enable="NO"
sysrc -f /mnt/etc/rc.conf hostname="${HOSTNAME}"
sysrc -f /mnt/etc/rc.conf zfs_enable="YES"
sysrc -f /mnt/etc/rc.conf dbus_enable="YES"
sysrc -f /mnt/etc/rc.conf pf_enable="YES"
sysrc -f /mnt/etc/rc.conf pflog_enable="YES"
sysrc -f /mnt/etc/rc.conf gateway_enable="YES"
cp -a /etc/resolv.conf /mnt/etc/resolv.conf
printf "%s\n" "${SSH_KEY}" > /mnt/etc/ssh/authorized_keys
cp /metadata/sys-x11.loader.conf /mnt/boot/loader.conf
ln -fs "/usr/share/zoneinfo/${TIMEZONE}" /mnt/etc/localtime
sysrc -f /mnt/etc/rc.conf sshd_enable="YES"
cp /metadata/sys-x11.sshd_config /mnt/etc/ssh/sshd_config
cp /metadata/sys-x11.sysctl.conf /mnt/etc/sysctl.conf
mkdir -p /mnt/usr/local/etc/pkg/repos
cp /metadata/sys-x11.FreeBSD.conf /mnt/usr/local/etc/pkg/repos/FreeBSD.conf
cp /metadata/sys-x11.FreeBSD-base.conf /mnt/usr/local/etc/pkg/repos/FreeBSD-base.conf
cp /metadata/sys-x11.FreeBSD-Custom.conf /mnt/usr/local/etc/pkg/repos/FreeBSD-Custom.conf
mkdir -p /mnt/usr/local/etc/appjail
cp /metadata/sys-x11.appjail.conf /mnt/usr/local/etc/appjail/appjail.conf
mkdir -p /mnt/usr/local/etc/appjail/hooks/pre.d
cp -a /mnt/usr/local/share/examples/appjail/hooks/pre.d/security-*.sh \
/mnt/usr/local/etc/appjail/hooks/pre.d
mkdir -p /mnt/etc
cp /metadata/sys-x11.pf.conf /mnt/etc/pf.conf
sysrc -f /mnt/etc/rc.conf gnbd_enable="YES"
sysrc -f /mnt/etc/rc.conf gnbd_devices="nbd0"
sysrc -f /mnt/etc/rc.conf gnbd_nbd0_host="10.0.0.62"
sysrc -f /mnt/etc/rc.conf gnbd_nbd0_conns="16"
mkdir -p /mnt/usr/local/etc/rc.d
cp /metadata/sys-x11.mountdata.rc /mnt/usr/local/etc/rc.d/mountdata
chmod +x /mnt/usr/local/etc/rc.d/mountdata
sysrc -f /mnt/etc/rc.conf mountdata_enable="YES"
mkdir -p /mnt/usr/local/etc
cp /metadata/sys-x11.doas.conf /mnt/usr/local/etc/doas.conf
chmod 600 /mnt/usr/local/etc/doas.conf
pw -R /mnt useradd -n noroot -u 15000 -d /zdata/noroot
</%text>
metadata:
- sys-x11.loader.conf
- sys-x11.sshd_config
- sys-x11.sysctl.conf
- sys-x11.FreeBSD.conf
- sys-x11.FreeBSD-base.conf
- sys-x11.FreeBSD-Custom.conf
- sys-x11.appjail.conf
- sys-x11.pf.conf
- sys-x11.mountdata.rc
- sys-x11.doas.conf
~/.reproduce/projects/sys-x11/Makejail:
OPTION start
OPTION overwrite=force
OPTION alias
OPTION ip4_inherit
OPTION type=thick
CMD mkdir -p /usr/local/etc/pkg/repos
COPY PKG.conf /usr/local/etc/pkg/repos/PKG.conf
PKG FreeBSD-set-base \
FreeBSD-kernel-generic \
appjail-devel \
git-tiny \
nbd-client-kmod \
xpra \
dbus \
pet \
neovim \
bin \
fzf \
xfce4-terminal \
tmux \
py311-dex \
s6 \
xorg-fonts \
xclipsync \
unixexec \
zenity4 \
libnotify
CMD rm -f /usr/local/etc/pkg/repos/PKG.conf
~/.reproduce/projects/sys-x11/PKG.conf:
FreeBSD-ports: {
url: "pkg+https://pkg.FreeBSD.org/${ABI}/latest",
mirror_type: "srv",
signature_type: "fingerprints",
fingerprints: "/usr/share/keys/pkg",
enabled: yes
}
FreeBSD-ports-kmods: {
enabled: yes
}
FreeBSD-base: {
enabled: yes
}
Custom: {
url: "http://pkg.dyn.dc-air.home.arpa:4080/150amd64-local",
mirror_type: "http",
priority: 1,
enabled: yes
}
~/.reproduce/projects/sys-x11/reproduce.conf:
tags: latest/15
arch: amd64
xlayer/sys-x11/sys-x11.yml:
kind: appConfig
datacenters:
main:
entrypoint: !ENV '${ENTRYPOINT}'
access_token: !ENV '${TOKEN}'
deployIn:
labels:
- desktop
appName: sys-x11
appFrom: sys-x11.appConfig
appConfig:
ncpu: '4'
memory: '4G'
hostname: 'sys-x11'
timezone: 'America/Caracas'
ssh_key: !ENV '${SSH_KEY}'
volume: '10.0.0.62'
As with nbdkit, Overlord templates for sys-x11 are used here. You can easily create multiple VMs for different purposes, just as QubeOS domains are used for different purposes and levels of trust.
$ overlord apply -f xlayer/sys-x11/metadata.yml
$ overlord apply -f xlayer/sys-x11/sys-x11.yml
$ overlord get-info -f xlayer/sys-x11/sys-x11.yml -t projects --filter-per-project
datacenter: http://127.0.0.1:8888
entrypoint: main
chain: None
labels:
- all
- desktop
- services
- vm-only
- dc-air
projects:
sys-x11:
state: UNFINISHED
last_log: 2026-04-29_12h20m33s
locked: True
services:
- {'name': 'vm', 'status': 0, 'jail': 'sys-x11'}
up:
operation: RUNNING
last_update: 10.76 seconds
job_id: 9
$ overlord get-info -f xlayer/sys-x11/sys-x11.yml -t projects --filter-per-project
datacenter: http://127.0.0.1:8888
entrypoint: main
chain: None
labels:
- all
- desktop
- services
- vm-only
- dc-air
projects:
sys-x11:
state: DONE
last_log: 2026-04-29_12h20m33s
locked: False
services:
- {'name': 'vm', 'status': 0, 'jail': 'sys-x11'}
up:
operation: COMPLETED
output:
rc: 0
stdout: {'errlevel': 0, 'message': None, 'failed': []}
last_update: 2 minutes and 19.78 seconds
job_id: 9
restarted: False
$ overlord get-info -f xlayer/sys-x11/sys-x11.yml -t vm --filter-per-project
datacenter: http://127.0.0.1:8888
entrypoint: main
chain: None
labels:
- all
- desktop
- services
- vm-only
- dc-air
projects:
sys-x11:
virtual-machines:
operation: COMPLETED
output: |
md0 created
md0p1 added
md0p2 added
md0p3 added
/dev/md0p3: 9214.0MB (18870272 sectors) block size 32768, fragment size 4096
using 15 cylinder groups of 625.22MB, 20007 blks, 80128 inodes.
with soft updates
super-block backups (for fsck_ffs -b #) at:
192, 1280640, 2561088, 3841536, 5121984, 6402432, 7682880, 8963328, 10243776,
11524224, 12804672, 14085120, 15365568, 16646016, 17926464
newfs: soft updates journaling set
Using inode 4 in cg 0 for 75497472 byte journal
bootcode written to md0
partcode written to md0p1
+ set -o pipefail
+ . /metadata/environment
+ export 'HOSTNAME=sys-x11'
+ export 'TIMEZONE=America/Caracas'
+ export $'SSH_KEY=[REDACTED]\''
+ sysrc -f /mnt/etc/rc.conf 'ifconfig_vtnet0=inet 192.168.8.2/24'
ifconfig_vtnet0: -> inet 192.168.8.2/24
+ sysrc -f /mnt/etc/rc.conf 'defaultrouter=192.168.8.1'
defaultrouter: NO -> 192.168.8.1
+ sysrc -f /mnt/etc/rc.conf 'fsck_y_enable=YES'
fsck_y_enable: NO -> YES
+ sysrc -f /mnt/etc/rc.conf 'clear_tmp_enable=YES'
clear_tmp_enable: NO -> YES
+ sysrc -f /mnt/etc/rc.conf 'dumpdev=NO'
dumpdev: NO -> NO
+ sysrc -f /mnt/etc/rc.conf 'moused_nondefault_enable=NO'
moused_nondefault_enable: YES -> NO
+ sysrc -f /mnt/etc/rc.conf 'hostname=sys-x11'
hostname: -> sys-x11
+ sysrc -f /mnt/etc/rc.conf 'zfs_enable=YES'
zfs_enable: NO -> YES
+ sysrc -f /mnt/etc/rc.conf 'dbus_enable=YES'
dbus_enable: -> YES
+ sysrc -f /mnt/etc/rc.conf 'pf_enable=YES'
pf_enable: NO -> YES
+ sysrc -f /mnt/etc/rc.conf 'pflog_enable=YES'
pflog_enable: NO -> YES
+ sysrc -f /mnt/etc/rc.conf 'gateway_enable=YES'
gateway_enable: NO -> YES
+ cp -a /etc/resolv.conf /mnt/etc/resolv.conf
+ printf '%s\n' $'[REDACTED]\''
+ cp /metadata/sys-x11.loader.conf /mnt/boot/loader.conf
+ ln -fs /usr/share/zoneinfo/America/Caracas /mnt/etc/localtime
+ sysrc -f /mnt/etc/rc.conf 'sshd_enable=YES'
sshd_enable: NO -> YES
+ cp /metadata/sys-x11.sshd_config /mnt/etc/ssh/sshd_config
+ cp /metadata/sys-x11.sysctl.conf /mnt/etc/sysctl.conf
+ mkdir -p /mnt/usr/local/etc/pkg/repos
+ cp /metadata/sys-x11.FreeBSD.conf /mnt/usr/local/etc/pkg/repos/FreeBSD.conf
+ cp /metadata/sys-x11.FreeBSD-base.conf /mnt/usr/local/etc/pkg/repos/FreeBSD-base.conf
+ cp /metadata/sys-x11.FreeBSD-Custom.conf /mnt/usr/local/etc/pkg/repos/FreeBSD-Custom.conf
+ mkdir -p /mnt/usr/local/etc/appjail
+ cp /metadata/sys-x11.appjail.conf /mnt/usr/local/etc/appjail/appjail.conf
+ mkdir -p /mnt/usr/local/etc/appjail/hooks/pre.d
+ cp -a /mnt/usr/local/share/examples/appjail/hooks/pre.d/security-group.sh /mnt/usr/local/share/examples/appjail/hooks/pre.d/security-table.sh /mnt/usr/local/etc/appjail/hooks/pre.d
+ mkdir -p /mnt/etc
+ cp /metadata/sys-x11.pf.conf /mnt/etc/pf.conf
+ sysrc -f /mnt/etc/rc.conf 'gnbd_enable=YES'
gnbd_enable: -> YES
+ sysrc -f /mnt/etc/rc.conf 'gnbd_devices=nbd0'
gnbd_devices: -> nbd0
+ sysrc -f /mnt/etc/rc.conf 'gnbd_nbd0_host=10.0.0.62'
gnbd_nbd0_host: -> 10.0.0.62
+ sysrc -f /mnt/etc/rc.conf 'gnbd_nbd0_conns=16'
gnbd_nbd0_conns: -> 16
+ mkdir -p /mnt/usr/local/etc/rc.d
+ cp /metadata/sys-x11.mountdata.rc /mnt/usr/local/etc/rc.d/mountdata
+ chmod +x /mnt/usr/local/etc/rc.d/mountdata
+ sysrc -f /mnt/etc/rc.conf 'mountdata_enable=YES'
mountdata_enable: -> YES
+ mkdir -p /mnt/usr/local/etc
+ cp /metadata/sys-x11.doas.conf /mnt/usr/local/etc/doas.conf
+ chmod 600 /mnt/usr/local/etc/doas.conf
+ pw -R /mnt useradd -n noroot -u 15000 -d /zdata/noroot
vm_list: -> sys-x11
Starting sys-x11
* found guest in /vm/sys-x11
* booting...
last_update: 1 minute and 22.92 seconds
job_id: 9
$ appjail cmd jexec sys-x11 vm list sys-x11
NAME DATASTORE LOADER CPU MEMORY VNC AUTO STATE TAGS
sys-x11 default bhyveload 4 4G - Yes [1] Running (68848) -
$ cat ~/.ssh/config
...
Host sys-x11
User root
StrictHostKeyChecking=no
UserKnownHostsFile=/dev/null
$ ssh sys-x11
Warning: Permanently added 'sys-x11' (ED25519) to the list of known hosts.
root@sys-x11:~ # ^D
Shared connection to sys-x11 closed.
It’s time to install applications from x11appjail. We could simply download a binary and run it (from Xpra, of course) but let’s add a bit more (necessary) complexity: using an OCI image. I prefer to use the latest branch, but, as you probably know, sometimes a package can be broken, so it might disappear at any moment, especially more complex ones like Thunderbird, Firefox, Chromium, etc., so it’s a good idea to cache them. Makejails in x11appjail (at least those based on FreeBSD) only install dependencies when they aren’t present in the jail. The other reason to use an OCI image is customization: for example, we can install a font we want that isn’t included by default, or do whatever we like. Just remember two things: /noroot (inside the jail) is persistent storage, so anything you save there will remain there, overwriting everything else, forever. The other thing to remember is that the noroot user is created at runtime, so you can't USER noroot, although chown(8) is used to set the owner and group permissions recursively on /noroot, so you can save files and, at runtime, the permissions will be set.
$ ssh noroot@sys-x11
Warning: Permanently added 'sys-x11' (ED25519) to the list of known hosts.
If you want df(1) and other commands to display disk sizes in
kilobytes instead of 512-byte blocks, set BLOCKSIZE in your
environment to 'K'. You can also use 'M' for Megabytes or 'G' for
Gigabytes. If you want df(1) to automatically select the best size
then use 'df -h'.
noroot@sys-x11:~ $ cat Containerfile.d/base/Containerfile
FROM ghcr.io/freebsd/freebsd-runtime:15.snap
ENV ASSUME_ALWAYS_YES=yes
RUN rm -rf /etc/pkg && \
mkdir -p /etc/pkg && \
rm -rf /usr/local/etc/pkg
COPY PKG.conf /etc/pkg/FreeBSD.conf
RUN pkg update && \
pkg install \
FreeBSD-set-base-jail \
dbus \
xauth \
xrandr \
ratpoison \
xorg-fonts \
mesa-dri \
mesa-libs \
setxkbmap \
xdg-utils \
dunst && \
pkg clean -a
noroot@sys-x11:~ $ cat Containerfile.d/base/PKG.conf
FreeBSD-ports: {
url: "pkg+https://pkg.FreeBSD.org/${ABI}/latest",
mirror_type: "srv",
signature_type: "fingerprints",
fingerprints: "/usr/share/keys/pkg",
enabled: yes
}
FreeBSD-ports-kmods: {
enabled: no
}
FreeBSD-base: {
url: "pkg+https://pkg.FreeBSD.org/${ABI}/base_latest",
mirror_type: "srv",
signature_type: "fingerprints",
fingerprints: "/usr/share/keys/pkg",
enabled: yes
}
Custom: {
url: "http://pkg.dyn.dc-air.home.arpa:4080/150amd64-local",
mirror_type: "http",
priority: 1,
enabled: yes
}
noroot@sys-x11:~/Containerfile.d/base $ doas ./build.sh
...
noroot@sys-x11:~/Containerfile.d/base $ doas buildah images x11appjail
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/x11appjail latest ac3ba84e38e0 2 days ago 3.52 GB
One of the advantages of OCI is its layers, which allow us to save space by creating an OCI image and then reusing it in another image. That’s what we’ll do here: create a “base” image for the others. Let’s start with nanonote, since it’s the simplest application.
noroot@sys-x11:~/Containerfile.d/base $ cd ../nanonote/
noroot@sys-x11:~/Containerfile.d/nanonote $ cat build.sh
#!/bin/sh
exec buildah build --network=host --tag=nanonote
noroot@sys-x11:~/Containerfile.d/nanonote $ cat Containerfile
FROM x11appjail
RUN pkg install -U nanonote && \
pkg clean -a
noroot@sys-x11:~/Containerfile.d/nanonote $ doas ./build.sh
...
noroot@sys-x11:~/Containerfile.d/nanonote $ doas buildah images nanonote
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/nanonote latest 01f0d2e27019 3 days ago 3.63 GB
Neat. Now it's time to install Nanonote. As described in x11appjail's README, the recommended way to run an installed x11appjail application is through a snippet manager. This way, we don't have to remember which environment variables we used previously.
noroot@sys-x11:~/Containerfile.d/nanonote $ mkdir -p ~/bin
noroot@sys-x11:~/Containerfile.d/nanonote $ pet version
pet version dev
noroot@sys-x11:~/Containerfile.d/nanonote $ grep Editor ~/.config/pet/config.toml
Editor = "nvim"
~/.config/pet/snippet.toml:
[[Snippets]]
Description = "Install Nanonote"
Output = ""
Tag = ["nanonote"]
command = """\
bin install github.com/DtxdF/x11appjail --force --name 'nanonote-amd64.appscript' && \
X11APPJAIL_INSTALL=1 \
X11APPJAIL_ALLOW_HOST=1 \
X11APPJAIL_NO_EPHEMERAL=1 \
X11APPJAIL_VIRTUALNET=ajnet \
X11APPJAIL_OCI_FROM=\"localhost/nanonote:latest\" \
${HOME}/bin/nanonote.appscript \
"""
We are using sysutils/bin to download the latest version of the AppScript, which will be saved in ~/bin. The environment variables are self-explanatory; the only one worth noting is X11APPJAIL_VIRTUALNET. As you can see above in sys-x11's deployment files, a virtual network is configured (and then pf.conf(5)). The use of virtual networks, together with the pf.conf(5) and the security-group hooks we have installed, allows us to grant or deny network visibility to a jail simply by using appjail-label(1)s and pf(4) rules. Although we are not using any pf(4) rules for this application, we will use them later on.
noroot@sys-x11:~/Containerfile.d/nanonote $ appjail network auto-create
[00:00:00] [ debug ] if_bridge kernel module already loaded.
[00:00:00] [ debug ] bridgestp kernel module already loaded.
[00:00:00] [ debug ] if_epair kernel module already loaded.
[00:00:00] [ debug ] Network information:
[00:00:00] [ debug ] - ADDRESS=192.168.16.0
[00:00:00] [ debug ] - NETWORK=192.168.16.0
[00:00:00] [ debug ] - NETMASK=255.255.255.0
[00:00:00] [ debug ] - CIDR=24
[00:00:00] [ debug ] - WILDCARD=0.0.0.255
[00:00:00] [ debug ] - BROADCAST=192.168.16.255
[00:00:00] [ debug ] - MINADDR=192.168.16.1
[00:00:00] [ debug ] - MAXADDR=192.168.16.254
[00:00:00] [ debug ] - ADDRESSES=254
[00:00:00] [ debug ] - NAME=ajnet
[00:00:00] [ debug ] - DESCRIPTION=AppJail network
[00:00:00] [ debug ] - GATEWAY=192.168.16.1
[00:00:00] [ debug ] - MTU=1500
[00:00:00] [ debug ] Done.
noroot@sys-x11:~/Containerfile.d/nanonote $ pet exec -t nanonote
> bin install github.com/DtxdF/x11appjail --force --name 'nanonote-amd64.appscript' && X11APPJAIL_INSTALL=1 X11APPJAIL_ALLOW_HOST=1 X11APPJAIL_VIRTUALNET=ajnet X11APPJAIL_OCI_FROM="localhost/nanonote:latest" ${HOME}/bin/nanonote.appscript
• Getting latest release for DtxdF/x11appjail
• Starting download of https://api.github.com/repos/DtxdF/x11appjail/releases/assets/407004720
20.52 KiB / 20.52 KiB [---------------------------------------------------------------------------------------------------] 100.00% ? p/s 0s
• Copying for nanonote-amd64.appscript@v0.17.1 into /zdata/noroot/bin/nanonote.appscript
• Done installing nanonote-amd64.appscript v0.17.1
[00:00:00] [ info ] [x11appjail-nanonote-15000_default] Building ...
[00:00:00] [ debug ] [x11appjail-nanonote-15000_default] Main Makejail: Makejail
[00:00:00] [ debug ] [x11appjail-nanonote-15000_default] Using method:file (args:Makejail) from Makejail.
[00:00:00] [ debug ] [x11appjail-nanonote-15000_default] Including /tmp/appscript_riBNrJMogml/Makejail ...
[00:00:00] [ debug ] [x11appjail-nanonote-15000_default] Using method:github (args:AppJail-makejails/pkg-quick-conf) from gh+AppJail-makejails/pkg-quick-conf.
[00:00:00] [ debug ] [x11appjail-nanonote-15000_default] Using global cache directory (git): /usr/local/appjail/cache/git
[00:00:00] [ debug ] [x11appjail-nanonote-15000_default] Updating /usr/local/appjail/cache/git/a610fa4f22efa529a0323cbbdbbbe61ac557e6448f5c82602017671bf43c337a ...
[00:00:00] [ debug ] [x11appjail-nanonote-15000_default] Including /usr/local/appjail/cache/git/a610fa4f22efa529a0323cbbdbbbe61ac557e6448f5c82602017671bf43c337a/Makejail ...
[00:00:00] [ debug ] [x11appjail-nanonote-15000_default] Using method:github (args:AppJail-makejails/user-mapping) from gh+AppJail-makejails/user-mapping.
[00:00:00] [ debug ] [x11appjail-nanonote-15000_default] Using global cache directory (git): /usr/local/appjail/cache/git
[00:00:00] [ debug ] [x11appjail-nanonote-15000_default] Updating /usr/local/appjail/cache/git/8694750346db5c632c881f3bdb4e972ad76c3b2920961c86a8458a53d8c2dc87 ...
[00:00:01] [ debug ] [x11appjail-nanonote-15000_default] Including /usr/local/appjail/cache/git/8694750346db5c632c881f3bdb4e972ad76c3b2920961c86a8458a53d8c2dc87/Makejail ...
[00:00:01] [ debug ] [x11appjail-nanonote-15000_default] Using method:github (args:AppJail-makejails/efficient-makejail) from gh+AppJail-makejails/efficient-makejail.
[00:00:01] [ debug ] [x11appjail-nanonote-15000_default] Using global cache directory (git): /usr/local/appjail/cache/git
[00:00:01] [ debug ] [x11appjail-nanonote-15000_default] Updating /usr/local/appjail/cache/git/769075bb5004e85a1055cc117f0946e2085c216ec4ab13eaba4ec9ea0f6eabfc ...
[00:00:01] [ debug ] [x11appjail-nanonote-15000_default] Including /usr/local/appjail/cache/git/769075bb5004e85a1055cc117f0946e2085c216ec4ab13eaba4ec9ea0f6eabfc/Makejail ...
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] Makejail generated:
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] RAW cd -- "/usr/local/appjail/cache/git/a610fa4f22efa529a0323cbbdbbbe61ac557e6448f5c82602017671bf43c337a" # Makejail: /usr/local/appjail/cache/git/a610fa4f22efa529a0323cbbdbbbe61ac557e6448f5c82602017671bf43c337a/Makejail
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] ARG pkg_conf?
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] ARG pkg_name=FreeBSD
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] RAW if [ -n "${pkg_conf}" ]; then
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] RAW echo "===> Configuring pkg.conf(5): ${pkg_conf} -> /usr/local/etc/pkg/repos/${pkg_name}.conf ..."
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] CMD mkdir -p /usr/local/etc/pkg/repos
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] COPY "${pkg_conf}" "/usr/local/etc/pkg/repos/${pkg_name}.conf"
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] RAW fi
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] RAW cd -- "/tmp/appscript_riBNrJMogml" # Makejail: /tmp/appscript_riBNrJMogml/Makejail
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] RAW cd -- "/usr/local/appjail/cache/git/8694750346db5c632c881f3bdb4e972ad76c3b2920961c86a8458a53d8c2dc87" # Makejail: /usr/local/appjail/cache/git/8694750346db5c632c881f3bdb4e972ad76c3b2920961c86a8458a53d8c2dc87/Makejail
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] ARG puid=1000
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] ARG pgid=1000
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] OPTION start
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] CMD pw groupadd -n noroot -g "${pgid}" && pw useradd -n noroot -d /noroot -u "${puid}" -g "${pgid}" && mkdir -p /noroot && chown noroot:noroot /noroot
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] RAW cd -- "/tmp/appscript_riBNrJMogml" # Makejail: /tmp/appscript_riBNrJMogml/Makejail
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] RAW cd -- "/usr/local/appjail/cache/git/769075bb5004e85a1055cc117f0946e2085c216ec4ab13eaba4ec9ea0f6eabfc" # Makejail: /usr/local/appjail/cache/git/769075bb5004e85a1055cc117f0946e2085c216ec4ab13eaba4ec9ea0f6eabfc/Makejail
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] SYSRC syslogd_flags=-ss
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] SYSRC sendmail_enable=NO
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] SYSRC sendmail_submit_enable=NO
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] SYSRC sendmail_outbound_enable=NO
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] SYSRC sendmail_msp_queue_enable=NO
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] SYSRC "cron_flags=-J 60"
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] SERVICE syslogd restart
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] SERVICE cron restart
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] RAW cd -- "/tmp/appscript_riBNrJMogml" # Makejail: /tmp/appscript_riBNrJMogml/Makejail
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] OPTION start
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] OPTION overwrite=force
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] OPTION type=thick
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] OPTION volume=data mountpoint:/noroot owner:${puid} group:${pgid}
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] CMD chown -Rh noroot:noroot /noroot
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] RAW packages=
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] RAW for package in nanonote xauth xrandr ratpoison xorg-fonts setxkbmap xdg-utils; do
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] RAW if ! appjail pkg jail "${APPJAIL_JAILNAME}" info -e "${package}"; then
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] RAW packages="${packages} ${package}"
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] RAW fi
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] RAW done
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] RAW if [ -n "${packages}" ]; then
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] RAW appjail pkg jail "${APPJAIL_JAILNAME}" install -y ${packages}
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] RAW fi
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/all/RAW (args:cd -- "/usr/local/appjail/cache/git/a610fa4f22efa529a0323cbbdbbbe61ac557e6448f5c82602017671bf43c337a" # Makejail: /usr/local/appjail/cache/git/a610fa4f22efa529a0323cbbdbbbe61ac557e6448f5c82602017671bf43c337a/Makejail)
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/all/ARG (args:pkg_conf?)
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/all/ARG (args:pkg_name=FreeBSD)
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/all/RAW (args:if [ -n "${pkg_conf}" ]; then)
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/all/RAW (args:echo "===> Configuring pkg.conf(5): ${pkg_conf} -> /usr/local/etc/pkg/repos/${pkg_name}.conf ...")
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/all/CMD (args:mkdir -p /usr/local/etc/pkg/repos)
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/all/COPY (args:"${pkg_conf}" "/usr/local/etc/pkg/repos/${pkg_name}.conf")
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/all/RAW (args:fi)
[00:00:02] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/all/RAW (args:cd -- "/tmp/appscript_riBNrJMogml" # Makejail: /tmp/appscript_riBNrJMogml/Makejail)
[00:00:03] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/all/RAW (args:cd -- "/usr/local/appjail/cache/git/8694750346db5c632c881f3bdb4e972ad76c3b2920961c86a8458a53d8c2dc87" # Makejail: /usr/local/appjail/cache/git/8694750346db5c632c881f3bdb4e972ad76c3b2920961c86a8458a53d8c2dc87/Makejail)
[00:00:03] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/all/ARG (args:puid=1000)
[00:00:03] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/all/ARG (args:pgid=1000)
[00:00:03] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/build/OPTION (args:start)
[00:00:03] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/all/CMD (args:pw groupadd -n noroot -g "${pgid}" && pw useradd -n noroot -d /noroot -u "${puid}" -g "${pgid}" && mkdir -p /noroot && chown noroot:noroot /noroot)
[00:00:03] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/all/RAW (args:cd -- "/tmp/appscript_riBNrJMogml" # Makejail: /tmp/appscript_riBNrJMogml/Makejail)
[00:00:03] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/all/RAW (args:cd -- "/usr/local/appjail/cache/git/769075bb5004e85a1055cc117f0946e2085c216ec4ab13eaba4ec9ea0f6eabfc" # Makejail: /usr/local/appjail/cache/git/769075bb5004e85a1055cc117f0946e2085c216ec4ab13eaba4ec9ea0f6eabfc/Makejail)
[00:00:03] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/all/SYSRC (args:syslogd_flags=-ss)
[00:00:03] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/all/SYSRC (args:sendmail_enable=NO)
[00:00:03] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/all/SYSRC (args:sendmail_submit_enable=NO)
[00:00:03] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/all/SYSRC (args:sendmail_outbound_enable=NO)
[00:00:03] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/all/SYSRC (args:sendmail_msp_queue_enable=NO)
[00:00:03] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/all/SYSRC (args:"cron_flags=-J 60")
[00:00:04] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/all/SERVICE (args:syslogd restart)
[00:00:04] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/all/SERVICE (args:cron restart)
[00:00:04] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/all/RAW (args:cd -- "/tmp/appscript_riBNrJMogml" # Makejail: /tmp/appscript_riBNrJMogml/Makejail)
[00:00:04] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/build/OPTION (args:start)
[00:00:04] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/build/OPTION (args:overwrite=force)
[00:00:04] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/build/OPTION (args:type=thick)
[00:00:04] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/build/OPTION (args:volume=data mountpoint:/noroot owner:${puid} group:${pgid})
[00:00:04] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/all/CMD (args:chown -Rh noroot:noroot /noroot)
[00:00:04] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/all/RAW (args:packages=)
[00:00:04] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/all/RAW (args:for package in nanonote xauth xrandr ratpoison xorg-fonts setxkbmap xdg-utils; do)
[00:00:04] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/all/RAW (args:if ! appjail pkg jail "${APPJAIL_JAILNAME}" info -e "${package}"; then)
[00:00:04] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/all/RAW (args:packages="${packages} ${package}")
[00:00:04] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/all/RAW (args:fi)
[00:00:04] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/all/RAW (args:done)
[00:00:04] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/all/RAW (args:if [ -n "${packages}" ]; then)
[00:00:04] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/all/RAW (args:appjail pkg jail "${APPJAIL_JAILNAME}" install -y ${packages})
[00:00:04] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (cmd): /usr/local/share/appjail/makejail/cmd/all/RAW (args:fi)
[00:00:04] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (write): /usr/local/share/appjail/makejail/write/all/ARG (input:/usr/local/appjail/cache/tmp/.appjail/appjail.ER7FLYKB9Y)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (write): /usr/local/share/appjail/makejail/write/build/OPTION (input:/usr/local/appjail/cache/tmp/.appjail/appjail.PnflFqcp1X)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (write): /usr/local/share/appjail/makejail/write/all/RAW (input:/usr/local/appjail/cache/tmp/.appjail/appjail.RUN19lVR8Q/stages/build/0.RAW)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (write): /usr/local/share/appjail/makejail/write/all/RAW (input:/usr/local/appjail/cache/tmp/.appjail/appjail.RUN19lVR8Q/stages/build/3.RAW)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (write): /usr/local/share/appjail/makejail/write/all/RAW (input:/usr/local/appjail/cache/tmp/.appjail/appjail.RUN19lVR8Q/stages/build/4.RAW)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (write): /usr/local/share/appjail/makejail/write/all/CMD (input:/usr/local/appjail/cache/tmp/.appjail/appjail.RUN19lVR8Q/stages/build/5.CMD)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (write): /usr/local/share/appjail/makejail/write/all/COPY (input:/usr/local/appjail/cache/tmp/.appjail/appjail.RUN19lVR8Q/stages/build/6.COPY)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (write): /usr/local/share/appjail/makejail/write/all/RAW (input:/usr/local/appjail/cache/tmp/.appjail/appjail.RUN19lVR8Q/stages/build/7.RAW)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (write): /usr/local/share/appjail/makejail/write/all/RAW (input:/usr/local/appjail/cache/tmp/.appjail/appjail.RUN19lVR8Q/stages/build/8.RAW)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (write): /usr/local/share/appjail/makejail/write/all/RAW (input:/usr/local/appjail/cache/tmp/.appjail/appjail.RUN19lVR8Q/stages/build/9.RAW)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (write): /usr/local/share/appjail/makejail/write/all/CMD (input:/usr/local/appjail/cache/tmp/.appjail/appjail.RUN19lVR8Q/stages/build/13.CMD)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (write): /usr/local/share/appjail/makejail/write/all/RAW (input:/usr/local/appjail/cache/tmp/.appjail/appjail.RUN19lVR8Q/stages/build/14.RAW)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (write): /usr/local/share/appjail/makejail/write/all/RAW (input:/usr/local/appjail/cache/tmp/.appjail/appjail.RUN19lVR8Q/stages/build/15.RAW)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (write): /usr/local/share/appjail/makejail/write/all/SYSRC (input:/usr/local/appjail/cache/tmp/.appjail/appjail.RUN19lVR8Q/stages/build/16.SYSRC)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (write): /usr/local/share/appjail/makejail/write/all/SYSRC (input:/usr/local/appjail/cache/tmp/.appjail/appjail.RUN19lVR8Q/stages/build/17.SYSRC)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (write): /usr/local/share/appjail/makejail/write/all/SYSRC (input:/usr/local/appjail/cache/tmp/.appjail/appjail.RUN19lVR8Q/stages/build/18.SYSRC)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (write): /usr/local/share/appjail/makejail/write/all/SYSRC (input:/usr/local/appjail/cache/tmp/.appjail/appjail.RUN19lVR8Q/stages/build/19.SYSRC)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (write): /usr/local/share/appjail/makejail/write/all/SYSRC (input:/usr/local/appjail/cache/tmp/.appjail/appjail.RUN19lVR8Q/stages/build/20.SYSRC)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (write): /usr/local/share/appjail/makejail/write/all/SYSRC (input:/usr/local/appjail/cache/tmp/.appjail/appjail.RUN19lVR8Q/stages/build/21.SYSRC)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (write): /usr/local/share/appjail/makejail/write/all/SERVICE (input:/usr/local/appjail/cache/tmp/.appjail/appjail.RUN19lVR8Q/stages/build/22.SERVICE)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (write): /usr/local/share/appjail/makejail/write/all/SERVICE (input:/usr/local/appjail/cache/tmp/.appjail/appjail.RUN19lVR8Q/stages/build/23.SERVICE)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (write): /usr/local/share/appjail/makejail/write/all/RAW (input:/usr/local/appjail/cache/tmp/.appjail/appjail.RUN19lVR8Q/stages/build/24.RAW)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (write): /usr/local/share/appjail/makejail/write/all/CMD (input:/usr/local/appjail/cache/tmp/.appjail/appjail.RUN19lVR8Q/stages/build/29.CMD)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (write): /usr/local/share/appjail/makejail/write/all/RAW (input:/usr/local/appjail/cache/tmp/.appjail/appjail.RUN19lVR8Q/stages/build/30.RAW)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (write): /usr/local/share/appjail/makejail/write/all/RAW (input:/usr/local/appjail/cache/tmp/.appjail/appjail.RUN19lVR8Q/stages/build/31.RAW)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (write): /usr/local/share/appjail/makejail/write/all/RAW (input:/usr/local/appjail/cache/tmp/.appjail/appjail.RUN19lVR8Q/stages/build/32.RAW)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (write): /usr/local/share/appjail/makejail/write/all/RAW (input:/usr/local/appjail/cache/tmp/.appjail/appjail.RUN19lVR8Q/stages/build/33.RAW)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (write): /usr/local/share/appjail/makejail/write/all/RAW (input:/usr/local/appjail/cache/tmp/.appjail/appjail.RUN19lVR8Q/stages/build/34.RAW)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (write): /usr/local/share/appjail/makejail/write/all/RAW (input:/usr/local/appjail/cache/tmp/.appjail/appjail.RUN19lVR8Q/stages/build/35.RAW)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (write): /usr/local/share/appjail/makejail/write/all/RAW (input:/usr/local/appjail/cache/tmp/.appjail/appjail.RUN19lVR8Q/stages/build/36.RAW)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (write): /usr/local/share/appjail/makejail/write/all/RAW (input:/usr/local/appjail/cache/tmp/.appjail/appjail.RUN19lVR8Q/stages/build/37.RAW)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] Running makejail command (write): /usr/local/share/appjail/makejail/write/all/RAW (input:/usr/local/appjail/cache/tmp/.appjail/appjail.RUN19lVR8Q/stages/build/38.RAW)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] Buildscript generated:
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] set -T
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default]
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] . "${APPJAIL_CONFIG}"
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] . "${LIBDIR}/load"
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default]
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] lib_load "${LIBDIR}/sysexits"
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] lib_load "${LIBDIR}/atexit"
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] lib_load "${LIBDIR}/log"
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] lib_load "${LIBDIR}/check_func"
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default]
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] lib_atexit_init
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default]
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] trap '' SIGINT
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] set -e
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] pkg_conf=""
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] pkg_name="FreeBSD"
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] puid="1000"
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] pgid="1000"
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] lib_load "${LIBDIR}/check_func"
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default]
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] while [ $# -gt 0 ]; do
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] case "$1" in
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] --pkg_conf)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] pkg_conf="$2"; shift
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] ;;
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] --pkg_name)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] pkg_name="$2"; shift
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] ;;
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] --puid)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] puid="$2"; shift
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] ;;
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] --pgid)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] pgid="$2"; shift
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] ;;
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] --)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] shift
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] break
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] ;;
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] --*)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] lib_err ${EX_USAGE} -- "$1: Invalid option."
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] ;;
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] *)
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] break
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] ;;
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] esac
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default]
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] shift
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] done
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] if lib_check_empty "$pkg_name"; then
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] lib_err ${EX_DATAERR} "option requires an argument -- pkg_name"
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] fi
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] if lib_check_empty "$puid"; then
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] lib_err ${EX_DATAERR} "option requires an argument -- puid"
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] fi
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] if lib_check_empty "$pgid"; then
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] lib_err ${EX_DATAERR} "option requires an argument -- pgid"
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] fi
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] "${APPJAIL_SCRIPT}" quick "${APPJAIL_JAILNAME}" "start" "start" "overwrite=force" "type=thick" "volume=data mountpoint:/noroot owner:${puid} group:${pgid}" "x11" "template=/tmp/appscript_riBNrJMogml/template.conf" "ephemeral" "virtualnet=ajnet:<random> default" "nat" "fstab=/zdata/noroot/x11appjail/data/x11appjail-nanonote-15000_default data <volumefs>" "fstab=/zdata/noroot/x11appjail/cache/x11appjail-nanonote-15000_default/pkg /var/cache/pkg" "copydir=/tmp/appscript_riBNrJMogml/files" "file=/etc/rc.conf.local" "from=localhost/nanonote:latest"
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] cd -- "/usr/local/appjail/cache/git/a610fa4f22efa529a0323cbbdbbbe61ac557e6448f5c82602017671bf43c337a" # Makejail: /usr/local/appjail/cache/git/a610fa4f22efa529a0323cbbdbbbe61ac557e6448f5c82602017671bf43c337a/Makejail
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] if [ -n "${pkg_conf}" ]; then
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] echo "===> Configuring pkg.conf(5): ${pkg_conf} -> /usr/local/etc/pkg/repos/${pkg_name}.conf ..."
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] "${APPJAIL_SCRIPT}" cmd jexec "${APPJAIL_JAILNAME}" -w "/" env "pkg_conf=${pkg_conf}" "pkg_name=${pkg_name}" sh -c "mkdir -p /usr/local/etc/pkg/repos"
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] cp -a -- "${pkg_conf}" "${APPJAIL_JAILDIR}//usr/local/etc/pkg/repos/${pkg_name}.conf"
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] fi
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] cd -- "/tmp/appscript_riBNrJMogml" # Makejail: /tmp/appscript_riBNrJMogml/Makejail
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] cd -- "/usr/local/appjail/cache/git/8694750346db5c632c881f3bdb4e972ad76c3b2920961c86a8458a53d8c2dc87" # Makejail: /usr/local/appjail/cache/git/8694750346db5c632c881f3bdb4e972ad76c3b2920961c86a8458a53d8c2dc87/Makejail
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] "${APPJAIL_SCRIPT}" cmd jexec "${APPJAIL_JAILNAME}" -w "/" env "pkg_conf=${pkg_conf}" "pkg_name=${pkg_name}" "puid=${puid}" "pgid=${pgid}" sh -c "pw groupadd -n noroot -g \"\${pgid}\" && pw useradd -n noroot -d /noroot -u \"\${puid}\" -g \"\${pgid}\" && mkdir -p /noroot && chown noroot:noroot /noroot"
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] cd -- "/tmp/appscript_riBNrJMogml" # Makejail: /tmp/appscript_riBNrJMogml/Makejail
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] cd -- "/usr/local/appjail/cache/git/769075bb5004e85a1055cc117f0946e2085c216ec4ab13eaba4ec9ea0f6eabfc" # Makejail: /usr/local/appjail/cache/git/769075bb5004e85a1055cc117f0946e2085c216ec4ab13eaba4ec9ea0f6eabfc/Makejail
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] "${APPJAIL_SCRIPT}" sysrc jail "${APPJAIL_JAILNAME}" -- "syslogd_flags=-ss"
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] "${APPJAIL_SCRIPT}" sysrc jail "${APPJAIL_JAILNAME}" -- "sendmail_enable=NO"
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] "${APPJAIL_SCRIPT}" sysrc jail "${APPJAIL_JAILNAME}" -- "sendmail_submit_enable=NO"
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] "${APPJAIL_SCRIPT}" sysrc jail "${APPJAIL_JAILNAME}" -- "sendmail_outbound_enable=NO"
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] "${APPJAIL_SCRIPT}" sysrc jail "${APPJAIL_JAILNAME}" -- "sendmail_msp_queue_enable=NO"
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] "${APPJAIL_SCRIPT}" sysrc jail "${APPJAIL_JAILNAME}" -- "cron_flags=-J 60"
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] "${APPJAIL_SCRIPT}" service jail "${APPJAIL_JAILNAME}" -- "syslogd" "restart"
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] "${APPJAIL_SCRIPT}" service jail "${APPJAIL_JAILNAME}" -- "cron" "restart"
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] cd -- "/tmp/appscript_riBNrJMogml" # Makejail: /tmp/appscript_riBNrJMogml/Makejail
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] "${APPJAIL_SCRIPT}" cmd jexec "${APPJAIL_JAILNAME}" -w "/" env "pkg_conf=${pkg_conf}" "pkg_name=${pkg_name}" "puid=${puid}" "pgid=${pgid}" sh -c "chown -Rh noroot:noroot /noroot"
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] packages=
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] for package in nanonote xauth xrandr ratpoison xorg-fonts setxkbmap xdg-utils; do
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] if ! appjail pkg jail "${APPJAIL_JAILNAME}" info -e "${package}"; then
[00:00:05] [ debug ] [x11appjail-nanonote-15000_default] packages="${packages} ${package}"
[00:00:06] [ debug ] [x11appjail-nanonote-15000_default] fi
[00:00:06] [ debug ] [x11appjail-nanonote-15000_default] done
[00:00:06] [ debug ] [x11appjail-nanonote-15000_default] if [ -n "${packages}" ]; then
[00:00:06] [ debug ] [x11appjail-nanonote-15000_default] appjail pkg jail "${APPJAIL_JAILNAME}" install -y ${packages}
[00:00:06] [ debug ] [x11appjail-nanonote-15000_default] fi
[00:00:06] [ debug ] [x11appjail-nanonote-15000_default] quick parameters: start start overwrite=force type=thick volume=data mountpoint:/noroot owner:15000 group:15000 x11 template=/tmp/appscript_riBNrJMogml/template.conf ephemeral virtualnet=ajnet:<random> default nat fstab=/zdata/noroot/x11appjail/data/x11appjail-nanonote-15000_default data <volumefs> fstab=/zdata/noroot/x11appjail/cache/x11appjail-nanonote-15000_default/pkg /var/cache/pkg copydir=/tmp/appscript_riBNrJMogml/files file=/etc/rc.conf.local from=localhost/nanonote:latest
[00:00:06] [ info ] [x11appjail-nanonote-15000_default] Creating an empty jail ...
[00:00:07] [ info ] [x11appjail-nanonote-15000_default] Done.
[00:00:07] [ debug ] [x11appjail-nanonote-15000_default] Creating a container (name:appjail-36e452c96d2) from localhost/nanonote:latest (args:) ...
appjail-36e452c96d2
[00:00:09] [ debug ] [x11appjail-nanonote-15000_default] Mounting container directory into jail directory ...
[00:00:09] [ debug ] [x11appjail-nanonote-15000_default] Checking for mounted file systems in `/usr/local/appjail/jails/x11appjail-nanonote-15000_default/jail` ...
[00:00:09] [ debug ] [x11appjail-nanonote-15000_default] Mounting: mount_nullfs "/var/db/containers/storage/zfs/graph/a313313359707583464c51d8e5b0e910dd85fcefc6f88a061b54ac276e39fbc6" "/usr/local/appjail/jails/x11appjail-nanonote-15000_default/jail"
[00:00:10] [ debug ] [x11appjail-nanonote-15000_default] Adding label io.buildah.version (value:1.43.1) ...
[00:00:10] [ debug ] [x11appjail-nanonote-15000_default] Adding files ("/etc/rc.conf.local") to the list of files to copy ...
[00:00:10] [ debug ] [x11appjail-nanonote-15000_default] (1/1): Checking /etc/rc.conf.local ...
[00:00:10] [ debug ] [x11appjail-nanonote-15000_default] (1/1): Copying etc/rc.conf.local ...
[00:00:10] [ debug ] [x11appjail-nanonote-15000_default] Copying /etc/localtime as /usr/local/appjail/jails/x11appjail-nanonote-15000_default/jail/etc/localtime
[00:00:10] [ debug ] [x11appjail-nanonote-15000_default] Copying /etc/resolv.conf as /usr/local/appjail/jails/x11appjail-nanonote-15000_default/jail/etc/resolv.conf
[00:00:10] [ debug ] [x11appjail-nanonote-15000_default] Reserving an IPv4 address for x11appjail-nanonote-15000_default in ajnet ...
[00:00:11] [ debug ] [x11appjail-nanonote-15000_default] VNET Interface:e[ab]_d778686ad50 Description:
[00:00:11] [ debug ] [x11appjail-nanonote-15000_default] ajnet is the default router.
[00:00:11] [ debug ] [x11appjail-nanonote-15000_default] Creating NAT rules ...
[00:00:11] [ debug ] [x11appjail-nanonote-15000_default] Setting NAT rule: network:ajnet ext_if:vtnet0 logopts:0 () on_if:vtnet0
[00:00:11] [ debug ] [x11appjail-nanonote-15000_default] Configuring volumes ...
[00:00:11] [ debug ] [x11appjail-nanonote-15000_default] volume (data): "group:15000" "mountpoint:/noroot" "owner:15000" "perm:" "type:"
[00:00:11] [ debug ] [x11appjail-nanonote-15000_default] Configuring fstab ...
[00:00:12] [ debug ] [x11appjail-nanonote-15000_default] fstab#0: "/zdata/noroot/x11appjail/data/x11appjail-nanonote-15000_default" "data" "<volumefs>" "rw" "0" "0"
[00:00:12] [ debug ] [x11appjail-nanonote-15000_default] fstab#1: "/zdata/noroot/x11appjail/cache/x11appjail-nanonote-15000_default/pkg" "/var/cache/pkg" "nullfs" "rw" "0" "0"
[00:00:12] [ debug ] [x11appjail-nanonote-15000_default] Using x11 option ...
[00:00:12] [ debug ] [x11appjail-nanonote-15000_default] Setting the boot flag to the x11appjail-nanonote-15000_default jail ...
[00:00:13] [ debug ] [x11appjail-nanonote-15000_default] unmounting: umount -f "/usr/local/appjail/jails/x11appjail-nanonote-15000_default/jail"
[00:00:13] [ debug ] [x11appjail-nanonote-15000_default] Template generated:
[00:00:13] [ debug ] [x11appjail-nanonote-15000_default] exec.start: "/bin/sh /etc/rc"
[00:00:13] [ debug ] [x11appjail-nanonote-15000_default] exec.stop: "/bin/sh /etc/rc.shutdown jail"
[00:00:13] [ debug ] [x11appjail-nanonote-15000_default] mount.devfs
[00:00:13] [ debug ] [x11appjail-nanonote-15000_default] persist
[00:00:13] [ debug ] [x11appjail-nanonote-15000_default] exec.clean
[00:00:13] [ debug ] [x11appjail-nanonote-15000_default] securelevel: 3
[00:00:13] [ debug ] [x11appjail-nanonote-15000_default] sysvmsg: new
[00:00:13] [ debug ] [x11appjail-nanonote-15000_default] sysvsem: new
[00:00:13] [ debug ] [x11appjail-nanonote-15000_default] sysvshm: new
[00:00:13] [ debug ] [x11appjail-nanonote-15000_default] allow.chflags
[00:00:13] [ debug ] [x11appjail-nanonote-15000_default] vnet
[00:00:13] [ debug ] [x11appjail-nanonote-15000_default] vnet.interface: "eb_d778686ad50"
[00:00:13] [ debug ] [x11appjail-nanonote-15000_default] exec.prestart: "appjail network plug -e \"d778686ad50\" -n \"ajnet\""
[00:00:13] [ debug ] [x11appjail-nanonote-15000_default] exec.poststart: "appjail network assign -d -e \"d778686ad50\" -j \"${name}\" -n \"ajnet\""
[00:00:13] [ debug ] [x11appjail-nanonote-15000_default] exec.poststop: "appjail network unplug \"ajnet\" \"d778686ad50\""
[00:00:13] [ debug ] [x11appjail-nanonote-15000_default] exec.prestart+: "appjail nat on jail \"${name}\""
[00:00:13] [ debug ] [x11appjail-nanonote-15000_default] exec.poststop+: "appjail nat off jail \"${name}\""
[00:00:14] [ debug ] [x11appjail-nanonote-15000_default] Locking x11appjail-nanonote-15000_default ...
[00:00:14] [ info ] [x11appjail-nanonote-15000_default] Starting x11appjail-nanonote-15000_default...
[00:00:15] [ debug ] [x11appjail-nanonote-15000_default] Using `/usr/local/appjail/jails/x11appjail-nanonote-15000_default/conf/template.conf` as the template.
[00:00:15] [ debug ] [x11appjail-nanonote-15000_default] Writing `/usr/local/appjail/jails/x11appjail-nanonote-15000_default/conf/template.conf` content to `/usr/local/appjail/cache/tmp/.appjail/appjail.d1vSQYtGP9` ...
[00:00:15] [ debug ] [x11appjail-nanonote-15000_default] Checking for parameters marked as required...
[00:00:15] [ debug ] [x11appjail-nanonote-15000_default] Jail 'x11appjail-nanonote-15000_default' is a container.
[00:00:16] [ debug ] [x11appjail-nanonote-15000_default] Checking for mounted file systems in `/usr/local/appjail/jails/x11appjail-nanonote-15000_default/jail` ...
[00:00:16] [ debug ] [x11appjail-nanonote-15000_default] Mounting: mount_nullfs "/var/db/containers/storage/zfs/graph/a313313359707583464c51d8e5b0e910dd85fcefc6f88a061b54ac276e39fbc6" "/usr/local/appjail/jails/x11appjail-nanonote-15000_default/jail"
[00:00:16] [ debug ] [x11appjail-nanonote-15000_default] Running: date +%Y-%m-%d.log
[00:00:16] [ debug ] [x11appjail-nanonote-15000_default] exec.consolelog: /var/log/appjail/jails/x11appjail-nanonote-15000_default/console/2026-04-29.log
[00:00:16] [ debug ] [x11appjail-nanonote-15000_default] Compiling fstab file ...
[00:00:16] [ debug ] [x11appjail-nanonote-15000_default] Compiling fstab #0: /zdata/noroot/x11appjail/data/x11appjail-nanonote-15000_default data <volumefs> rw 0 0
[00:00:17] [ debug ] [x11appjail-nanonote-15000_default] Compiling fstab #1: /zdata/noroot/x11appjail/cache/x11appjail-nanonote-15000_default/pkg /var/cache/pkg nullfs rw 0 0
[00:00:17] [ debug ] [x11appjail-nanonote-15000_default] Compiling fstab #2: /tmp/.X11-unix /tmp/.X11-unix nullfs ro 0 0
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] mount.fstab: /usr/local/appjail/jails/x11appjail-nanonote-15000_default/conf/fstab
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] host.hostname: x11appjail-nanonote-15000_default.appjail
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] Path: /usr/local/appjail/jails/x11appjail-nanonote-15000_default/jail
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] Resolving dependencies for x11appjail-nanonote-15000_default...
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] x11appjail-nanonote-15000_default appended to the `seen` list.
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] x11appjail-nanonote-15000_default appended to the `resolved` list.
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] Compiling template to `/usr/local/appjail/jails/x11appjail-nanonote-15000_default/conf/jail.conf` ...
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] jail.conf generated:
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] x11appjail-nanonote-15000_default {
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] exec.start = "/bin/sh /etc/rc";
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] exec.stop = "/bin/sh /etc/rc.shutdown jail";
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] mount.devfs;
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] persist;
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] exec.clean;
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] securelevel = "3";
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] sysvmsg = "new";
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] sysvsem = "new";
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] sysvshm = "new";
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] allow.chflags;
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] vnet;
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] vnet.interface = "eb_d778686ad50";
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] exec.prestart = "appjail network plug -e \"d778686ad50\" -n \"ajnet\"";
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] exec.poststart = "appjail network assign -d -e \"d778686ad50\" -j \"${name}\" -n \"ajnet\"";
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] exec.poststop = "appjail network unplug \"ajnet\" \"d778686ad50\"";
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] exec.prestart += "appjail nat on jail \"${name}\"";
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] exec.poststop += "appjail nat off jail \"${name}\"";
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] exec.consolelog = "/var/log/appjail/jails/x11appjail-nanonote-15000_default/console/2026-04-29.log";
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] mount.fstab = "/usr/local/appjail/jails/x11appjail-nanonote-15000_default/conf/fstab";
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] host.hostname = "x11appjail-nanonote-15000_default.appjail";
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] path = "/usr/local/appjail/jails/x11appjail-nanonote-15000_default/jail";
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] }
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] Inspecting config.conf:
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] appjail_version: 4.11.1.20260425+883316ed118b827109fb53a159e859448a3e5851
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] birth: 1777489193
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] jail_type: thick
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] release_name: default
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] osarch: amd64
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] osversion: 15.1-PRERELEASE
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] container: 1
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] container_image: localhost/nanonote:latest
[00:00:18] [ debug ] [x11appjail-nanonote-15000_default] Creating...
[00:00:00] [ debug ] if_bridge kernel module already loaded.
[00:00:00] [ debug ] bridgestp kernel module already loaded.
ajnet
[00:00:00] [ debug ] ajnet: the bridge has been created.
[00:00:00] [ debug ] Setting parameters: address:192.168.16.1 netmask:255.255.255.0 broadcast:192.168.16.255 descr:AppJail network
[00:00:00] [ debug ] Adding appjail_bridge group to ajnet ...
[00:00:00] [ debug ] Renaming epair epair0[ab] to e[ab]_d778686ad50 ...
ea_d778686ad50
eb_d778686ad50
[00:00:00] [ debug ] Setting MTU (1500) to e[ab]_d778686ad50 ...
[00:00:00] [ debug ] Marking e[ab]_d778686ad50 "up"
[00:00:00] [ debug ] Adding appjail_epair group to ea_d778686ad50
[00:00:00] [ debug ] Adding ea_d778686ad50 as a member of ajnet ...
[00:00:00] [ debug ] Done.
x11appjail-nanonote-15000_default: created
ifconfig_eb_d778686ad50: -> inet 192.168.16.3 netmask 255.255.255.0 broadcast 192.168.16.255
add net default: gateway 192.168.16.1
defaultrouter: NO -> 192.168.16.1
[00:00:22] [ debug ] [x11appjail-nanonote-15000_default] Converting x11appjail-nanonote-15000_default to an ephemeral jail ...
[00:00:22] [ debug ] [x11appjail-nanonote-15000_default] Done.
syslogd_flags: -s -> -ss
sendmail_enable: NONE -> NO
sendmail_submit_enable: YES -> NO
sendmail_outbound_enable: YES -> NO
sendmail_msp_queue_enable: YES -> NO
cron_flags: -> -J 60
Stopping syslogd.
Waiting for PIDS: 40976.
Starting syslogd.
Stopping cron.
Waiting for PIDS: 95066.
Starting cron.
[00:00:28] [ debug ] [x11appjail-nanonote-15000_default] initscript generated:
[00:00:28] [ debug ] [x11appjail-nanonote-15000_default] set -T
[00:00:28] [ debug ] [x11appjail-nanonote-15000_default]
[00:00:28] [ debug ] [x11appjail-nanonote-15000_default] . "${APPJAIL_CONFIG}"
[00:00:28] [ debug ] [x11appjail-nanonote-15000_default] . "${LIBDIR}/load"
[00:00:28] [ debug ] [x11appjail-nanonote-15000_default]
[00:00:28] [ debug ] [x11appjail-nanonote-15000_default] lib_load "${LIBDIR}/sysexits"
[00:00:28] [ debug ] [x11appjail-nanonote-15000_default] lib_load "${LIBDIR}/atexit"
[00:00:28] [ debug ] [x11appjail-nanonote-15000_default] lib_load "${LIBDIR}/log"
[00:00:28] [ debug ] [x11appjail-nanonote-15000_default] lib_load "${LIBDIR}/check_func"
[00:00:28] [ debug ] [x11appjail-nanonote-15000_default]
[00:00:28] [ debug ] [x11appjail-nanonote-15000_default] lib_atexit_init
[00:00:28] [ debug ] [x11appjail-nanonote-15000_default]
[00:00:28] [ debug ] [x11appjail-nanonote-15000_default] trap '' SIGINT
[00:00:00] [ debug ] [x11appjail-nanonote-15000_default] x11 parameters: assign_only
noroot@sys-x11:~/Containerfile.d/nanonote $ appjail jail list -j x11appjail-nanonote-15000_default
STATUS NAME TYPE VERSION PORTS NETWORK_IP4
UP x11appjail-nanonote-15000_default thick 15.1-PRERELEASE - 192.168.16.3
Now it's time to run Nanonote from Xpra. We'll use xfce4-terminal as the launcher and to manage the x11appjail services. You can run xpra start and then env DISPLAY=:0 ~/x11appjail/apps/x11appjail-nanonote-15000_default/START, from our ssh session, but the environment created by Xpra isn't inherited, which is necessary for notifications since they use D-Bus.
~/bin/start-sys-x11.sh:
#!/bin/sh
exec xpra start \
--start-child=xfce4-terminal \
--exit-with-children=yes \
--terminate-children=yes \
--border=blue \
--ssh=ssh \
--pulseaudio=no \
--idle-timeout=0 \
--server-idle-timeout=0 \
--clipboard=yes \
--pulseaudio=no \
--speaker=disabled \
--microphone=disabled \
--webcam=no \
--notifications=yes \
--forward-xdg-open=off \
--printing=no \
--system-tray=no \
ssh://noroot@sys-x11/0
This script is stored on the server, not on the VM. Mark it as executable, execute it, and then...
On the xfce4-terminal terminal (and if you prefer, using tmux) run Nanonote.
dex ~/.local/share/applications/default-com.agateau.nanonote.desktop
Done. Now it's time to take the next step and run Firefox.
The process is the same as with Nanonote or any other application: we build the OCI image and then run the x11appjail application. However, with Firefox, we need to configure it to connect to sndio listening on sys-sound.
noroot@sys-x11:~/Containerfile.d/nanonote $ cd ../firefox/
noroot@sys-x11:~/Containerfile.d/firefox $ cat build.sh
#!/bin/sh
exec buildah build --network=host --tag=firefox
noroot@sys-x11:~/Containerfile.d/firefox $ cat Containerfile
FROM x11appjail
RUN pkg install -U firefox && \
pkg clean -a && \
mv /usr/local/bin/firefox /usr/local/bin/_firefox && \
echo "#!/bin/sh" > /usr/local/bin/firefox && \
echo "exec /usr/bin/env AUDIODEVICE=snd@10.0.0.63/0 /usr/local/bin/_firefox" >> /usr/local/bin/firefox && \
chmod 555 /usr/local/bin/firefox
noroot@sys-x11:~/Containerfile.d/firefox $ doas ./build.sh
...
noroot@sys-x11:~/Containerfile.d/firefox $ doas buildah images firefox
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/firefox latest 28adda2b9d2c 42 hours ago 5.55 GB
In the previous example, we are running Firefox in a wrapper to set the AUDIODEVICE environment variable. There is no need to install sndio, since Firefox already includes it (at least by default).
~/.config/pet/snippet.toml:
[[Snippets]]
Description = "Install Firefox"
Output = ""
Tag = ["firefox"]
command = """\
bin install github.com/DtxdF/x11appjail --force --name 'firefox-amd64.appscript' && \
X11APPJAIL_INSTALL=1 \
X11APPJAIL_ALLOW_HOST=1 \
X11APPJAIL_VIRTUALNET=ajnet \
X11APPJAIL_LABEL0=\"security-group:1\" \
X11APPJAIL_LABEL1=\"security-group.tables.allow-dns:allow-dns\" \
X11APPJAIL_LABEL2=\"security-group.rules.allow-sys-sound:pass on ajnet proto tcp from %i to 10.0.0.63/32 port 11025\" \
X11APPJAIL_OCI_FROM=\"localhost/firefox:latest\" \
X11APPJAIL_NO_EPHEMERAL=1 \
${HOME}/bin/firefox.appscript \
"""
Unlike Nanonote, here we specify the labels used by the security-group hooks to allow access to my DNS server and sndio. The process for running Firefox is the same as in Nanonote.
dex ~/.local/share/applications/default-firefox.desktop
Notes about audio
PulseAudio:
Beyond the main topic of this article, which doesn't focus on audio, it's important to note that, in FreeBSD, some applications may rely on /dev/dsp*, while others may use a more abstract method to access audio resources. For example, through PulseAudio. We can configure PulseAudio to use sndio.
$ mkdir -p ~/.config/pulse
$ cp /usr/local/etc/pulse/default.pa ~/.config/pulse/default.pa
$ pulseaudio -k
...
$ $EDITOR ~/.config/pulse/default.pa
$ cat ~/.config/pulse/default.pa
...
load-module module-sndio device=snd@10.0.0.63/0 rate=48000 channels=2 format=s16
set-default-sink sndio-sink
...
ALSA:
We need to configure ALSA to use PulseAudio (and, therefore, to use sndio). First, enable the PULSEAUDIO option in audio/alsa-plugins, build it, and then (re)install it. Next, add the following to your ~/.asoundrc file:
pcm.!default {
type pulse
fallback "sysdefault"
}
ctl.!default {
type pulse
}
OpenAL-Soft:
Apps like Telegram use OpenAL-Soft, so you need to enable the SNDIO option in audio/openal-soft.
Note about audio notes:
As you may have noticed, these notes are agnostic to the main topic of this article. The idea is that, since you already know how to do this in a typical scenario, you can easily do the same thing in an OCI image.
Notes about notifications
This is no different from running the notification service on the host, except that you need to start the service from xfce4-terminal (and, if you want, via tmux). As you can see, s6 is already installed, so follow the steps outlined in the x11appjail's README 10 and you’ll be able to receive notifications.
Afterword
I don’t think the above approach could withstand an attack by a nation-state, but it will provide a good level of security in most cases. Unlike QubeOS, I rely on TCP/IP, which may be inferior to Xen/vchains in terms of security, but it allows us to use tools like Ansible.
At the very least, this experiment has been fun, and I hope future readers will use what I’ve implemented here to create a more robust and straightforward way to run GUI applications in isolated environments.
-
https://theinvisiblethings.blogspot.com/2008/09/three-approaches-to-computer-security.html ↩
-
https://theinvisiblethings.blogspot.com/2011/04/linux-security-circus-on-gui-isolation.html ↩
-
https://github.com/freebsd/freebsd-ports/compare/main...shkhln:freebsd-ports:chromium-sandbox ↩
-
https://appjail.readthedocs.io/en/latest/x11/#sandboxed-x11-applications ↩
-
https://www.freebsd.org/security/advisories/FreeBSD-SA-24:17.bhyve.asc ↩
-
https://freebsd.uw.cz/2025/12/freebsd-high-performance-network-stack.html ↩
-
https://lists.freebsd.org/archives/dev-commits-src-all/2026-February/069014.html ↩
-
https://github.com/DtxdF/x11appjail#receiving-notifications ↩