Run 34421944328 pihole#1 (README replay, sso off) never left
/admin/login: the native login was refused. The compose template put
the administrator password inline under environment:, and compose
interpolates every $name in it, so the container held the inventory
password with each $name removed (its sha256 prefix matches that
mangled value, not the real one). The environment moves into
templates/env.j2, which has_env wires as env_file, with the password
through dotenv_quote like every other role. Rendering the real template
with a password containing $name, ${...} and $$ and reading it back from a
compose v5.5.1 container returns it unchanged, while an unquoted RAW=S$x
control line in the same file comes back as "S". TZ now comes from the
shared sys-svc-container include, so PIHOLE_TIMEZONE goes.
pihole#0 (onion) timed out on "still handing off": oauth2-proxy serves
its 403 page for a user outside the allowed group on /oauth2/callback,
and the poll checked the callback URL before the page text. The CI
trace holds "You do not have permission to access this resource." in
that page's collapsed More Info block, which textContent reads. The
poll now judges the denial text first, then the Keycloak auth URL, and
only then treats the callback URL as a handoff in progress. The loose
"403" and "forbidden" matches go, since a reached admin page can
contain either.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Under self_signed TLS the shared sso-proxy template (web-app-keycloak) now both bind-mounts the platform CA to CA_TRUST.cert_container (sso_proxy/container.yml.j2) and points provider_ca_files at it (oauth2-proxy-keycloak.cfg.j2), gated on the same condition as this task. pihole's lineinfile added a SECOND provider_ca_files (the wrapper-injected /tmp path, which is skipped for the distroless oauth2-proxy image), producing 'toml: key provider_ca_files is already defined' so the sidecar crash-looped and never converged. The task is redundant and its path less reliable than the template's explicit bind mount, so remove it and the now-unused PIHOLE_SSO_PROXY_CA_CONTAINER_PATH.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Route image/volumes/networks through the container_image, container_volumes, compose_volumes and *_networks lookups (was a raw image ref, an inline service volumes block, and the deprecated compose_volumes pipe form); add meta/volumes.yml as the volume source and drop the four now-unused PIHOLE_IMAGE/VERSION/VOLUME vars. Pi-hole sends no mail, so the email service is pinned enabled=false/shared=false (dynamic-flag nocheck) and dropped from the variant matrix.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
oauth2-proxy (quay.io/oauth2-proxy/oauth2-proxy) is a distroless image
with no /bin/sh, so the platform's CA-trust wrapper (with-ca-trust.sh,
applied by compose_ca.py for services with a shell) is skipped for this
container. Go binaries ignore SSL_CERT_FILE/REQUESTS_CA_BUNDLE and use
only the OS system cert pool, so on self-signed TLS setups oauth2-proxy
fails OIDC discovery against Keycloak with
'x509: certificate signed by unknown authority'. nginx then falls back
to pihole's native /admin/login instead of redirecting to Keycloak,
breaking SSO entirely in CI and on any self-signed deployment.
Fix: append provider_ca_file to the rendered sso-proxy config after
web-app-keycloak/tasks/sso_proxy.yml renders it and before compose up
runs. oauth2-proxy loads this CA explicitly for the OIDC provider
connection, bypassing the system cert pool entirely.
Scoped to this role: avoids touching the shared
oauth2-proxy-keycloak.cfg.j2 template (would affect every
flavor: oauth2 role platform-wide). No-op on production
(TLS_MODE=letsencrypt).
- tasks/01_core.yml: set docker_compose_flush_handlers: true, remove manual flush_handlers task
- tasks/01_setup.yml: add wait for oauth2-proxy port to fix HTTP 500 in CI wait check
- meta/services.yml: use NETWORK_PUBLIC_DNS_RESOLVERS SPOT for upstream_dns
- vars/main.yml: remove default from upstream_dns lookup, join list with semicolon
Variable is already defined platform-wide in test-e2e-playwright/defaults/main.yml.
Redefining it in vars/main.yml violates the project-wide constant uniqueness rule.
- Update logout test to navigate directly to oauth2/sign_out endpoint with Keycloak logout redirect
- Remove duplicate oauth2-proxy service definition from compose.yml.j2 (was causing duplicate key error)
- Add TEST_E2E_PLAYWRIGHT_WAIT_ENABLED: false to vars/main.yml (oauth2-proxy returns 403 before redirect)
- Deploy Pi-hole as web app with NGINX proxy and OAuth2 protection
- Only web-app-pihole-administrators group can access the dashboard
- DNS port 53 (TCP/UDP) exposed for network-wide ad blocking
- Upstream DNS configurable per inventory (default: Cloudflare 1.1.1.1)
- Redis session store for OAuth2 proxy
- Timezone from HOST_TIMEZONE platform variable
- Port bindings use DOCKER_BIND_HOST platform variable
- Register subnet 192.168.102.240/28 in networks
- Register ports 8070 (http) and 16493 (oauth2) in ports
- Playwright tests: oauth2 protection, admin login, biber denied (3/3 passing)
Known limitations:
- Pi-hole v6 gravity blocklist requires manual setup via admin UI
- Internal *.infinito.example DNS forwarding to CoreDNS planned as follow-up