first commit

This commit is contained in:
2026-08-17 19:30:25 +00:00
commit ae27f0b989
8 changed files with 1152 additions and 0 deletions
+181
View File
@@ -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.