// 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//... 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. // 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*"), // 2026-10-21, a pedido explícito del usuario: el crawler de Meta FUERA // del todo, no sólo de las vistas caras. Medido en el log: 22.641 // peticiones desde 48 IPs, y pedía /blame/commit estando PROHIBIDO en // el robots.txt — o sea que no lo lee. Fue el 33,6 % del coste de la // máquina él solo cuando el load estaba en 14,78. ("User-Agent", "*meta-webindexer*"), ("User-Agent", "*meta-externalfetcher*"), // ⚠ `facebookexternalhit` NO se veta a propósito: no es un crawler, // es el que busca la vista previa cuando una PERSONA comparte el // enlace — y lo usa WhatsApp también. Vetarlo rompe las previsualiza- // ciones de los enlaces propios. Eran 45 peticiones, coste ~0. ("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//..., 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//.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*"), // 2026-10-21, a pedido explícito del usuario: el crawler de Meta FUERA // del todo, no sólo de las vistas caras. Medido en el log: 22.641 // peticiones desde 48 IPs, y pedía /blame/commit estando PROHIBIDO en // el robots.txt — o sea que no lo lee. Fue el 33,6 % del coste de la // máquina él solo cuando el load estaba en 14,78. ("User-Agent", "*meta-webindexer*"), ("User-Agent", "*meta-externalfetcher*"), // ⚠ `facebookexternalhit` NO se veta a propósito: no es un crawler, // es el que busca la vista previa cuando una PERSONA comparte el // enlace — y lo usa WhatsApp también. Vetarlo rompe las previsualiza- // ciones de los enlaces propios. Eran 45 peticiones, coste ~0. ("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//..., 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//.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, )