WIP: caddy: add support for error msg

This commit is contained in:
jeltz 2024-05-02 22:28:50 +02:00
parent 004a033606
commit 48c4ecafae
Signed by: jeltz
GPG key ID: 800882B66C0C3326
6 changed files with 203 additions and 50 deletions

View file

@ -31,17 +31,21 @@ caddy__routes_https:
status: 301
www3.test.auro.re:
reverse:
- "[2a09:6840:128::98]:3000"
- 10.128.0.98:3000
- "[2a09:6840:128::198]:3000"
- 10.128.0.198:3000
caddy__errors:
- root: /var/www
- rewrite: /error.html
- file_server: true
templates: true
caddy__servers:
https:
listen: ":443"
routes: "{{ caddy__routes_https }}"
errors: "{{ caddy__errors }}"
http:
listen: ":80"
#caddy__error_handlers:
#"*":
# - error_template: "/var/www/error.html"
...

View file

@ -0,0 +1,3 @@
---
caddy__error_dir: /var/www/error
...

View file

@ -14,8 +14,8 @@ from pydantic import (
T = TypeVar("T")
def flatten(iterable: Iterable[Iterable[T]]) -> Iterable[T]:
return itertools.chain.from_iterable(iterable)
def flatten_list(iterable: Iterable[Iterable[T]]) -> list[T]:
return list(itertools.chain.from_iterable(iterable))
class AutoList(list[T], Generic[T]):
@ -43,7 +43,8 @@ class FilesHandler(BaseHandler):
root: str
def to_caddy(self):
yield {"handle": [{"handler": "vars", "root": self.root}]}
handler = {"handler": "vars", "root": self.root}
yield {"handle": [handler]}
class StaticHandler(BaseHandler):
@ -51,35 +52,60 @@ class StaticHandler(BaseHandler):
body: str | None = None
def to_caddy(self):
response = {"handler": "static_response"}
handler = {"handler": "static_response"}
if self.status is not None:
response["status_code"] = self.status
handler["status_code"] = self.status
if self.body is not None:
response["body"] = self.body
yield {"handle": [response]}
handler["body"] = self.body
yield {"handle": [handler]}
class ReverseHandler(BaseHandler):
reverse: AutoList[str]
def to_caddy(self):
yield {
"handle": [
{
handler = {
"handler": "reverse_proxy",
"upstreams": [{"dial": s} for s in self.reverse],
}
yield {"handle": [handler]}
class RewriteHandler(BaseHandler):
rewrite: str
def to_caddy(self):
handler = {"handler": "rewrite", "uri": self.rewrite}
yield {"handle": [handler]}
class FileServerHandler(BaseHandler):
file_server: Literal[True]
templates: bool = False
def to_caddy(self):
handlers = [
{"handler": "templates"},
]
}
if self.templates:
handlers.append({"handler": "file_server"})
yield {"handle": handlers}
Handler = FilesHandler | ReverseHandler | StaticHandler
Handler = (
FilesHandler
| ReverseHandler
| RewriteHandler
| FileServerHandler
| StaticHandler
)
Routes = dict[str, AutoList[Handler]]
class Server(BaseModel):
listen: AutoList[str]
routes: Routes = {}
errors: AutoList[Handler] = {}
Config = dict[str, Server]
@ -96,23 +122,22 @@ class Context:
def strip_path_prefix(prefix: str) -> Any:
return {
"strip_path_prefix": prefix,
"handler": "rewrite",
"strip_path_prefix": prefix,
}
def handler_to_caddy(handler: Handler, ctx: Context) -> Any:
def to_caddy_inner():
if handler.headers:
yield {
"handle": [
handlers = [
{
"handler": "headers",
"response": {"set": {name: [value]}},
}
for name, value in handler.headers.items()
]
}
yield {"handle": handlers}
yield from handler.to_caddy()
if handler.path is None:
@ -133,30 +158,29 @@ def handler_to_caddy(handler: Handler, ctx: Context) -> Any:
}
def route_to_caddy(host: str, handlers: list[Handler], ctx: Context) -> Any:
return {
"match": [{"host": [host]}],
"handle": [
{
def route_to_caddy(
host: str | None, handlers: list[Handler], ctx: Context
) -> Any:
handler = {
"handler": "subroute",
"routes": list(
flatten(handler_to_caddy(h, ctx) for h in handlers)
),
}
],
"terminal": True,
"routes": flatten_list(handler_to_caddy(h, ctx) for h in handlers),
}
route = {"handle": [handler], "terminal": True}
if host is not None:
route["match"] = [{"host": [host]}]
return route
def server_to_caddy(server: Server, ctx: Context) -> Any:
return {
"listen": server.listen,
"errors": {}, # TODO
"logs": {}, # TODO
"routes": [
routes = [
route_to_caddy(host, handlers, ctx)
for host, handlers in server.routes.items()
],
]
return {
"listen": server.listen,
"errors": {"routes": [route_to_caddy(None, server.errors, ctx)]},
"logs": {},
"routes": routes,
}

View file

@ -23,7 +23,7 @@
- name: Configure caddy
copy:
content: "{{ caddy__servers | caddy__of_servers | to_json }}"
content: "{{ caddy__servers | caddy__of_servers | to_nice_json }}"
dest: /etc/caddy/caddy.json
owner: root
group: root
@ -31,6 +31,22 @@
notify:
- Reload caddy
- name: Create error directory
file:
path: "{{ caddy__error_dir }}"
state: directory
owner: root
group: root
mode: u=rwx=,g=rx,o=rx
- name: Configure error page
template:
src: error.html.j2
dest: "{{ caddy__error_dir }}/error.html"
owner: root
group: root
mode: u=rw,g=r,o=r
- name: Enable caddy
systemd:
daemon_reload: true

View file

@ -1 +0,0 @@
{{ caddy__servers | caddy__of_servers | to_nice_json }}

View file

@ -0,0 +1,107 @@
<!doctype html>
{{ ansible_managed | comment("xml") }}
<html>
<head>
<meta charset="utf-8"/>
<title>{{ '{{placeholder "http.error.status_code"}}' }}</title>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<style>
* {
line-height: 1.2;
margin: 0;
}
html {
font-family: sans-serif;
text-align: center;
color: #888;
display: grid;
min-height: 100vh;
}
body {
margin: 2em auto;
place-self: center;
}
a {
text-decoration: underline dotted;
color: #888;
}
h1 {
color: #555;
font-size: 2em;
font-weight: 400;
margin: 1em 0;
}
p {
margin: 1em auto;
max-width: 480px;
}
@media only screen and (max-width: 280px) {
body, p {
width: 95vw;
}
h1 {
font-size: 1.5em;
margin: 0 0 0.3em;
}
}
#switch {
margin: 0 0 1em 0;
justify-content: center;
}
#switch a {
padding: .5em;
}
</style>
<script>
document.addEventListener("DOMContentLoaded", () => {
document.getElementById("switch").hidden = false;
});
function translate(lang) {
document.querySelectorAll('[lang]').forEach((element) => {
element.hidden = element.getAttribute("lang") !== lang;
});
}
</script>
</head>
<body>
<nav id="switch" hidden>
<a href="javascript:translate('en')">English</a>
<a href="javascript:translate('fr')">Français</a>
</nav>
<h1 lang="fr" hidden>Erreur {{ '{{placeholder "http.error.status_code"}}' }}</h1>
<h1 lang="en">Error {{ '{{placeholder "http.error.status_code"}}' }}</h1>
{{ '{{$http_code := atoi (placeholder "http.error.status_code")}}' }}
{{ '{{if eq $http_code 502 503}}' }}
<p lang="fr" hidden>Whoops, le service est temporairement indisponible ou en maintenance…<br/>Essayez de rafraîchir la page plus tard.</p>
<p lang="en">Whoops, the service is temporarily unavailable or under maintenance…<br/>Try refreshing the page later.</p>
{{ '{{else if eq $http_code 504}}' }}
<p lang="fr" hidden>Whoops, le service prend trop de temps à répondre…<br/>Essayez de rafraîchir la page plus tard.</p>
<p lang="en">Whoops, the service takes too long to respond…<br/>Try refreshing the page later.</p>
{{ '{{end}}' }}
{{ '{{if and (lt $http_code 600) (gt $http_code 499)}}' }}
<p lang="fr" hidden>Si le problème persiste, contactez <a href="mailto:tech.aurore@lists.crans.org">léquipe technique dAurore</a>.</p>
<p lang="en">If the problem persists, contact <a href="mailto:tech.aurore@lists.crans.org">the Aurore technical team</a>.</p>
{{ '{{end}}' }}
{{ '{{if eq $http_code 403}}' }}
<p lang="fr" hidden>Whoops, cette page vous est interdite.</p>
<p lang="en">Whoops, you are not allowed on this page.</p>
{{ '{{else if eq $http_code 404}}' }}
<p lang="fr" hidden>Whoops, cette page nexiste pas.</p>
<p lang="en">Whoops, this page doesn't exist.</p>
{{ '{{else if and (lt $http_code 499) (gt $http_code 399)}}' }}
<p lang="fr" hidden>Whoops, requête malformée.</p>
<p lang="en">Whoops, malformed request.</p>
{{ '{{end}}' }}
<p lang="fr" hidden>ID pour le support : <code>{{ '{{placeholder "http.error.id"}}' }}</code></p>
<p lang="en">Support ID: <code>{{ '{{placeholder "http.error.id"}}' }}</code></p>
</body>
</html>