Caddy named every span ipx-web, so Tempo's lists and trace view showed the same name for each request. Caddy 2.11 takes placeholders in the span name: now the method and path, as ipx names its own. The prod check's span list shows a span's url.path when it has no route. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
143 lines
4.6 KiB
Caddyfile
143 lines
4.6 KiB
Caddyfile
# ipx-web (#203): the frontend as static files, and everything else passed to ipx, on one origin,
|
|
# so the session cookie, live updates and Cloudflare Access work as they did when ipx served it.
|
|
# IPX_UPSTREAM where ipx is, host:port (ipx:8099)
|
|
# WEB_BIND where to listen (:8080)
|
|
# WEB_TRACING on: a span for each request to OpenTelemetry (OTEL_EXPORTER_OTLP_ENDPOINT,
|
|
# OTEL_EXPORTER_OTLP_PROTOCOL, OTEL_SERVICE_NAME), continuing the page's traceparent
|
|
# and passing it to ipx, so one trace runs browser, Caddy, ipx (#213). Off: none,
|
|
# and no export failing every few seconds where there is nothing to send to.
|
|
# TUNNEL_PEER the one address whose Cf-Access-* headers are passed on: where cloudflared's
|
|
# requests come from. Nobody else's are, so nobody else can claim to be signed in
|
|
# by Cloudflare. Unset passes none.
|
|
{
|
|
admin off
|
|
auto_https off
|
|
# Everything else Caddy logs, its errors among them, which carry the request too: the same
|
|
# filter as the access log's below.
|
|
log default {
|
|
output stderr
|
|
format filter {
|
|
wrap json
|
|
fields {
|
|
request>uri query {
|
|
delete token
|
|
}
|
|
request>headers delete
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
(tracing-on) {
|
|
tracing {
|
|
# Named for the request, as ipx names its own (#286): one name for all left every span in
|
|
# Tempo looking the same.
|
|
span "{http.request.method} {http.request.uri.path}"
|
|
}
|
|
}
|
|
(tracing-off) {
|
|
}
|
|
|
|
http://{$WEB_BIND::8080} {
|
|
import tracing-{$WEB_TRACING:off}
|
|
|
|
root * {$WEB_ROOT:/srv}
|
|
|
|
# One JSON line a request on stdout, for Loki and the dashboard's Page row (#210). Not the
|
|
# shared token a ?token= link carries, nor the request's headers, which hold the session
|
|
# cookie and Cloudflare Access's token.
|
|
log {
|
|
output stdout
|
|
format filter {
|
|
wrap json
|
|
fields {
|
|
request>uri query {
|
|
delete token
|
|
}
|
|
request>headers delete
|
|
}
|
|
}
|
|
}
|
|
|
|
# Compressed, zstd or gzip as the browser takes them: the feed list and a page of items went
|
|
# out at over 100 KB each (#239). Not the live updates, which a compressor would hold back
|
|
# until it had enough to send, nor /media, which is audio and video already compressed.
|
|
@compress not path /api/events /media/*
|
|
encode @compress zstd gzip
|
|
|
|
# The copies web/build.mjs compressed are served in place of their file, never asked for by
|
|
# name: /admin.js.br would be the admin page's script without the check below that keeps it to
|
|
# admins (#253).
|
|
# A handle of its own: a bare respond runs after the handles, and the last one serves any file.
|
|
@packed path *.br *.gz
|
|
handle @packed {
|
|
respond 404
|
|
}
|
|
|
|
# Before anything else, so no page or API call sees them: only ipx sets these.
|
|
@forged not remote_ip {$TUNNEL_PEER:255.255.255.255}
|
|
request_header @forged -Cf-Access-Authenticated-User-Email
|
|
request_header @forged -Cf-Access-Jwt-Assertion
|
|
|
|
@api path /api/* /media/*
|
|
handle @api {
|
|
# Live updates are a stream: flushed as they come, never buffered.
|
|
reverse_proxy {$IPX_UPSTREAM:ipx:8099} {
|
|
flush_interval -1
|
|
}
|
|
}
|
|
|
|
# Signing in with the shared token in the address (?token=): ipx sets the session cookie and
|
|
# sends the browser on to the page without it.
|
|
@tokened {
|
|
path / /admin
|
|
query token=*
|
|
}
|
|
handle @tokened {
|
|
rewrite * /api/signin?{query}
|
|
reverse_proxy {$IPX_UPSTREAM:ipx:8099}
|
|
}
|
|
|
|
# The app and the admin page go to whoever ipx says may have them: a browser not signed in is
|
|
# sent to /login, anyone but an admin asking for /admin to the app.
|
|
@page path / /index.html /admin /admin.html /admin.js
|
|
handle @page {
|
|
forward_auth {$IPX_UPSTREAM:ipx:8099} {
|
|
uri /api/page-check
|
|
}
|
|
rewrite / /index.html
|
|
rewrite /admin /admin.html
|
|
header Cache-Control no-cache
|
|
file_server {
|
|
precompressed br gzip
|
|
}
|
|
}
|
|
|
|
# The sign-in form, except for someone Cloudflare Access vouches for, who is sent to the app.
|
|
@login path /login /login.html
|
|
handle @login {
|
|
forward_auth {$IPX_UPSTREAM:ipx:8099} {
|
|
uri /api/login-check
|
|
}
|
|
rewrite * /login.html
|
|
header Cache-Control no-cache
|
|
file_server {
|
|
precompressed br gzip
|
|
}
|
|
}
|
|
|
|
# Scripts and the stylesheet are named by a hash of their contents (web/build.mjs), so a browser
|
|
# keeps them a year; the icons a day, under names that do not change.
|
|
@hashed path /app.js /admin.js /login.js /app.css /hls.js /boot.js /icons/index.json
|
|
header @hashed Cache-Control "public, max-age=31536000, immutable"
|
|
# The same PNG as /favicon.png, for a browser that asks for /favicon.ico on its own.
|
|
header /favicon.ico Content-Type image/png
|
|
@icons path *.png *.svg *.ico *.woff2
|
|
header @icons Cache-Control "public, max-age=86400"
|
|
handle {
|
|
file_server {
|
|
precompressed br gzip
|
|
}
|
|
}
|
|
}
|