# homelab-ansible-lxc-dawarich Configuration for **dawarich** (LXC 459 on pve02, `192.168.1.159`) — self-hosted location-history app, [Dawarich](https://dawarich.app). Ingests location data from OwnTracks, the Dawarich iOS/Android apps, and Google Takeout exports; renders heatmaps, places visited, distance/time statistics, and timeline view. - **URL:** `dawarich.balders.ca` (Caddy → `192.168.1.159:3000`, Authentik OIDC) - **Image:** `freikin/dawarich:1.7.11` (pinned in `vars/main.yml`) — **must be ≥ 1.7.8** for OIDC. - **Tier:** T2 (Proxmox) - **Auth:** Authentik OIDC (SSO-only — `ALLOW_EMAIL_PASSWORD_REGISTRATION=false`) - **DB:** PostGIS 16-3.4 **local to this LXC**. The central DB VM (.172) runs `postgres:16-alpine` which doesn't bundle the PostGIS extension; rather than swap that image (16 other databases) we run a colocated container here. See memory `project_dawarich` for the convention-exception rationale. ## Architecture ``` Phone (OwnTracks / Dawarich app) │ HTTP POST /api/v1/owntracks/points?api_key=… ▼ Caddy (TLS, Authentik OIDC on UI; API bypassed by query-string auth) │ ▼ dawarich_app (Rails 8 + Puma) ──▶ dawarich_db (PostGIS 16-3.4) │ ▲ ▼ │ dawarich_sidekiq ─── enqueue ─────────── dawarich_redis └──── solid_queue ──▶ /dawarich_db_data (SQLite) ``` ## Roles | Role | Purpose | |------------|---------| | `dawarich` | System setup, Docker, registry mirror, 4-container compose, prune timer | | `alloy` | Docker logs + journald → Loki, node metrics → Prometheus | ## Prerequisites (before first deploy) 1. **LXC provisioned** — `module.dawarich` in homelab-terraform (CTID 459, pve02, `.159`). `tf.sh apply lxc` then bootstrap the cbalders user manually on the new CT (the bpg module only seeds root keys; cbalders + NOPASSWD sudo is post-create). See `feedback_lxc_bootstrap_user`. 2. **`vars/vault.yml`** — holds `vault_infisical_client_secret`. Shared universal-auth client across all LXC repos — copy from `homelab-ansible-lxc-rag/vars/vault.yml`. 3. **Infisical `/dawarich` folder + 5 secrets** — run `./scripts/bootstrap-secrets.sh`. It creates the folder if missing and pushes: - `vault_dawarich_db_password` (`openssl rand -hex 32`) - `vault_dawarich_secret_key_base` (`openssl rand -hex 64`) - `vault_dawarich_otp_primary_key` (`openssl rand -hex 32`) - `vault_dawarich_otp_deterministic_key` (`openssl rand -hex 32`) - `vault_dawarich_otp_salt` (`openssl rand -hex 32`) 4. **Infisical `/oidc` secrets** — generate `vault_dawarich_oidc_client_id` (40 hex) and `vault_dawarich_oidc_client_secret` (64 hex), push as `--type shared`. These values ARE the credentials — pi-auth creates the Authentik provider with them, it doesn't fetch them back. Generation pattern: ```bash infisical secrets set "vault_dawarich_oidc_client_id=$(openssl rand -hex 20)" \ --projectId 50062d7c-06ff-4d5c-8ca3-6c0cdba9f270 --env prod --path /oidc --type shared infisical secrets set "vault_dawarich_oidc_client_secret=$(openssl rand -hex 32)" \ --projectId 50062d7c-06ff-4d5c-8ca3-6c0cdba9f270 --env prod --path /oidc --type shared ``` 5. **pi-auth site.yml mapping** — `homelab-ansible-pi-auth/site.yml` must have a `dawarich_oidc_client_id` / `dawarich_oidc_client_secret` set_fact line reading from `oidc_secrets.secrets.vault_dawarich_oidc_client_{id,secret}`. Without this, pi-auth's loop fails with `'dawarich_oidc_client_id' is undefined`. ## Deploy ```bash ./deploy.sh # full deploy ./deploy.sh --tags dawarich # dawarich role only ./logs.sh -f # follow dawarich_app ./logs.sh dawarich_sidekiq -f # follow background jobs ``` ## Bring-up order (proven 2026-06-01) 1. `cd ../homelab-terraform && ./tf.sh apply lxc` → CT 459 created. 2. Bootstrap `cbalders` user + SSH keys + NOPASSWD sudo on the new CT: ```bash ssh root@pve02 'pct exec 459 -- bash -c " useradd -m -s /bin/bash -G sudo cbalders && mkdir -p /home/cbalders/.ssh && cat > /home/cbalders/.ssh/authorized_keys < EOF chown -R cbalders:cbalders /home/cbalders/.ssh && chmod 700 /home/cbalders/.ssh && chmod 600 /home/cbalders/.ssh/authorized_keys && apt-get update -qq && apt-get install -y -qq sudo && mkdir -p /etc/sudoers.d && echo \"cbalders ALL=(ALL) NOPASSWD:ALL\" > /etc/sudoers.d/cbalders && chmod 440 /etc/sudoers.d/cbalders"' ``` 3. `./scripts/bootstrap-secrets.sh` → 5 `/dawarich` secrets in Infisical. 4. Push `/oidc` secrets (see prereq 4 above). 5. Add `dawarich_oidc_client_{id,secret}` set_fact mapping in `homelab-ansible-pi-auth/site.yml`. 6. First `./deploy.sh` here → app boots, migrations run (~3 min cold-start). 7. `cd ../homelab-ansible-pi-auth && ./deploy.sh` → creates Authentik provider + application. 8. `cd ../homelab-ansible-proxy && ./deploy.sh` → adds Caddy vhost + Technitium CNAME. 9. `cd ../homelab-ansible-pve && ./deploy.sh` → adds CTID 459 to `pbs-prod-daily`. 10. Browser-test `https://dawarich.balders.ca/users/sign_in` → click "Sign in with Authentik". ## Mobile clients After first OIDC login, the user goes to Settings → API Keys in the Dawarich UI to generate a per-user token. Use it as the bearer for either: - **OwnTracks** (any platform) — HTTP recorder mode, URL: `https://dawarich.balders.ca/api/v1/owntracks/points?api_key=` - **Dawarich iOS/Android app** — paste the token + host on first launch. ## Google Takeout import 1. Take the `location-history.json` out of the Takeout archive. 2. Drop it into `/opt/dawarich/watched/` on the LXC (NFS mount or `scp`). 3. Sidekiq picks it up via the import watcher within ~30s and emits progress to `dawarich_sidekiq` logs. Large histories (>10y) can take an hour. ## Gotchas (build-day 2026-06-01) - **Image version pin.** OIDC support landed in `freikin/dawarich:1.7.8`. The `0.27.x` line silently ignores `OIDC_*` env vars (no OmniAuth controllers in the codebase). Pinned at `1.7.11`. Bump app + sidekiq lockstep — same image tag in two containers, breaking schema migrations on minor bumps. - **Image entrypoint scripts are required.** Image ships `ENTRYPOINT ["bundle", "exec"]` with no CMD. Compose MUST set `entrypoint: ["/usr/local/bin/web-entrypoint.sh"]` (and `sidekiq-entrypoint.sh` for sidekiq). Those scripts run `db:prepare`, create the SQLite files for solid_cache/solid_queue/solid_cable at `/dawarich_db_data/`, and migrate Postgres before exec'ing the command. - **Multi-DB SQLite persistence.** The solid_cache/solid_queue/solid_cable files live at `/dawarich_db_data/` — bind-mount `/opt/dawarich/db_data:/dawarich_db_data` to keep in-flight Sidekiq jobs across recreates. - **`APPLICATION_HOSTS` must include localhost.** Rails 8 host authorization blocks loopback if only the public host is listed — kills Docker HEALTHCHECK and the Ansible health-wait. - **Rails 8 `force_ssl` + healthcheck.** `APPLICATION_PROTOCOL=https` makes Rails 301-redirect every plain-HTTP request to HTTPS. The Docker healthcheck is a TCP-socket probe (`ruby -rsocket -e 'TCPSocket.new("localhost",3000).close'`) rather than HTTP, because curl/wget either chase the redirect to TLS (Puma is plain-HTTP behind Caddy → "SSL wrong version number") or fail 2xx. The Ansible `Wait for Dawarich to be healthy` task is `ignore_errors: true` for the same reason. - **SSO button is POST, not GET.** The signin page renders a `
`; the button submits with a Rails CSRF token. `GET /users/auth/openid_connect` returns 404 — that's CSRF working, not a routing bug. ## Notes - Postgres data lives at `/opt/dawarich/postgres` (uid 70, alpine `postgres` user). Pre-chowned in the role to avoid the empty-dir trap. - Redis data at `/opt/dawarich/redis` (uid 999) — see `feedback_redis_uid_999`. - PBS LXC snapshot covers everything nightly (the postgis + db_data volumes). No central pg_backup wiring — DB is local. - Watchtower exposes its HTTP API on port 8088 inside the LXC (matches the fleet pattern; 8080 collides with potential second app on this CT).