first commit
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
# Cashow error pages
|
||||
|
||||
Branded static error pages served by Traefik in front of the LNbits instances,
|
||||
replacing Traefik's default plain-text bodies (`no available server`,
|
||||
`Internal Server Error`).
|
||||
|
||||
```
|
||||
deploy/error-pages/
|
||||
├── README.md you are here — not deployed
|
||||
├── preview.html local review grid — not deployed
|
||||
└── pages/ ← the ConfigMap is built from this directory only
|
||||
├── 429.html
|
||||
├── 500.html
|
||||
├── 502.html
|
||||
├── 503.html
|
||||
├── 504.html
|
||||
└── onderhoud.html
|
||||
```
|
||||
|
||||
`README.md` and `preview.html` sit *outside* `pages/` on purpose: the ConfigMap
|
||||
is built with `--from-file=.`, which would otherwise sweep them both in as keys.
|
||||
|
||||
## Design constraints
|
||||
|
||||
Each page is a single self-contained HTML file making **zero external requests** —
|
||||
inline `<style>`, inline `<script>`, the logo inlined as a base64 `<image>` inside
|
||||
an `<svg>`. This is not a preference. Traefik's `errors` middleware fetches only
|
||||
the HTML document from the error service and serves that body on the *original*
|
||||
domain, so any relative subresource (`/logo.svg`, `/style.css`) would resolve
|
||||
against the LNbits instance that is currently down and 404.
|
||||
|
||||
System font stack throughout — no webfont, embedded or otherwise.
|
||||
|
||||
| file | size |
|
||||
|---|---|
|
||||
| `500.html` | 20.7 KB |
|
||||
| `429.html`, `502.html`, `503.html`, `504.html`, `onderhoud.html` | 22.1 KB each |
|
||||
| **total** | **131 KB** |
|
||||
|
||||
`500.html` is smaller because it has no retry block, and therefore ships no
|
||||
countdown JS, no `<noscript>` refresh and none of the rail CSS.
|
||||
|
||||
About 16 KB of every page is the base64 logo. Budget is 1 MB (ConfigMap hard cap),
|
||||
so there is room, but keep it in mind before adding anything per-page.
|
||||
|
||||
## Regenerate the ConfigMap
|
||||
|
||||
From `deploy/error-pages/pages`:
|
||||
|
||||
```bash
|
||||
kubectl -n kube-system create configmap error-pages --from-file=. --dry-run=client -o yaml | kubectl apply -f -
|
||||
```
|
||||
|
||||
The pod serving these mounts the ConfigMap; if it is mounted as a volume the
|
||||
kubelet refreshes it within a minute or so, but restart the deployment if you
|
||||
want the change immediately:
|
||||
|
||||
```bash
|
||||
kubectl -n kube-system rollout restart deploy/error-pages
|
||||
```
|
||||
|
||||
## Serving pod
|
||||
|
||||
The `errors` middleware needs a real backend to fetch pages from. Anything that
|
||||
serves static files works; this is the smallest version.
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: error-pages
|
||||
namespace: kube-system
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels: { app: error-pages }
|
||||
template:
|
||||
metadata:
|
||||
labels: { app: error-pages }
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginxinc/nginx-unprivileged:alpine
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
volumeMounts:
|
||||
- name: pages
|
||||
mountPath: /usr/share/nginx/html
|
||||
readinessProbe:
|
||||
httpGet: { path: /503.html, port: 8080 }
|
||||
volumes:
|
||||
- name: pages
|
||||
configMap:
|
||||
name: error-pages
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: error-pages
|
||||
namespace: kube-system
|
||||
spec:
|
||||
selector: { app: error-pages }
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 8080
|
||||
```
|
||||
|
||||
## Middleware
|
||||
|
||||
```yaml
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: error-pages
|
||||
namespace: kube-system
|
||||
spec:
|
||||
errors:
|
||||
status:
|
||||
- "429"
|
||||
- "500-599"
|
||||
service:
|
||||
name: error-pages
|
||||
port: 80
|
||||
query: "/{status}.html"
|
||||
```
|
||||
|
||||
`query` substitutes the numeric status directly, which is why the filenames must
|
||||
be exactly `{status}.html`. A status in the range with no matching file (`501`,
|
||||
`507`, …) gets a 404 from the error service and Traefik falls back to its own
|
||||
plain body — so the six files cover the codes that actually occur, and anything
|
||||
else degrades to today's behaviour rather than breaking.
|
||||
|
||||
Attach it to an instance ingress with the usual annotation:
|
||||
|
||||
```yaml
|
||||
traefik.ingress.kubernetes.io/router.middlewares: kube-system-error-pages@kubernetescrd
|
||||
```
|
||||
|
||||
> **Cross-namespace reference.** The middleware lives in `kube-system` while the
|
||||
> instance ingresses live in their own namespaces. Traefik allows this by default,
|
||||
> but if `providers.kubernetesCRD.allowCrossNamespace=false` is set the reference
|
||||
> is silently ignored and you get the default error bodies back. Either enable it
|
||||
> or copy the Middleware object into each instance namespace.
|
||||
|
||||
## No 404 page
|
||||
|
||||
Deliberate. Traefik does not run the middleware chain when no router matches, so
|
||||
a 404 page would only ever fire for LNbits-generated 404s. Not worth the file.
|
||||
|
||||
## Planned maintenance
|
||||
|
||||
`onderhoud.html` is not wired to a status code — it is swapped in by hand.
|
||||
Repoint the middleware query for the duration of the window:
|
||||
|
||||
```yaml
|
||||
query: "/onderhoud.html"
|
||||
```
|
||||
|
||||
Apply it, and every 5xx from the instances renders the maintenance page instead
|
||||
of the per-status ones. Revert to `query: "/{status}.html"` when the window ends.
|
||||
|
||||
This only changes what is *rendered*. Traefik still returns the upstream status
|
||||
code, so `onderhoud.html` goes out as a 503 for as long as the instances are
|
||||
down — which is the correct signal for crawlers and uptime checks.
|
||||
|
||||
## Open TODOs
|
||||
|
||||
Both are marked in the HTML.
|
||||
|
||||
- **Logo.** Currently `www.cashow.be/img/cashow_extrasmall.png` base64-inlined —
|
||||
a 250×250 raster, not a vector. Drop `assets/cashow-logo.svg` in the repo and
|
||||
swap the marked `<!-- ==== CASHOW LOGO ==== -->` block in all six files; it is
|
||||
byte-identical across them, so it is one find-and-replace.
|
||||
- **Status host.** The ghost pill links to `https://status.cashow.be`, which did
|
||||
not resolve as of 2026-08-17 (NXDOMAIN). Point it at the real status page or
|
||||
remove the pill.
|
||||
|
||||
## Local review
|
||||
|
||||
Open `preview.html` in a browser to see all six side by side. It is a review aid
|
||||
only and is not part of the ConfigMap.
|
||||
Reference in New Issue
Block a user