e7b8d4df17
Self-hosted location history. 4-container compose: Rails 8 app + Sidekiq + PostGIS 16-3.4 + Redis 7, plus watchtower. Authentik OIDC end-to-end. Image pinned at freikin/dawarich:1.7.11 (OIDC support requires >= 1.7.8). PostGIS DB lives in this LXC, not on the central DB VM (.172) — central image is postgres:16-alpine without postgis, swapping it carries broader blast radius than colocating here. Convention exception captured in homelab-docs project_dawarich memory. Roles: - dawarich: system + Docker + compose + weekly prune timer - alloy: logs+journald → Loki, node metrics → Prometheus Bring-up sequence proven 2026-06-01. README documents the 5-trap build chain (image version, entrypoint scripts, solid_cache SQLite bind mount, APPLICATION_HOSTS+localhost, force_ssl+healthcheck). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
153 lines
8.3 KiB
Markdown
153 lines
8.3 KiB
Markdown
# 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
|
|
<your-keys-here>
|
|
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=<TOKEN>`
|
|
- **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 `<form method="post" action="/users/auth/openid_connect">`; 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).
|