WIP: caddy: add support for error msg
This commit is contained in:
parent
004a033606
commit
48c4ecafae
6 changed files with 203 additions and 50 deletions
|
@ -31,17 +31,21 @@ caddy__routes_https:
|
||||||
status: 301
|
status: 301
|
||||||
www3.test.auro.re:
|
www3.test.auro.re:
|
||||||
reverse:
|
reverse:
|
||||||
- "[2a09:6840:128::98]:3000"
|
- "[2a09:6840:128::198]:3000"
|
||||||
- 10.128.0.98:3000
|
- 10.128.0.198:3000
|
||||||
|
|
||||||
|
caddy__errors:
|
||||||
|
- root: /var/www
|
||||||
|
- rewrite: /error.html
|
||||||
|
- file_server: true
|
||||||
|
templates: true
|
||||||
|
|
||||||
caddy__servers:
|
caddy__servers:
|
||||||
https:
|
https:
|
||||||
listen: ":443"
|
listen: ":443"
|
||||||
routes: "{{ caddy__routes_https }}"
|
routes: "{{ caddy__routes_https }}"
|
||||||
|
errors: "{{ caddy__errors }}"
|
||||||
http:
|
http:
|
||||||
listen: ":80"
|
listen: ":80"
|
||||||
|
|
||||||
#caddy__error_handlers:
|
|
||||||
#"*":
|
|
||||||
# - error_template: "/var/www/error.html"
|
|
||||||
...
|
...
|
||||||
|
|
3
roles/caddy/defaults/main.yml
Normal file
3
roles/caddy/defaults/main.yml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
---
|
||||||
|
caddy__error_dir: /var/www/error
|
||||||
|
...
|
|
@ -14,8 +14,8 @@ from pydantic import (
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
def flatten(iterable: Iterable[Iterable[T]]) -> Iterable[T]:
|
def flatten_list(iterable: Iterable[Iterable[T]]) -> list[T]:
|
||||||
return itertools.chain.from_iterable(iterable)
|
return list(itertools.chain.from_iterable(iterable))
|
||||||
|
|
||||||
|
|
||||||
class AutoList(list[T], Generic[T]):
|
class AutoList(list[T], Generic[T]):
|
||||||
|
@ -43,7 +43,8 @@ class FilesHandler(BaseHandler):
|
||||||
root: str
|
root: str
|
||||||
|
|
||||||
def to_caddy(self):
|
def to_caddy(self):
|
||||||
yield {"handle": [{"handler": "vars", "root": self.root}]}
|
handler = {"handler": "vars", "root": self.root}
|
||||||
|
yield {"handle": [handler]}
|
||||||
|
|
||||||
|
|
||||||
class StaticHandler(BaseHandler):
|
class StaticHandler(BaseHandler):
|
||||||
|
@ -51,35 +52,60 @@ class StaticHandler(BaseHandler):
|
||||||
body: str | None = None
|
body: str | None = None
|
||||||
|
|
||||||
def to_caddy(self):
|
def to_caddy(self):
|
||||||
response = {"handler": "static_response"}
|
handler = {"handler": "static_response"}
|
||||||
if self.status is not None:
|
if self.status is not None:
|
||||||
response["status_code"] = self.status
|
handler["status_code"] = self.status
|
||||||
if self.body is not None:
|
if self.body is not None:
|
||||||
response["body"] = self.body
|
handler["body"] = self.body
|
||||||
yield {"handle": [response]}
|
yield {"handle": [handler]}
|
||||||
|
|
||||||
|
|
||||||
class ReverseHandler(BaseHandler):
|
class ReverseHandler(BaseHandler):
|
||||||
reverse: AutoList[str]
|
reverse: AutoList[str]
|
||||||
|
|
||||||
def to_caddy(self):
|
def to_caddy(self):
|
||||||
yield {
|
handler = {
|
||||||
"handle": [
|
"handler": "reverse_proxy",
|
||||||
{
|
"upstreams": [{"dial": s} for s in self.reverse],
|
||||||
"handler": "reverse_proxy",
|
|
||||||
"upstreams": [{"dial": s} for s in self.reverse],
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
yield {"handle": [handler]}
|
||||||
|
|
||||||
|
|
||||||
Handler = FilesHandler | ReverseHandler | StaticHandler
|
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
|
||||||
|
| RewriteHandler
|
||||||
|
| FileServerHandler
|
||||||
|
| StaticHandler
|
||||||
|
)
|
||||||
Routes = dict[str, AutoList[Handler]]
|
Routes = dict[str, AutoList[Handler]]
|
||||||
|
|
||||||
|
|
||||||
class Server(BaseModel):
|
class Server(BaseModel):
|
||||||
listen: AutoList[str]
|
listen: AutoList[str]
|
||||||
routes: Routes = {}
|
routes: Routes = {}
|
||||||
|
errors: AutoList[Handler] = {}
|
||||||
|
|
||||||
|
|
||||||
Config = dict[str, Server]
|
Config = dict[str, Server]
|
||||||
|
@ -96,23 +122,22 @@ class Context:
|
||||||
|
|
||||||
def strip_path_prefix(prefix: str) -> Any:
|
def strip_path_prefix(prefix: str) -> Any:
|
||||||
return {
|
return {
|
||||||
"strip_path_prefix": prefix,
|
|
||||||
"handler": "rewrite",
|
"handler": "rewrite",
|
||||||
|
"strip_path_prefix": prefix,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def handler_to_caddy(handler: Handler, ctx: Context) -> Any:
|
def handler_to_caddy(handler: Handler, ctx: Context) -> Any:
|
||||||
def to_caddy_inner():
|
def to_caddy_inner():
|
||||||
if handler.headers:
|
if handler.headers:
|
||||||
yield {
|
handlers = [
|
||||||
"handle": [
|
{
|
||||||
{
|
"handler": "headers",
|
||||||
"handler": "headers",
|
"response": {"set": {name: [value]}},
|
||||||
"response": {"set": {name: [value]}},
|
}
|
||||||
}
|
for name, value in handler.headers.items()
|
||||||
for name, value in handler.headers.items()
|
]
|
||||||
]
|
yield {"handle": handlers}
|
||||||
}
|
|
||||||
yield from handler.to_caddy()
|
yield from handler.to_caddy()
|
||||||
|
|
||||||
if handler.path is None:
|
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:
|
def route_to_caddy(
|
||||||
return {
|
host: str | None, handlers: list[Handler], ctx: Context
|
||||||
"match": [{"host": [host]}],
|
) -> Any:
|
||||||
"handle": [
|
handler = {
|
||||||
{
|
"handler": "subroute",
|
||||||
"handler": "subroute",
|
"routes": flatten_list(handler_to_caddy(h, ctx) for h in handlers),
|
||||||
"routes": list(
|
|
||||||
flatten(handler_to_caddy(h, ctx) for h in handlers)
|
|
||||||
),
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": True,
|
|
||||||
}
|
}
|
||||||
|
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:
|
def server_to_caddy(server: Server, ctx: Context) -> Any:
|
||||||
|
routes = [
|
||||||
|
route_to_caddy(host, handlers, ctx)
|
||||||
|
for host, handlers in server.routes.items()
|
||||||
|
]
|
||||||
return {
|
return {
|
||||||
"listen": server.listen,
|
"listen": server.listen,
|
||||||
"errors": {}, # TODO
|
"errors": {"routes": [route_to_caddy(None, server.errors, ctx)]},
|
||||||
"logs": {}, # TODO
|
"logs": {},
|
||||||
"routes": [
|
"routes": routes,
|
||||||
route_to_caddy(host, handlers, ctx)
|
|
||||||
for host, handlers in server.routes.items()
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
|
|
||||||
- name: Configure caddy
|
- name: Configure caddy
|
||||||
copy:
|
copy:
|
||||||
content: "{{ caddy__servers | caddy__of_servers | to_json }}"
|
content: "{{ caddy__servers | caddy__of_servers | to_nice_json }}"
|
||||||
dest: /etc/caddy/caddy.json
|
dest: /etc/caddy/caddy.json
|
||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
|
@ -31,6 +31,22 @@
|
||||||
notify:
|
notify:
|
||||||
- Reload caddy
|
- 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
|
- name: Enable caddy
|
||||||
systemd:
|
systemd:
|
||||||
daemon_reload: true
|
daemon_reload: true
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
{{ caddy__servers | caddy__of_servers | to_nice_json }}
|
|
107
roles/caddy/templates/error.html.j2
Normal file
107
roles/caddy/templates/error.html.j2
Normal 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 d’Aurore</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 n’existe 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>
|
Loading…
Reference in a new issue