tech_notes

A collection of notes on setups and implementations.
Log | Files | Refs | README

commit 06ecc88efcb24f743d2cb45cdd7c38a4a405f24a
parent de66c3d5a7947dae6a5643a69c36de1c75506093
Author: Julian Piribauer <julian.piribauer@gmail.com>
Date:   Sat,  2 May 2026 06:50:35 +0000

Add calendar app installation guide

Diffstat:
Acalendar.md | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 94 insertions(+), 0 deletions(-)

diff --git a/calendar.md b/calendar.md @@ -0,0 +1,94 @@ +# Calendar app + +We document the installation of the self-hosted calendar app running at [calendar.piribauer.ch](https://calendar.piribauer.ch). +The source is available at [git.piribauer.ch/calendar](https://git.piribauer.ch/calendar). + +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/calendar`. + +## Python environment + +The system Python on Arch Linux is PEP 668-managed and rejects direct `pip install` calls. +We therefore use a virtual environment: + +```console +$ python -m venv /home/julian/calendar/venv +$ /home/julian/calendar/venv/bin/pip install -r /home/julian/calendar/requirements.txt +``` + +## Database directory + +The app stores its data in a SQLite database under `/var/lib/calendar/`. +We create the directory and assign ownership to the service user: + +```console +# mkdir -p /var/lib/calendar +# chown julian:julian /var/lib/calendar +``` + +## Environment file + +The app reads its configuration from `/home/julian/calendar/.env`. +We create it with the database URL: + +```bash +DATABASE_URL=sqlite:////var/lib/calendar/calendar.db +``` + +## Systemd service + +We install the unit file included in the repo: + +```console +# cp /home/julian/calendar/calendar.service /etc/systemd/system/calendar.service +# systemctl daemon-reload +# systemctl enable --now calendar +``` + +The service runs as user `julian`, listening on `127.0.0.1:8765`. +We can verify it started cleanly with + +```console +$ systemctl status calendar +``` + +## Nginx + +We copy the nginx config from the repo and enable it: + +```console +# cp /home/julian/calendar/nginx/calendar /etc/nginx/sites-available/calendar +# ln -s /etc/nginx/sites-available/calendar /etc/nginx/sites-enabled/calendar +# 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: + +```nginx +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 the `description` column to the `events` table: + +```console +$ sqlite3 /var/lib/calendar/calendar.db "ALTER TABLE events ADD COLUMN description TEXT;" +``` + +## Invitation flow + +Users are invited by email address from within the app. +The invitation is stored as a pending record linked to the calendar. +When the invited user logs in for the first time, their Authentik email is matched against all pending invitations and the corresponding calendar memberships are created automatically.