sergio/takana
> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…
⎇ 3137d47f165b900042653af6db5513180fe471c8
scripts/mudanza/formatos/caddy.py
History
Who touched what
raw
traducir: CENTRO de traducción con modelo pivote y plugins — N+M en vez de N×M
1"""Plugin de formato: ESCRITOR de Caddy (SDD 29)."""
2import os, sys
3sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
4from formatos import escritor # noqa: E402
5
6
7def _direcciones(s):
8 """En nginx dos `server` con el MISMO nombre se distinguen por su `listen`; en Caddy, por el
9 ESQUEMA de la dirección. Sin traducir eso, los bloques colisionan y caddy rechaza el fichero
10 entero con «ambiguous site definition» — encontrado validando con el binario real, no leyendo."""
11 solo_80 = bool(s.puertos) and all(p == "80" for p in s.puertos)
12 pref = "http://" if solo_80 else ""
13 return ", ".join(pref + n for n in s.nombres) or (":80" if solo_80 else ":443")
14
15
16def _emitir_acciones(acciones, ind):
17 L = []
18 for a in acciones:
19 if a.tipo == "proxy":
20 L.append(f"{ind}reverse_proxy {a.valor.rstrip('/').replace('http://','').replace('https://','')}")
21 elif a.tipo == "archivos":
22 L.append(f"{ind}root * {a.valor}")
23 elif a.tipo == "intentar":
24 L.append(f"{ind}try_files {a.valor}")
25 elif a.tipo == "redir":
26 p = a.valor.split()
27 code = p[0] if p and p[0].isdigit() else ""
28 dest = " ".join(p[1:]) if code else a.valor
29 L.append(f"{ind}redir {dest} {code}".rstrip() if dest else f"{ind}respond {code}")
30 return L
31
32
33@escritor("caddy")
34def escribir(cfg):
35 A = cfg.acta
36 L = [f"# Generado por scripts/mudanza/traducir.py desde una configuración de {cfg.formato_origen}.",
37 "# ⚠ NO es una traducción completa. Abajo va el ACTA de lo que NO se pudo traducir, con su",
38 "# línea de origen. Desplegar esto sin resolverlo es desplegar un servidor incompleto.",
39 ""]
40 for s in cfg.sitios:
41 L.append(f"{_direcciones(s)} {{")
42 L.append(f"\t# desde la línea {s.origen} del {cfg.formato_origen} original")
43 if s.tls.get("cert"):
44 L.append(f"\ttls {s.tls.get('cert','')} {s.tls.get('key','')}")
45 if s.raiz:
46 L.append(f"\troot * {s.raiz}")
47 L += _emitir_acciones(s.acciones, "\t")
48 for r in s.rutas:
49 if r.clase == "regex":
50 # Caddy no tiene un equivalente directo de `location ~`. Se DECLARA en el acta en vez
51 # de emitir algo parecido: un `handle` de prefijo no es lo mismo que una regex, y
52 # parecerse es peor que faltar.
53 A.anota(r.origen, "SIN-TRADUCIR", f"ruta por regex: {r.patron}",
54 "Caddy no tiene equivalente directo de `location ~`; se escribe con "
55 "`@matcher path_regexp` y hay que revisar el orden de evaluación")
56 continue
57 cuerpo = _emitir_acciones(r.acciones, "\t\t")
58 if cuerpo:
59 L.append(f"\thandle {r.patron} {{ # línea {r.origen}")
60 L += cuerpo
61 L.append("\t}")
62 if s.raiz:
63 L.append("\tfile_server")
64 L.append("}")
65 L.append("")
66
67 pend = A.sin_traducir()
68 if pend:
69 L.append("# ══ ACTA: lo que NO se tradujo — hay que resolverlo a mano ══")
70 for e in pend:
71 L.append(f"# línea {e['linea']:>4} {e['directiva'][:70]}")
72 if e["motivo"]:
73 L.append(f"# └─ {e['motivo'][:96]}")
74 return "\n".join(L) + "\n"