Tommi Mäklin

Postdoctoral researcher | University of Helsinki

How to cleanly add services on OpenBSD

Published October 22, 2023

Adapted from, this post documents how to add daemons that run services on OpenBSD in a way that I find cleaner and easier to maintain than what I used to before. I’m writing this for future-me to use as reference and credit for the setup goes to Joel Carnat.

The commands in this post were originally tested on OpenBSD 7.4, which was released on 16 October 2023.

Setting up daemon users

Set up a new daemon user called _service:

useradd -g =uid -c "I am a service that does stuff." -L daemon -s /sbin/nologin -d /home/service -m _service

We’ll store the service in their homedir /home/service (don’t pollute /var, it’s meant for logging, temporaries & transients, and spool files), so the -m option needs to be given to create it.

Installing a service as the daemon user


doas -u _service /bin/ksh -l

to log in as the daemon user. The above will work even though we disabled logging in with the _service user with the -s /sbin/nologin flag.

Python services (optional)

Services that have many python dependencies are annoying because of bloating the installed packages. Luckily these can be handed nicely with venvs strategically placed in the /home/service directory. We’ll use the server back-end of the end-to-end encrypted CalDAV etesync as an example.

Assuming the _etebase was created as instructed above, we’ll install the etebase backend in /home/etebase:

doas -u _etebase /bin/ksh -l

git clone ~/src

Create a virtual environment in the service home directory and make the daemon automatically activate it:

python3 -m venv ~/pyenv
echo ". ~/pyenv/bin/activate" >> ~/.profile

Now let’s install the server:

doas -u _etebase /bin/ksh -l

cd ~/src
pip install -r requirements.txt

Configuration files for git-based installations

Since etebase is installed and updated through git, we’ll need to place the configuration file and secrets in $HOME to avoid them being overridden during updates:

doas -u _etebase /bin/ksh -l

sed -i 's/"secret.txt"/"..\/secrets.txt"/g' etebase_server/
sed -i 's/"db.sqlite3"/"..\/db.sqlite3"/g' etebase_server/
sed -i 's/ALLOWED_HOSTS = [[][]]/ALLOWED_HOSTS = [[] \"\" []]/g' etebase_server/

Setup the server with:

./ migrate

and you’re ready to go.

Setting up a daemon

Create the daemon script in /etc/rc.d/etebase:


daemon_flags="etebase_server.asgi:application --host --port 8000"

. /etc/rc.d/rc.subr


pexp="/home/etebase/pyenv/bin/python3 ${daemon} ${daemon_flags}"

rc_start() {
        rc_exec ". ~/.profile; ${daemon} ${daemon_flags} >> ${daemon_logfile} 2>&1"

rc_cmd $1

Change permissions to r-xr-xr-x:

chmod 0555 /etc/rc.d/searxng

Enable the daemon and that’s it:

rcctl -d enable searxng
rcctl -d start searxng

Updating the service

Create a update script in /home/service/update:

doas -u _service /bin/ksh -l

vi ~/update
cd ~/src
git fetch origin "HEAD"
git reset --hard "origin/HEAD"

for Python based services also add the following:

pip install -U pip
pip install -U setuptools
pip install -U wheel
pip install -U pyyaml
pip install -U -e .

Change permissions to something sensible:

chmod 0750 ~/update

… and create a cronjob that runs the update:

doas -u _service ksh -l ~_searxng/update

diff -U2 ./src/searx/settings.yml settings.yml

rcctl restart service

that’s all.