Setting up IoTs ("S" for security) at home, apparently HomeAssistant is the tooling to use. It's Python, so at least I have a chance of understanding what goes wrong. Which it will...

Overview
Some caveats:
- HomeAssistant provides no support for FreeBSD (we have to tell it to ignore OS)
- HomeAssistant no longer provides this style of installation, only containers are supported.
- There is a FreeBSD HomeAssistant port, but it is broken.
A bit of a faff to get this to work, but it does run. We're using the latest available Python package
NOTE: When you start HomeAssistant, it will start using uv to build additional things (e.g. habluetooth)
Jail setup
The minimal-jail pkgset wasn't enough to install, but it was enough to run. I use "thin" jails that share the base jail. This ties nicely in to the breakage of the port: it needs to compile things so you need clang.
I've ended up with the folloing jail.conf file `/etc/jail.conf.d/hass.conf
hass {
host.hostname = "hass";
path = "/jails/hass";
ip4.addr += "lo0|127.0.0.20/24";
allow.raw_sockets = 0;
exec.clean;
exec.system_user = "root";
exec.jail_user = "root";
exec.start += "/bin/sh /etc/rc";
exec.stop = "";
exec.consolelog = "/var/log/jail_hass_console.log";
mount.fstab = "/etc/fstab.hass";
mount.devfs;
devfs_ruleset = "6";
mount.fdescfs;
allow.set_hostname = 0;
allow.sysvipc = 0;
enforce_statfs = "2";
}
and in /etc/fstab.hass
/jails/basefull150 /jails/hass/basejail nullfs ro 0 0
And start the jail. Verify it is running.
Create a homeassistant user
On the host system we create a homeasisstant user ("hass" for short) without login rights or homedir. This way the process shows up under user "hass" on the host.
pw useradd hass -u 8123 -d /var/empty -s /usr/bin/nologin
In the jail, we'll give it a homedir /home/hass which we'll use for npm's storage.
jexec hass pw useradd hass -u 8123 -d /home/hass -m -s /bin/sh
Config for (temporary) internet access
pkg, git and npm need access to internet resources.
# Add to /usr/local/etc/pkg.conf
PKG_ENV {
http_proxy = "http://squid.example.org:3128";
}
git config --global http.proxy = http://squid.example.org:3128
npm config set proxy http://squid.example.org:3128
NOTE: this works once you've installed npm using pkg
To use pkg, you must allow the following in squid
- pkg.freebsd.org
- pkgmir.geo.freebsd.org
- (pkg0.fra.freebsd.org?)
- (cloudfront.aws.pkgbase.freebsd.org?)
Your squid config will have to allow the hass jail to use
- pypi.org
- files.pythonhosted.org
Your squid access logs will show what went wrong.
Install
Install required packages in the jail
Adapted from this blog-post.
pkg -r /jails/hass install python314 rust
We need to create the directory in /usr/local as hass has no permissions to do so.
install -d -o 8123 -g 8123 /jails/hass/usr/local/homeassistant
Now we switch to the hass user.
The rest of the install is as hass.
su -l hass
mkdir $HOME/bin $HOME/lib
export PATH=$PATH:$HOME/bin
Zigbee2MQTT
Another jail named zigbee, and a user zigbee.
The cheap dongle I bought shows up in dmesg like so
uchcom0 on uhub0
uchcom0: <vendor 0x1a86 USB Serial, rev 1.10/2.64, addr 1> on usbus1
uchcom0: CH340 detected
The device nodes are named ttyU*.
To allow access to the Zigbee dongle from the jail, we need a specific devfs ruleset.
$ cat /etc/devfs.rules
[devfsrules_jail_zigbee=16]
add include $devfsrules_hide_all
add include $devfsrules_unhide_basic
add include $devfsrules_unhide_login
add path 'ttyU*' unhide group 1883 mode 660
The jail configuration is internal only with the specific devfs ruleset.
zigbee {
host.hostname = "zigbee";
path = "/jails/zigbee";
ip4.addr += "lo0|127.0.0.21/24";
allow.raw_sockets = 0;
exec.clean;
exec.system_user = "root";
exec.jail_user = "root";
exec.start += "/bin/sh /etc/rc";
exec.stop = "";
exec.consolelog = "/var/log/jail_zigbee_console.log";
mount.fstab = "/etc/fstab.zigbee";
mount.devfs;
devfs_ruleset = "16";
mount.fdescfs;
mount.procfs;
allow.mount;
allow.set_hostname = 0;
allow.sysvipc = 0;
enforce_statfs = "2";
}
Create a group and user (id is the same as in devfs.rules!) and the homedir
pw -R /jails/zigbee groupadd zigbee -g 1883
pw -R /jails/zigbee useradd zigbee -u 1883 -d /usr/local/z2m -g zigbee -s /usr/sbin/nologin
install -d -o zigbee -g zigbee -m 750 /jails/zigbee/usr/local/z2m
Start the zigbee jail and open a shell as user zigbee in the jail.
Get the source (check for latest tag)
cd /usr/local
git clone --depth=1 https://github.com/Koenkk/zigbee2mqtt.git z2m
cd z2m
git remote set-branches --add origin 2.8.0
git fetch origin 2.8.0:2.8.0
git checkout 2.8.0
Your zigbee2mqtt now lives in /usr/local/z2m.
For upgrading to later Zigbee2mqtt versions, add and checkout the new tag.
Configure npm, install pnpm, and install zigbee2mqtt:
# Configure npm
$ npm set prefix="$HOME"
$ npm config set proxy http://squid.example.org:3128
# Install pnpm
$ npm install -g pnpm@latest-9
$ which pnpm
/usr/local/z2m/bin/pnpm
$ pnpm --version
9.15.9
# Install zigbee2mqtt dependencies
$ pnpm i --frozen-lockfile
# Install zigbee2mqtt
$ pnpm run build
This should finish without errors.
Create the rc script /usr/local/etc/rc.d/z2m
#!/bin/sh
# PROVIDE: z2m
# REQUIRE: DAEMON
# KEYWORD: shutdown
# FreeBSD rc.d script for zigbee2mqtt
#
# The z2m service has the following rc.conf options:
#
# z2m_enable (bool): Set to YES to enable z2m
# Default: NO
# z2m_user (str): The user to run z2m as
# Default: z2m
# z2m_group (str): The group to run z2m as
# Default: z2m
# z2m_chdir (str): The directory where z2m is installed
# Default: /usr/local/z2m
# z2m_datadir (str): The directory where z2m's data is stored
# Default: /var/db/z2m
# z2m_restart (bool): Set to YES if z2m should be automatically
# restarted after it crashes.
# Default: NO
. /etc/rc.subr
name=z2m
desc="zigbee2mqtt service"
rcvar=z2m_enable
load_rc_config $name
: ${z2m_enable:=NO}
: ${z2m_group:=zigbee}
: ${z2m_datadir:=/var/db/z2m}
: ${z2m_pidfile=/var/run/z2m/z2m.pid}
: ${z2m_restart=NO}
: ${z2m_user:=zigbee}
: ${z2m_chdir=/usr/local/z2m}
: ${z2m_env:="ZIGBEE2MQTT_DATA=${z2m_datadir}"}
# If z2m_restart is YES, then restart z2m when it crashes, otherwise
# daemon(8) will exit.
if checkyesno z2m_restart; then
_restartargs="-r"
else
_restartargs=""
fi
pidfile=${z2m_pidfile}
command=/usr/sbin/daemon
command_args="-f -H \
-P ${pidfile} -t ${name} -T ${name} \
${_restartargs} \
/usr/local/bin/node index.js"
required_files="${z2m_datadir}/configuration.yaml"
start_precmd="[ -d ${pidfile%/*} ] || install -d -o ${z2m_user} -g ${z2m_group} ${pidfile%/*}"
run_rc_command "$1"
Check and run
Inside your zigbee jail, you should see the USB dongle
/dev/ttyU0
/dev/ttyU0.init
/dev/ttyU0.lock
You can now start z2m by creating an rc-script /usr/local/etc/rc.d/z2m.
This comes from the comms/hass2mqtt port.
#!/bin/sh
# PROVIDE: z2m
# REQUIRE: DAEMON
# KEYWORD: shutdown
# FreeBSD rc.d script for hass2mqtt
#
# The z2m service has the following rc.conf options:
#
# z2m_enable (bool): Set to YES to enable z2m
# Default: NO
# z2m_user (str): The user to run z2m as
# Default: z2m
# z2m_group (str): The group to run z2m as
# Default: z2m
# z2m_chdir (str): The directory where z2m is installed
# Default: /usr/local/z2m
# z2m_datadir (str): The directory where z2m's data is stored
# Default: /var/db/z2m
# z2m_restart (bool): Set to YES if z2m should be automatically
# restarted after it crashes.
# Default: NO
. /etc/rc.subr
name=z2m
desc="hass2mqtt service"
rcvar=z2m_enable
load_rc_config $name
: ${z2m_enable:=NO}
: ${z2m_group:=hass}
: ${z2m_datadir:=/var/db/z2m}
: ${z2m_pidfile=/var/run/z2m/z2m.pid}
: ${z2m_restart=NO}
: ${z2m_user:=hass}
: ${z2m_chdir=/usr/local/z2m}
: ${z2m_env:="hass2MQTT_DATA=${z2m_datadir}"}
# If z2m_restart is YES, then restart z2m when it crashes, otherwise
# daemon(8) will exit.
if checkyesno z2m_restart; then
_restartargs="-r"
else
_restartargs=""
fi
pidfile=${z2m_pidfile}
command=/usr/sbin/daemon
command_args="-f -H \
-P ${pidfile} -t ${name} -T ${name} \
${_restartargs} \
/usr/local/bin/node index.js"
required_files="${z2m_datadir}/configuration.yaml"
start_precmd="[ -d ${pidfile%/*} ] || install -d -o ${z2m_user} -g ${z2m_group} ${pidfile%/*}"
run_rc_command "$1"
You can now enable the service
service z2m enable
and start!
service z2m start
