Files
homelab-ansible-lxc-dawarich/README.md
T
Your Name e7b8d4df17 initial commit: Dawarich LXC role (CT 459 on pve02, .159)
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>
2026-06-01 21:24:09 -04:00

8.3 KiB

homelab-ansible-lxc-dawarich

Configuration for dawarich (LXC 459 on pve02, 192.168.1.159) — self-hosted location-history app, Dawarich. 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 provisionedmodule.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:
    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 mappinghomelab-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

./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:
    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).