Rebuild auth on Keycloak OIDC, fix rootless Ansible deploy

Replaces the single shared HTTP Basic service-account credential (which
caused a production outage from a username mismatch) with per-user login:
Keycloak (already running on this VM for gcnm, now also fronted on
auth.friessn.de with its own "homekeeper" realm) authenticates the user
once via the landing page, FastAPI verifies the OIDC id_token and mints
its own signed session JWT as a cookie, and both Shiny apps forward that
per-session token as a Bearer credential instead of a static shared one.
Authorization is a simple ALLOWED_USERS allowlist; the old auth.users
table and bcrypt seeding are gone entirely.

Also carries forward the in-progress rootless Podman/Quadlet migration
(gitea, homekeeper, podman roles) and fixes a pre-existing bug where
each role's handlers were malformed inside tasks/main.yml instead of
their own handlers/main.yml, which broke ansible-playbook entirely.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FbiCdckkTX2HyAkyi1R39d
This commit is contained in:
friessn
2026-07-13 06:31:36 +00:00
parent 58983e6855
commit 6c9cd67a08
36 changed files with 579 additions and 245 deletions
+53 -10
View File
@@ -2,28 +2,71 @@
# Main domain (apps + landing page)
domain: home.friessn.de
# Everything runs rootless — one Linux user owns all Podman Quadlets
# (systemd --user units under ~/.config/containers/systemd), started via
# `loginctl enable-linger` so they survive logout/reboot without root.
deploy_user: nico
deploy_uid: 1000
# Gitea on a subdomain — cleaner than a subpath, required for container registry
gitea_domain: git.friessn.de
gitea_data_dir: /opt/gitea
gitea_data_volume: gitea-data # named Podman volume, not a bind mount (avoids rootless UID mapping issues)
gitea_http_port: 3000 # internal port, nginx proxies HTTPS → here
gitea_admin_user: nico
gitea_ssh_port: 2222 # published directly (nginx can't proxy SSH)
gitea_admin_user: friessn # actual Gitea account created during the install wizard
gitea_admin_password: CHANGE_ME # replace — set via ansible-vault in production
gitea_admin_email: nico.friess@googlemail.com
# Container registry — Gitea's built-in OCI registry, same host as Gitea
# Images: git.friessn.de/nico/homekeeper-api:latest
# Container registry — Gitea's built-in OCI registry, same host as Gitea, over HTTPS (443)
# Images: git.friessn.de/friessn/homekeeper-api:latest
registry_host: "{{ gitea_domain }}"
registry_user: "{{ gitea_admin_user }}"
registry_token: CHANGE_ME # Gitea API token with package:write — set after first Gitea start
registry_token: !vault |
$ANSIBLE_VAULT;1.1;AES256
33626439663535643963613436373132336333633839613965396539653834313831383039323961
3339613430323237636532663665313331303735623137660a613032306362343435633034383965
61383131393939393561366335376339343432636431663033363036346337633865363561303238
3339313363656137380a663264346666346166663362656638343838643333626438646162643835
39656564316130383766656364303035343061653164356664643433336531363334396530376432
3162326539326134323639363661363165323632383932623434
# Homekeeper app
homekeeper_data_dir: /opt/homekeeper
homekeeper_db_volume: homekeeper-db-data # named Podman volume
db_name: homestead
db_user: homestead
db_password: CHANGE_ME # replace — set via ansible-vault in production
api_user: homestead
api_pass: CHANGE_ME # replace
initial_users: "nico:CHANGE_ME" # user:password pairs for API auth
# Auto-update timer interval (OnCalendar syntax)
# Auth — Keycloak (already running on this VM for the unrelated gcnm app,
# published on 127.0.0.1:8080) acts as OIDC identity provider, exposed here
# on its own subdomain with its own realm so it's cleanly separated from
# gcnm's realm. FastAPI verifies the Keycloak login once via the "homekeeper"
# realm, then mints its own session JWT — see api/app/auth.py.
# NOTE: the Keycloak container itself is not managed by this repo/role — only
# the nginx site + the "homekeeper" realm/client (created manually in the
# Keycloak admin console) belong to this setup.
keycloak_domain: auth.friessn.de
keycloak_realm: homekeeper
# This Keycloak instance serves under a /auth path prefix (KC_HTTP_RELATIVE_PATH=/auth
# in gcnm's docker-compose setup) — not at root.
oidc_issuer_url: "https://{{ keycloak_domain }}/auth/realms/{{ keycloak_realm }}"
oidc_client_id: homekeeper
oidc_client_secret: !vault |
$ANSIBLE_VAULT;1.1;AES256
66323965333830653838356233623461323835303663353530396166653330303130616231323262
3233626366633532643434326338313835646533343934640a333037646138393531613730616633
64653330623639376164353033663061636436323465646662333934306337353765343739313561
3631333636643965370a663464333430306539396164363466633636643466383461636463626631
30343732363664356165363565393565346439646235313637346164376265326635613132663132
6232663636656233653466393338333532383764616633653234
session_jwt_secret: !vault |
$ANSIBLE_VAULT;1.1;AES256
64613365363266313061396438313965646462313630636631353236353032383532376631303138
6663626664346538363464386333383136306165343163320a653763633839353637613136333464
62373231363664633935373632346161613865623930613436306661323439656462323538623864
6136343462376436320a666463346530653062323431626135666665373135393262633766353264
65303164373366393635653964316664633031313536383965353330343936623532
allowed_users: "nico" # comma-separated Keycloak usernames allowed to log in
# Auto-update timer interval (OnCalendar syntax) — rootless uses podman-auto-update.timer
# in the user systemd instance, same schedule override mechanism as the system one.
autoupdate_schedule: "*:*:0/10" # every 10 seconds