Family Tree app

We document the installation of the self-hosted family tree app running at familytree.piribauer.ch. The source is available at git.piribauer.ch/familytree.

The app is built with FastAPI and stores its data in a SQLite database. Authentication is handled entirely by an Authentik outpost via nginx forward-auth; the app reads the X-Authentik-Username and X-Authentik-Email headers injected by nginx and never touches the Authentik API directly.

We assume that nginx and an Authentik outpost are already running, and that the repo has been cloned to /home/julian/familytree.

Python environment

The system Python on Arch Linux is PEP 668-managed and rejects direct pip install calls. We therefore use a virtual environment:

$ python -m venv /home/julian/familytree/venv
$ /home/julian/familytree/venv/bin/pip install -r /home/julian/familytree/requirements.txt

Database directory

The app stores its data in a SQLite database under /var/lib/familytree/. We create the directory and assign ownership to the service user:

# mkdir -p /var/lib/familytree
# chown julian:julian /var/lib/familytree

Environment file

The app reads its configuration from /home/julian/familytree/.env. We create it with the database URL:

DATABASE_URL=sqlite:////var/lib/familytree/familytree.db

Systemd service

We install the unit file included in the repo:

# cp /home/julian/familytree/familytree.service /etc/systemd/system/familytree.service
# systemctl daemon-reload
# systemctl enable --now familytree

The service runs as user julian, listening on 127.0.0.1:8766. We can verify it started cleanly with

$ systemctl status familytree

Nginx

We copy the nginx config from the repo and enable it:

# cp /home/julian/familytree/nginx/family /etc/nginx/sites-available/family
# ln -s /etc/nginx/sites-available/family /etc/nginx/sites-enabled/family
# nginx -t && systemctl reload nginx

The config forwards all requests through the Authentik outpost running on 127.0.0.1:9000. On success, nginx extracts the username and email from the outpost response and passes them to the app:

auth_request_set $authentik_username  $upstream_http_x_authentik_username;
auth_request_set $authentik_email     $upstream_http_x_authentik_email;

proxy_set_header  X-Authentik-Username $authentik_username;
proxy_set_header  X-Authentik-Email    $authentik_email;

Unauthenticated requests are redirected to the Authentik login page.

Schema migrations

SQLAlchemy creates missing tables on startup with create_all, but does not modify tables that already exist. If a column is added to the models, it must be applied to the existing database manually. For example, adding a notes column to the persons table:

$ sqlite3 /var/lib/familytree/familytree.db "ALTER TABLE persons ADD COLUMN notes TEXT;"

Invitation flow

Users are invited by email address from within the app. The invitation is stored as a pending record linked to the tree. When the invited user logs in for the first time, their Authentik email is matched against all pending invitations and the corresponding tree memberships are created automatically. Trees have three roles: owner, editor, and viewer. Only owners can invite others or change roles.