servidor: freno anti-rastreo en gitea — de load 14,78 a 0,96
La caja estaba ahogada: load 14,78 sobre 4 cores, gitea al 144 % de un core
y diez `git` hijos colgando. La causa NO era un leak ni el GOMEMLIMIT que se
le puso ayer (RSS 678 MB contra un techo de 2 GiB: no peleaba con el GC).
Medido sobre el log de saywa, 36 minutos de ventana:
· 1.179.729 peticiones/día desde 8.945 IPs distintas ⇒ por IP no se corta
· 99,5 % del coste en las vistas POR COMMIT: /src|commits|blame|raw
/commit/<sha>/… y /compare — ficheros × commits y refs × refs, o sea
espacio de rastreo INFINITO por construcción
· las vistas POR RAMA, que es lo que usa una persona, el 0,4 %
· 96 % de respuestas 200: se llevaban el contenido entero
· gitea forkeaba un `git cat-file` y un `git check-attr` (detección de
lenguaje) por cada vista de fichero ⇒ la tormenta de forks
El robots.txt ya prohibía esas rutas y los bots la ignoran — incluido
meta-webindexer, que pedía /blame/commit estando explícitamente prohibido y
se llevaba el 33,6 % del coste él solo. La capa educada estaba agotada.
La regla corta con 403 sólo a ANÓNIMOS: la cookie de sesión exime, así que
quien entra con usuario conserva los permalinks. Verificado con `saywa
explain` y en vivo que clonar, empujar, la portada, el árbol por rama,
issues, pulls, los ficheros de un PR y el robots.txt siguen en 200; y con un
`git clone` real de punta a punta.
Resultado: coste medio de 35,4 cores a 0,98 · load 14,78 → 0,96 · gitea de
144,6 % a 2,7 % de un core · cero hijos git.
Se versiona porque vivía sólo en /etc del anfitrión, que es la lección de los
enlaces del scratch y del terminfo: un arreglo que hay que recordar no es un
arreglo.
Delta semántico
Ninguna definición cambió: el cambio es de texto, documentación o datos.
1 archivos · +2002 −0
- scripts/servidor/saywa.ron +2002 −0
diff --git a/scripts/servidor/saywa.ron b/scripts/servidor/saywa.ron
new file mode 100644
index 00000000..d797db3d
--- /dev/null
+++ b/scripts/servidor/saywa.ron
@@ -0,0 +1,2002 @@
+// Config DESPLEGADA de `saywa` en la caja `takana` (2.29.29.217) — el servidor de borde que
+// reemplazó a caddy el 2026-10-20. Esto es una COPIA de /etc/saywa/saywa.ron, versionada acá
+// porque antes vivía SÓLO en /etc del anfitrión: si la caja se rehace, se pierde. Mismo criterio
+// que `cortafuegos-entrada.ron`, al lado.
+//
+// saywa check --config scripts/servidor/saywa.ron # valida sin arrancar
+// saywa explain --config scripts/servidor/saywa.ron URL # qué contestaría, y por qué
+// # desplegar: copiar a /etc/saywa/saywa.ron y `saywa ctl reload` (en caliente, sin cortar)
+//
+// ⚠ `explain` es la herramienta que hay que usar ANTES de tocar esto: dice qué ruta coincide y
+// qué se responde, leyendo un fichero cualquiera. Evita probar en producción.
+//
+// ══ EL FRENO ANTI-RASTREO (2026-10-21) ════════════════════════════════════════════════════════
+// Los dos frentes de gitea (git.gioser.net y git.tawasuyu.net) llevan una ruta que corta con 403
+// las vistas "por commit" para visitantes ANÓNIMOS. Medido, no preventivo:
+//
+// · 1.179.729 peticiones/día desde 8.945 IPs distintas ⇒ bloquear por IP es inútil
+// · el 99,5 % del coste estaba en /src|commits|blame|raw /commit/<sha>/... y en /compare:
+// son ficheros × commits y refs × refs — espacio de rastreo INFINITO por construcción
+// · las URL "por rama", que es lo que usa una persona, eran el 0,4 %
+// · load 14,78 en 4 cores; gitea forkeaba un `git cat-file` y un `git check-attr` por vista
+// · tras el freno: 35,4 → 0,98 cores de coste medio, y CERO hijos `git` colgando
+//
+// El robots.txt YA prohibía esas rutas y los bots la ignoran (incluido meta-webindexer, que pedía
+// /blame/commit estando explícitamente prohibido) ⇒ la capa educada estaba agotada y había que
+// hacer cumplir la norma. La cookie de sesión EXIME, así que quien entra con usuario conserva los
+// permalinks; clonar y empujar no pasan por esas rutas y están verificados intactos.
+//
+// ⚠ Y la trampa del recargado en caliente, que casi me hizo diagnosticar mal: tras `ctl reload`
+// las conexiones HTTP/2 ya abiertas SIGUEN con la tabla de rutas vieja. Se ve en el log por el
+// campo `saywa.ruta` (el índice del proxy se corre al insertar una ruta): 707 peticiones por la
+// ruta vieja en el minuto 0, 649 en el 1, 2 en el 2, 0 en el 3. Se drena solo en ~3 minutos, pero
+// medir antes de eso dice que la regla NO funciona cuando sí funciona.
+// saywa en takana: la config de caddy (/etc/caddy/Caddyfile) absorbida con
+// `saywa import caddyfile` el 2026-10-20, más lo que ya servía saywa
+// (repo.gioser.net desde /srv/repo). Los registros siguen en los mismos
+// archivos de caddy, con la misma forma JSON y la misma rotación: lo que los
+// lee no cambia. Volver a caddy: ver /root/saywa-cambio/VOLVER.
+(
+ listen: [
+ (
+ address: "[::]:80",
+ tls: false,
+ egress: false,
+ ),
+ (
+ address: "[::]:443",
+ tls: true,
+ egress: false,
+ // HTTP/3 por UDP en el mismo puerto (SDD §17); el cortafuegos lo deja pasar.
+ http3: true,
+ ),
+ ],
+ sites: [
+ (
+ names: [
+ "gitea.gioser.net",
+ ],
+ tls: Auto,
+ log: Some("requests"),
+ routes: [
+ (
+ actions: [
+ Redirect(
+ to: "https://git.gioser.net{uri}",
+ status: 301,
+ ),
+ ],
+ ),
+ ],
+ ),
+ (
+ names: [
+ "git.takana-os.net",
+ ],
+ tls: Auto,
+ log: Some("requests"),
+ routes: [
+ (
+ actions: [
+ Redirect(
+ to: "https://git.gioser.net{uri}",
+ status: 301,
+ ),
+ ],
+ ),
+ ],
+ ),
+ (
+ names: [
+ "git.gioser.net",
+ ],
+ tls: Auto,
+ log: Some("requests"),
+ routes: [
+ (
+ match: (
+ header: [
+ ("User-Agent", "*meta-externalagent*"),
+ ("User-Agent", "*FacebookBot*"),
+ ("User-Agent", "*GPTBot*"),
+ ("User-Agent", "*OAI-SearchBot*"),
+ ("User-Agent", "*ChatGPT*"),
+ ("User-Agent", "*Amazonbot*"),
+ ("User-Agent", "*ClaudeBot*"),
+ ("User-Agent", "*anthropic-ai*"),
+ ("User-Agent", "*Claude-Web*"),
+ ("User-Agent", "*Bytespider*"),
+ ("User-Agent", "*CCBot*"),
+ ("User-Agent", "*Google-Extended*"),
+ ("User-Agent", "*Applebot-Extended*"),
+ ("User-Agent", "*PerplexityBot*"),
+ ("User-Agent", "*Diffbot*"),
+ ("User-Agent", "*Omgilibot*"),
+ ("User-Agent", "*DataForSeoBot*"),
+ ],
+ ),
+ actions: [
+ Respond(
+ status: 403,
+ body: None,
+ type: None,
+ ),
+ ],
+ ),
+ (
+ // ⚠ FRENO ANTI-RASTREO — puesto 2026-10-21, medido, no preventivo.
+ // El log de saywa daba 1.179.729 peticiones/día de 8.945 IPs distintas, y
+ // el 99,5 % del coste estaba en las URL "por commit": /src|commits|blame|raw
+ // /commit/<sha>/..., que son ficheros × commits = espacio INFINITO. Las URL
+ // "por rama" —lo que usa una persona— eran el 0,4 %. Load 14,78 en 4 cores.
+ // El robots.txt YA prohibía estas rutas y lo ignoran: la capa educada está
+ // agotada, así que acá se hace cumplir. La cookie de sesión exime, o sea que
+ // quien entra con usuario conserva los permalinks; clonar y empujar no pasan
+ // por acá (van por /info/refs y /git-upload-pack) y no se tocan.
+ match: (
+ path: [
+ "/*/*/src/commit/*",
+ "/*/*/commits/commit/*",
+ "/*/*/blame/commit/*",
+ "/*/*/raw/commit/*",
+ "/*/*/commit/*",
+ // 2ª tanda, misma medición: con lo de arriba ya cortado, /compare se
+ // quedó con el 96 % del coste restante (5,2 s de media por peticion).
+ // Es refs × refs — la misma trampa combinatoria que /commit, y los
+ // bots la recorren igual. /activity y /lastcommit son agregados caros
+ // (vi un /activity/monthly de 42 s en UNA peticion).
+ "/*/*/compare/*",
+ "/*/*/activity",
+ "/*/*/activity/*",
+ "/*/*/lastcommit/*",
+ ],
+ not: [
+ (
+ header: [
+ ("Cookie", "*i_like_gitea*"),
+ ],
+ ),
+ ],
+ ),
+ actions: [
+ Respond(
+ status: 403,
+ body: Some("Esta vista por commit esta cerrada a visitantes anonimos: un rastreo la convierte en ficheros x commits y se come la maquina. Navega por rama (/src/branch/...), o entra con tu usuario. Clonar funciona siempre: git clone https://git.gioser.net/<dueno>/<repo>.git\n"),
+ type: Some("text/plain; charset=utf-8"),
+ ),
+ ],
+ ),
+ (
+ match: (
+ path: [
+ "/robots.txt",
+ ],
+ ),
+ actions: [
+ Files(
+ root: "/var/www/git-robots",
+ try: [],
+ browse: false,
+ index: [
+ "index.html",
+ ],
+ on_missing: NotFound,
+ hidden: false,
+ ),
+ ],
+ ),
+ (
+ match: (
+ not: [
+ (
+ path: [
+ "/robots.txt",
+ ],
+ ),
+ ],
+ ),
+ actions: [
+ Proxy(
+ to: [
+ "127.0.0.1:3002",
+ ],
+ policy: First,
+ ),
+ ],
+ ),
+ ],
+ ),
+ (
+ names: [
+ "git.tawasuyu.net",
+ ],
+ tls: Auto,
+ log: Some("requests"),
+ routes: [
+ (
+ match: (
+ header: [
+ ("User-Agent", "*meta-externalagent*"),
+ ("User-Agent", "*FacebookBot*"),
+ ("User-Agent", "*GPTBot*"),
+ ("User-Agent", "*OAI-SearchBot*"),
+ ("User-Agent", "*ChatGPT*"),
+ ("User-Agent", "*Amazonbot*"),
+ ("User-Agent", "*ClaudeBot*"),
+ ("User-Agent", "*anthropic-ai*"),
+ ("User-Agent", "*Claude-Web*"),
+ ("User-Agent", "*Bytespider*"),
+ ("User-Agent", "*CCBot*"),
+ ("User-Agent", "*Google-Extended*"),
+ ("User-Agent", "*Applebot-Extended*"),
+ ("User-Agent", "*PerplexityBot*"),
+ ("User-Agent", "*Diffbot*"),
+ ("User-Agent", "*Omgilibot*"),
+ ("User-Agent", "*DataForSeoBot*"),
+ ],
+ ),
+ actions: [
+ Respond(
+ status: 403,
+ body: None,
+ type: None,
+ ),
+ ],
+ ),
+ (
+ // ⚠ FRENO ANTI-RASTREO — puesto 2026-10-21, medido, no preventivo.
+ // El log de saywa daba 1.179.729 peticiones/día de 8.945 IPs distintas, y
+ // el 99,5 % del coste estaba en las URL "por commit": /src|commits|blame|raw
+ // /commit/<sha>/..., que son ficheros × commits = espacio INFINITO. Las URL
+ // "por rama" —lo que usa una persona— eran el 0,4 %. Load 14,78 en 4 cores.
+ // El robots.txt YA prohibía estas rutas y lo ignoran: la capa educada está
+ // agotada, así que acá se hace cumplir. La cookie de sesión exime, o sea que
+ // quien entra con usuario conserva los permalinks; clonar y empujar no pasan
+ // por acá (van por /info/refs y /git-upload-pack) y no se tocan.
+ match: (
+ path: [
+ "/*/*/src/commit/*",
+ "/*/*/commits/commit/*",
+ "/*/*/blame/commit/*",
+ "/*/*/raw/commit/*",
+ "/*/*/commit/*",
+ // 2ª tanda, misma medición: con lo de arriba ya cortado, /compare se
+ // quedó con el 96 % del coste restante (5,2 s de media por peticion).
+ // Es refs × refs — la misma trampa combinatoria que /commit, y los
+ // bots la recorren igual. /activity y /lastcommit son agregados caros
+ // (vi un /activity/monthly de 42 s en UNA peticion).
+ "/*/*/compare/*",
+ "/*/*/activity",
+ "/*/*/activity/*",
+ "/*/*/lastcommit/*",
+ ],
+ not: [
+ (
+ header: [
+ ("Cookie", "*i_like_gitea*"),
+ ],
+ ),
+ ],
+ ),
+ actions: [
+ Respond(
+ status: 403,
+ body: Some("Esta vista por commit esta cerrada a visitantes anonimos: un rastreo la convierte en ficheros x commits y se come la maquina. Navega por rama (/src/branch/...), o entra con tu usuario. Clonar funciona siempre: git clone https://git.gioser.net/<dueno>/<repo>.git\n"),
+ type: Some("text/plain; charset=utf-8"),
+ ),
+ ],
+ ),
+ (
+ match: (
+ path: [
+ "/robots.txt",
+ ],
+ ),
+ actions: [
+ Files(
+ root: "/var/www/git-robots",
+ try: [],
+ browse: false,
+ index: [
+ "index.html",
+ ],
+ on_missing: NotFound,
+ hidden: false,
+ ),
+ ],
+ ),
+ (
+ match: (
+ not: [
+ (
+ path: [
+ "/robots.txt",
+ ],
+ ),
+ ],
+ ),
+ actions: [
+ Proxy(
+ to: [
+ "127.0.0.1:3002",
+ ],
+ policy: First,
+ ),
+ ],
+ ),
+ ],
+ ),
+ (
+ names: [
+ "takana.gioser.net",
+ ],
+ tls: Auto,
+ log: Some("requests"),
+ routes: [
+ (
+ actions: [
+ Redirect(
+ to: "https://takana-os.net{uri}",
+ status: 301,
+ ),
+ ],
+ ),
+ ],
+ ),
+ (
+ names: [
+ "hifas.gioser.net",
+ ],
+ tls: Auto,
+ log: Some("sitios"),
+ routes: [
+ (
+ match: (
+ not: [
+ (
+ path: [
+ "*.*",
+ ],
+ ),
+ ],
+ ),
+ actions: [
+ Header(
+ name: "Cache-Control",
+ value: Some("no-cache"),
+ phase: Response,
+ ),
+ ],
+ ),
+ (
+ match: (
+ path: [
+ "*.html",
+ "*.json",
+ "*.css",
+ "*.js",
+ "*.xml",
+ ],
+ ),
+ actions: [
+ Header(
+ name: "Cache-Control",
+ value: Some("no-cache"),
+ phase: Response,
+ ),
+ ],
+ ),
+ (
+ match: (
+ file: Some((
+ root: Dir("/work/sergio/gioser-web/hifas"),
+ try: [
+ "{path}",
+ ],
+ )),
+ ),
+ actions: [
+ Rewrite(
+ to: "{path}",
+ ),
+ ],
+ ),
+ (
+ match: (
+ not: [
+ (
+ file: Some((
+ root: Dir("/work/sergio/gioser-web/hifas"),
+ try: [
+ "{path}",
+ ],
+ )),
+ ),
+ ],
+ all: [
+ (
+ file: Some((
+ root: Dir("/work/sergio/gioser-web/hifas"),
+ try: [
+ "{path}/",
+ ],
+ )),
+ ),
+ ],
+ ),
+ actions: [
+ Rewrite(
+ to: "{path}/",
+ ),
+ ],
+ ),
+ (
+ match: (
+ not: [
+ (
+ file: Some((
+ root: Dir("/work/sergio/gioser-web/hifas"),
+ try: [
+ "{path}",
+ ],
+ )),
+ ),
+ (
+ file: Some((
+ root: Dir("/work/sergio/gioser-web/hifas"),
+ try: [
+ "{path}/",
+ ],
+ )),
+ ),
+ ],
+ all: [
+ (
+ file: Some((
+ root: Dir("/work/sergio/gioser-web/hifas"),
+ try: [
+ "/index.html",
+ ],
+ )),
+ ),
+ ],
+ ),
+ actions: [
+ Rewrite(
+ to: "/index.html",
+ ),
+ ],
+ ),
+ (
+ actions: [
+ Encode(
+ with: [
+ Gzip,
+ Zstd,
+ ],
+ ),
+ ],
+ ),
+ (
+ actions: [
+ Files(
+ root: "/work/sergio/gioser-web/hifas",
+ try: [],
+ browse: false,
+ index: [
+ "index.html",
+ ],
+ on_missing: NotFound,
+ hidden: false,
+ ),
+ ],
+ ),
+ ],
+ ),
+ (
+ names: [
+ "api.sergio.gioser.net",
+ ],
+ tls: Auto,
+ log: Some("requests"),
+ routes: [
+ (
+ actions: [
+ Proxy(
+ to: [
+ "127.0.0.1:8771",
+ ],
+ policy: First,
+ ),
+ ],
+ ),
+ ],
+ ),
+ (
+ names: [
+ "tawasuyu.net",
+ "www.tawasuyu.net",
+ ],
+ tls: Auto,
+ log: Some("sitios"),
+ routes: [
+ (
+ match: (
+ path: [
+ "/pkg/*.wasm",
+ ],
+ ),
+ actions: [
+ Header(
+ name: "Content-Type",
+ value: Some("application/wasm"),
+ phase: Response,
+ ),
+ ],
+ ),
+ (
+ match: (
+ path: [
+ "*.webm",
+ ],
+ ),
+ actions: [
+ Header(
+ name: "Content-Type",
+ value: Some("video/webm"),
+ phase: Response,
+ ),
+ ],
+ ),
+ (
+ match: (
+ not: [
+ (
+ path: [
+ "*.*",
+ ],
+ ),
+ ],
+ ),
+ actions: [
+ Header(
+ name: "Cache-Control",
+ value: Some("no-cache"),
+ phase: Response,
+ ),
+ ],
+ ),
+ (
+ match: (
+ path: [
+ "*.html",
+ "*.json",
+ "*.css",
+ "*.js",
+ ],
+ ),
+ actions: [
+ Header(
+ name: "Cache-Control",
+ value: Some("no-cache"),
+ phase: Response,
+ ),
+ ],
+ ),
+ (
+ match: (
+ path: [
+ "/index.html",
+ ],
+ ),
+ actions: [
+ Rewrite(
+ to: "/web/tawasuyu-web/index.html",
+ ),
+ ],
+ ),
+ (
+ match: (
+ not: [
+ (
+ path: [
+ "/index.html",
+ ],
+ ),
+ ],
+ all: [
+ (
+ path: [
+ "/styles.css",
+ ],
+ ),
+ ],
+ ),
+ actions: [
+ Rewrite(
+ to: "/web/tawasuyu-web/styles.css",
+ ),
+ ],
+ ),
+ (
+ match: (
+ not: [
+ (
+ path: [
+ "/index.html",
+ ],
+ ),
+ (
+ path: [
+ "/styles.css",
+ ],
+ ),
+ ],
+ all: [
+ (
+ path: [
+ "/escritorio",
+ ],
+ ),
+ ],
+ ),
+ actions: [
+ Rewrite(
+ to: "/web/tawasuyu-web/escritorio.html",
+ ),
+ ],
+ ),
+ (
+ match: (
+ not: [
+ (
+ path: [
+ "/index.html",
+ ],
+ ),
+ (
+ path: [
+ "/styles.css",
+ ],
+ ),
+ (
+ path: [
+ "/escritorio",
+ ],
+ ),
+ ],
+ all: [
+ (
+ path: [
+ "/galeria",
+ ],
+ ),
+ ],
+ ),
+ actions: [
+ Rewrite(
+ to: "/web/tawasuyu-web/galeria.html",
+ ),
+ ],
+ ),
+ (
+ match: (
+ not: [
+ (
+ path: [
+ "/index.html",
+ ],
+ ),
+ (
+ path: [
+ "/styles.css",
+ ],
+ ),
+ (
+ path: [
+ "/escritorio",
+ ],
+ ),
+ (
+ path: [
+ "/galeria",
+ ],
+ ),
+ ],
+ all: [
+ (
+ path: [
+ "/pkg/*",
+ ],
+ ),
+ ],
+ ),
+ actions: [
+ Rewrite(
+ to: "/web/tawasuyu-web{path}",
+ ),
+ ],
+ ),
+ (
+ match: (
+ not: [
+ (
+ path: [
+ "/index.html",
+ ],
+ ),
+ (
+ path: [
+ "/styles.css",
+ ],
+ ),
+ (
+ path: [
+ "/escritorio",
+ ],
+ ),
+ (
+ path: [
+ "/galeria",
+ ],
+ ),
+ (
+ path: [
+ "/pkg/*",
+ ],
+ ),
+ ],
+ all: [
+ (
+ path: [
+ "/",
+ ],
+ ),
+ ],
+ ),
+ actions: [
+ Rewrite(
+ to: "/web/tawasuyu-web/index.html",
+ ),
+ ],
+ ),
+ (
+ match: (
+ not: [
+ (
+ path: [
+ "/index.html",
+ ],
+ ),
+ (
+ path: [
+ "/styles.css",
+ ],
+ ),
+ (
+ path: [
+ "/escritorio",
+ ],
+ ),
+ (
+ path: [
+ "/galeria",
+ ],
+ ),
+ (
+ path: [
+ "/pkg/*",
+ ],
+ ),
+ (
+ path: [
+ "/",
+ ],
+ ),
+ ],
+ all: [
+ (
+ not: [
+ (
+ path: [
+ "*.*",
+ ],
+ ),
+ (
+ path: [
+ "/",
+ ],
+ ),
+ (
+ path: [
+ "/visto",
+ ],
+ ),
+ (
+ file: Some((
+ root: Dir("/work/sergio/tawasuyu"),
+ try: [
+ "{path}",
+ "{path}/index.html",
+ ],
+ )),
+ ),
+ ],
+ ),
+ ],
+ ),
+ actions: [
+ Rewrite(
+ to: "/web/tawasuyu-web/index.html",
+ ),
+ ],
+ ),
+ (
+ actions: [
+ Encode(
+ with: [
+ Gzip,
+ Zstd,
+ ],
+ ),
+ ],
+ ),
+ (
+ match: (
+ path: [
+ "/visto",
+ ],
+ ),
+ actions: [
+ Respond(
+ status: 204,
+ body: None,
+ type: None,
+ ),
+ ],
+ ),
+ (
+ match: (
+ path: [
+ "/.*",
+ "*/.*",
+ ],
+ ),
+ actions: [
+ Respond(
+ status: 404,
+ body: None,
+ type: None,
+ ),
+ ],
+ ),
+ (
+ actions: [
+ Files(
+ root: "/work/sergio/tawasuyu",
+ try: [],
+ browse: false,
+ index: [
+ "index.html",
+ ],
+ on_missing: NotFound,
+ hidden: false,
+ ),
+ ],
+ ),
+ ],
+ ),
+ (
+ names: [
+ "gioser.net",
+ "www.gioser.net",
+ ],
+ tls: Auto,
+ log: Some("sitios"),
+ routes: [
+ (
+ match: (
+ not: [
+ (
+ path: [
+ "*.*",
+ ],
+ ),
+ ],
+ ),
+ actions: [
+ Header(
+ name: "Cache-Control",
+ value: Some("no-cache"),
+ phase: Response,
+ ),
+ ],
+ ),
+ (
+ match: (
+ path: [
+ "*.html",
+ "*.json",
+ "*.css",
+ "*.js",
+ "*.xml",
+ ],
+ ),
+ actions: [
+ Header(
+ name: "Cache-Control",
+ value: Some("no-cache"),
+ phase: Response,
+ ),
+ ],
+ ),
+ (
+ match: (
+ path: [
+ "/reencuentro/*",
+ ],
+ ),
+ strip_prefix: Some("/reencuentro"),
+ actions: [
+ Header(
+ name: "X-Robots-Tag",
+ value: Some("noindex, nofollow, noarchive, noimageindex"),
+ phase: Response,
+ ),
+ ],
+ ),
+ (
+ match: (
+ path: [
+ "/reencuentro/*",
+ ],
+ ),
+ strip_prefix: Some("/reencuentro"),
+ actions: [
+ Encode(
+ with: [
+ Gzip,
+ Zstd,
+ ],
+ ),
+ ],
+ ),
+ (
+ match: (
+ path: [
+ "/reencuentro/*",
+ ],
+ ),
+ strip_prefix: Some("/reencuentro"),
+ actions: [
+ FastCgi(
+ to: "127.0.0.1:9000",
+ root: "/work/www/gioser-web/reencuentro",
+ index: "index.php",
+ ),
+ ],
+ ),
+ (
+ match: (
+ path: [
+ "/reencuentro/*",
+ ],
+ ),
+ strip_prefix: Some("/reencuentro"),
+ actions: [
+ Files(
+ root: "/work/www/gioser-web/reencuentro",
+ try: [],
+ browse: false,
+ index: [
+ "index.html",
+ ],
+ on_missing: NotFound,
+ hidden: false,
+ ),
+ ],
+ ),
+ (
+ match: (
+ not: [
+ (
+ path: [
+ "/reencuentro/*",
+ ],
+ ),
+ ],
+ all: [
+ (
+ path: [
+ "/humanoid/*",
+ ],
+ ),
+ ],
+ ),
+ strip_prefix: Some("/humanoid"),
+ actions: [
+ Encode(
+ with: [
+ Gzip,
+ ],
+ ),
+ ],
+ ),
+ (
+ match: (
+ not: [
+ (
+ path: [
+ "/reencuentro/*",
+ ],
+ ),
+ ],
+ all: [
+ (
+ path: [
+ "/humanoid/*",
+ ],
+ ),
+ ],
+ ),
+ strip_prefix: Some("/humanoid"),
+ actions: [
+ Files(
+ root: "/work/www/gioser-web/humanoid",
+ try: [],
+ browse: false,
+ index: [
+ "index.html",
+ ],
+ on_missing: NotFound,
+ hidden: false,
+ ),
+ ],
+ ),
+ (
+ match: (
+ not: [
+ (
+ path: [
+ "/reencuentro/*",
+ ],
+ ),
+ (
+ path: [
+ "/humanoid/*",
+ ],
+ ),
+ ],
+ all: [
+ (
+ path: [
+ "/muestras/*",
+ ],
+ ),
+ ],
+ ),
+ strip_prefix: Some("/muestras"),
+ actions: [
+ Encode(
+ with: [
+ Gzip,
+ ],
+ ),
+ ],
+ ),
+ (
+ match: (
+ not: [
+ (
+ path: [
+ "/reencuentro/*",
+ ],
+ ),
+ (
+ path: [
+ "/humanoid/*",
+ ],
+ ),
+ ],
+ all: [
+ (
+ path: [
+ "/muestras/*",
+ ],
+ ),
+ ],
+ ),
+ strip_prefix: Some("/muestras"),
+ actions: [
+ Files(
+ root: "/work/www/gioser-web/muestras",
+ try: [],
+ browse: true,
+ index: [
+ "index.html",
+ ],
+ on_missing: NotFound,
+ hidden: false,
+ ),
+ ],
+ ),
+ (
+ match: (
+ not: [
+ (
+ path: [
+ "/reencuentro/*",
+ ],
+ ),
+ (
+ path: [
+ "/humanoid/*",
+ ],
+ ),
+ (
+ path: [
+ "/muestras/*",
+ ],
+ ),
+ ],
+ all: [
+ (
+ path: [
+ "/vivo/*",
+ ],
+ ),
+ ],
+ ),
+ strip_prefix: Some("/vivo"),
+ actions: [
+ Encode(
+ with: [
+ Gzip,
+ ],
+ ),
+ ],
+ ),
+ (
+ match: (
+ not: [
+ (
+ path: [
+ "/reencuentro/*",
+ ],
+ ),
+ (
+ path: [
+ "/humanoid/*",
+ ],
+ ),
+ (
+ path: [
+ "/muestras/*",
+ ],
+ ),
+ ],
+ all: [
+ (
+ path: [
+ "/vivo/*",
+ ],
+ ),
+ ],
+ ),
+ strip_prefix: Some("/vivo"),
+ actions: [
+ Files(
+ root: "/work/www/gioser-web/vivo",
+ try: [],
+ browse: true,
+ index: [
+ "index.html",
+ ],
+ on_missing: NotFound,
+ hidden: false,
+ ),
+ ],
+ ),
+ (
+ match: (
+ not: [
+ (
+ path: [
+ "/reencuentro/*",
+ ],
+ ),
+ (
+ path: [
+ "/humanoid/*",
+ ],
+ ),
+ (
+ path: [
+ "/muestras/*",
+ ],
+ ),
+ (
+ path: [
+ "/vivo/*",
+ ],
+ ),
+ ],
+ all: [
+ (
+ path: [
+ "/ende/*",
+ ],
+ ),
+ ],
+ ),
+ strip_prefix: Some("/ende"),
+ actions: [
+ Encode(
+ with: [
+ Gzip,
+ Zstd,
+ ],
+ ),
+ ],
+ ),
+ (
+ match: (
+ not: [
+ (
+ path: [
+ "/reencuentro/*",
+ ],
+ ),
+ (
+ path: [
+ "/humanoid/*",
+ ],
+ ),
+ (
+ path: [
+ "/muestras/*",
+ ],
+ ),
+ (
+ path: [
+ "/vivo/*",
+ ],
+ ),
+ ],
+ all: [
+ (
+ path: [
+ "/ende/*",
+ ],
+ ),
+ ],
+ ),
+ strip_prefix: Some("/ende"),
+ actions: [
+ Files(
+ root: "/work/www/gioser-web/ende",
+ try: [],
+ browse: true,
+ index: [
+ "index.html",
+ ],
+ on_missing: NotFound,
+ hidden: false,
+ ),
+ ],
+ ),
+ (
+ match: (
+ not: [
+ (
+ path: [
+ "/reencuentro/*",
+ ],
+ ),
+ (
+ path: [
+ "/humanoid/*",
+ ],
+ ),
+ (
+ path: [
+ "/muestras/*",
+ ],
+ ),
+ (
+ path: [
+ "/vivo/*",
+ ],
+ ),
+ (
+ path: [
+ "/ende/*",
+ ],
+ ),
+ ],
+ all: [
+ (
+ path: [
+ "/visto",
+ ],
+ ),
+ ],
+ ),
+ actions: [
+ Respond(
+ status: 204,
+ body: None,
+ type: None,
+ ),
+ ],
+ ),
+ (
+ match: (
+ not: [
+ (
+ path: [
+ "/reencuentro/*",
+ ],
+ ),
+ (
+ path: [
+ "/humanoid/*",
+ ],
+ ),
+ (
+ path: [
+ "/muestras/*",
+ ],
+ ),
+ (
+ path: [
+ "/vivo/*",
+ ],
+ ),
+ (
+ path: [
+ "/ende/*",
+ ],
+ ),
+ (
+ path: [
+ "/visto",
+ ],
+ ),
+ ],
+ all: [
+ (
+ path: [
+ "/scripts",
+ "/scripts/*",
+ "/.*",
+ "/.*/*",
+ ],
+ ),
+ ],
+ ),
+ actions: [
+ Respond(
+ status: 404,
+ body: None,
+ type: None,
+ ),
+ ],
+ ),
+ (
+ match: (
+ not: [
+ (
+ path: [
+ "/reencuentro/*",
+ ],
+ ),
+ (
+ path: [
+ "/humanoid/*",
+ ],
+ ),
+ (
+ path: [
+ "/muestras/*",
+ ],
+ ),
+ (
+ path: [
+ "/vivo/*",
+ ],
+ ),
+ (
+ path: [
+ "/ende/*",
+ ],
+ ),
+ (
+ path: [
+ "/visto",
+ ],
+ ),
+ (
+ path: [
+ "/scripts",
+ "/scripts/*",
+ "/.*",
+ "/.*/*",
+ ],
+ ),
+ ],
+ ),
+ actions: [
+ Encode(
+ with: [
+ Gzip,
+ Zstd,
+ ],
+ ),
+ ],
+ ),
+ (
+ match: (
+ not: [
+ (
+ path: [
+ "/reencuentro/*",
+ ],
+ ),
+ (
+ path: [
+ "/humanoid/*",
+ ],
+ ),
+ (
+ path: [
+ "/muestras/*",
+ ],
+ ),
+ (
+ path: [
+ "/vivo/*",
+ ],
+ ),
+ (
+ path: [
+ "/ende/*",
+ ],
+ ),
+ (
+ path: [
+ "/visto",
+ ],
+ ),
+ (
+ path: [
+ "/scripts",
+ "/scripts/*",
+ "/.*",
+ "/.*/*",
+ ],
+ ),
+ ],
+ ),
+ actions: [
+ Files(
+ root: "/work/sergio/gioser-web/",
+ try: [],
+ browse: false,
+ index: [
+ "index.html",
+ ],
+ on_missing: NotFound,
+ hidden: false,
+ ),
+ ],
+ ),
+ ],
+ ),
+ (
+ names: [
+ "sergio.gioser.net",
+ ],
+ tls: Auto,
+ log: Some("requests"),
+ routes: [
+ (
+ match: (
+ path: [
+ "/shuma/*",
+ ],
+ ),
+ strip_prefix: Some("/shuma"),
+ actions: [
+ Proxy(
+ to: [
+ "127.0.0.1:7378",
+ ],
+ policy: First,
+ ),
+ ],
+ ),
+ (
+ match: (
+ all: [
+ (
+ not: [
+ (
+ path: [
+ "/shuma/*",
+ ],
+ ),
+ ],
+ ),
+ (
+ file: Some((
+ root: Dir("/work/www/sergioh"),
+ try: [
+ "{path}",
+ ],
+ )),
+ ),
+ ],
+ ),
+ actions: [
+ Rewrite(
+ to: "{path}",
+ ),
+ ],
+ ),
+ (
+ match: (
+ not: [
+ (
+ file: Some((
+ root: Dir("/work/www/sergioh"),
+ try: [
+ "{path}",
+ ],
+ )),
+ ),
+ ],
+ all: [
+ (
+ all: [
+ (
+ not: [
+ (
+ path: [
+ "/shuma/*",
+ ],
+ ),
+ ],
+ ),
+ (
+ file: Some((
+ root: Dir("/work/www/sergioh"),
+ try: [
+ "{path}/",
+ ],
+ )),
+ ),
+ ],
+ ),
+ ],
+ ),
+ actions: [
+ Rewrite(
+ to: "{path}/",
+ ),
+ ],
+ ),
+ (
+ match: (
+ not: [
+ (
+ file: Some((
+ root: Dir("/work/www/sergioh"),
+ try: [
+ "{path}",
+ ],
+ )),
+ ),
+ (
+ file: Some((
+ root: Dir("/work/www/sergioh"),
+ try: [
+ "{path}/",
+ ],
+ )),
+ ),
+ ],
+ all: [
+ (
+ all: [
+ (
+ not: [
+ (
+ path: [
+ "/shuma/*",
+ ],
+ ),
+ ],
+ ),
+ (
+ file: Some((
+ root: Dir("/work/www/sergioh"),
+ try: [
+ "/index.html",
+ ],
+ )),
+ ),
+ ],
+ ),
+ ],
+ ),
+ actions: [
+ Rewrite(
+ to: "/index.html",
+ ),
+ ],
+ ),
+ (
+ match: (
+ not: [
+ (
+ path: [
+ "/shuma/*",
+ ],
+ ),
+ ],
+ ),
+ actions: [
+ Encode(
+ with: [
+ Gzip,
+ ],
+ ),
+ ],
+ ),
+ (
+ match: (
+ not: [
+ (
+ path: [
+ "/shuma/*",
+ ],
+ ),
+ ],
+ ),
+ actions: [
+ Files(
+ root: "/work/www/sergioh",
+ try: [],
+ browse: false,
+ index: [
+ "index.html",
+ ],
+ on_missing: NotFound,
+ hidden: false,
+ ),
+ ],
+ ),
+ ],
+ ),
+ (
+ names: [
+ "repo.gioser.net",
+ ],
+ tls: Auto,
+ log: Some("repo"),
+ routes: [
+ (actions: [Files(root: "/srv/repo", browse: true)]),
+ ],
+ ),
+ (
+ names: [
+ "takana-os.net",
+ "www.takana-os.net",
+ ],
+ tls: Auto,
+ log: Some("sitios"),
+ routes: [
+ (
+ match: (
+ not: [
+ (
+ path: [
+ "*.*",
+ ],
+ ),
+ ],
+ ),
+ actions: [
+ Header(
+ name: "Cache-Control",
+ value: Some("no-cache"),
+ phase: Response,
+ ),
+ ],
+ ),
+ (
+ match: (
+ path: [
+ "*.html",
+ "*.json",
+ "*.css",
+ "*.js",
+ "*.xml",
+ ],
+ ),
+ actions: [
+ Header(
+ name: "Cache-Control",
+ value: Some("no-cache"),
+ phase: Response,
+ ),
+ ],
+ ),
+ (
+ match: (
+ path: [
+ "/descargas",
+ ],
+ ),
+ actions: [
+ Redirect(
+ to: "/descargas/",
+ status: 301,
+ ),
+ ],
+ ),
+ (
+ match: (
+ path: [
+ "/repo",
+ ],
+ ),
+ actions: [
+ Redirect(
+ to: "/repo/",
+ status: 301,
+ ),
+ ],
+ ),
+ (
+ match: (
+ not: [
+ (
+ path: [
+ "/descargas/*",
+ ],
+ ),
+ ],
+ ),
+ actions: [
+ Encode(
+ with: [
+ Gzip,
+ Zstd,
+ ],
+ ),
+ ],
+ ),
+ (
+ match: (
+ path: [
+ "/descargas/*",
+ ],
+ ),
+ strip_prefix: Some("/descargas"),
+ actions: [
+ Header(
+ name: "Content-Disposition",
+ value: Some("attachment"),
+ phase: Response,
+ ),
+ ],
+ ),
+ (
+ match: (
+ path: [
+ "/descargas/*",
+ ],
+ ),
+ strip_prefix: Some("/descargas"),
+ actions: [
+ Files(
+ root: "/work/sergio/descargas",
+ try: [],
+ browse: true,
+ index: [
+ "index.html",
+ ],
+ on_missing: NotFound,
+ hidden: false,
+ ),
+ ],
+ ),
+ (
+ match: (
+ not: [
+ (
+ path: [
+ "/descargas/*",
+ ],
+ ),
+ ],
+ all: [
+ (
+ path: [
+ "/repo/*",
+ ],
+ ),
+ ],
+ ),
+ strip_prefix: Some("/repo"),
+ actions: [
+ Files(
+ root: "/srv/repo",
+ try: [],
+ browse: true,
+ index: [
+ "index.html",
+ ],
+ on_missing: NotFound,
+ hidden: false,
+ ),
+ ],
+ ),
+ (
+ match: (
+ not: [
+ (
+ path: [
+ "/descargas/*",
+ ],
+ ),
+ (
+ path: [
+ "/repo/*",
+ ],
+ ),
+ ],
+ all: [
+ (
+ path: [
+ "/visto",
+ ],
+ ),
+ ],
+ ),
+ actions: [
+ Respond(
+ status: 204,
+ body: None,
+ type: None,
+ ),
+ ],
+ ),
+ (
+ match: (
+ all: [
+ (
+ not: [
+ (
+ path: [
+ "/descargas/*",
+ ],
+ ),
+ (
+ path: [
+ "/repo/*",
+ ],
+ ),
+ (
+ path: [
+ "/visto",
+ ],
+ ),
+ ],
+ ),
+ (
+ path: [
+ "/.*",
+ "*/.*",
+ "/README.md",
+ ],
+ ),
+ ],
+ ),
+ actions: [
+ Respond(
+ status: 404,
+ body: None,
+ type: None,
+ ),
+ ],
+ ),
+ (
+ match: (
+ not: [
+ (
+ path: [
+ "/descargas/*",
+ ],
+ ),
+ (
+ path: [
+ "/repo/*",
+ ],
+ ),
+ (
+ path: [
+ "/visto",
+ ],
+ ),
+ ],
+ ),
+ actions: [
+ Files(
+ root: "/work/sergio/takana/web/takana-os.net",
+ try: [],
+ browse: false,
+ index: [
+ "index.html",
+ ],
+ on_missing: NotFound,
+ hidden: false,
+ ),
+ ],
+ ),
+ ],
+ ),
+ ],
+ snippets: {},
+ egress: None,
+ tls: (
+ state_dir: "/var/lib/saywa",
+ acme_directory: "https://acme-v02.api.letsencrypt.org/directory",
+ email: None,
+ renew_days: 30,
+ challenge: Http01,
+ ),
+ logs: [
+ (name: "repo", file: "/var/log/saywa/repo.json", ip: Truncated),
+ (
+ name: "requests",
+ file: "/var/log/caddy/requests.json",
+ ip: Full,
+ roll_mb: 50,
+ roll_keep: 14,
+ roll_keep_days: 30,
+ format: Json,
+ ),
+ (
+ name: "sitios",
+ file: "/var/log/caddy/sitios.json",
+ ip: Full,
+ roll_mb: 20,
+ roll_keep: 60,
+ roll_keep_days: 90,
+ format: Json,
+ ),
+ ],
+ limits: (
+ max_header_bytes: 65536,
+ max_body_bytes: 10485760,
+ header_timeout: 10,
+ idle_timeout: 120,
+ max_conns_per_ip: 256,
+ max_h2_streams: 128,
+ drain_timeout: 30,
+ rate_per_ip: 0,
+ body_timeout: 20,
+ min_body_rate: 500,
+ max_inflight: 4096,
+ ban_after: 200,
+ ban_secs: 600,
+ ban_file: Some("/var/lib/saywa/castigo.txt"),
+ ),
+ trusted_proxies: [],
+ store: None,
+ trees: {},
+ functions: None,
+)
\ No newline at end of file