# Blueprint de despliegue: Pi-hole + Unbound + Tailscale Estado: **pausado**. Este documento es un plan de ejecucion posterior, no una orden de cambio inmediata. ## Estado real verificado hoy Verificado localmente en `ballbox-first`: - Host real: `ballbox-first` - OS: Debian GNU/Linux 13 (trixie) - Kernel: `6.12.47+rpt-rpi-2712` - LAN real: `wlan0` con `192.168.1.33/24` - Tailscale: `tailscale0` con `100.116.176.16/32` - Ruta por defecto: via `192.168.1.1` por `wlan0` - `https://ballbox-first.emperor-ratio.ts.net/` responde `200` con `server: nginx`. - `https://ballbox-first.emperor-ratio.ts.net:8443/` responde `200`. - `http://100.116.176.16:8082/api/status` responde; el personal-agent esta vivo. - `http://100.116.176.16:8091/api/status` responde con `ok: true`; la agents database esta viva. - `nginx` escucha en `:80`. - `tailscaled` publica `:443` y `:8443` para Tailscale Serve. - `opencode` escucha solo en `127.0.0.1:3967`. - `python3` escucha en `:8082` y `:8091`. - No hay listeners en `:53` ni en `:5335`. - `systemd-resolved` esta `inactive`. - `/etc/resolv.conf` esta generado por Tailscale y usa `nameserver 100.100.100.100`. - Espacio libre real en `/`: ~930 MB. - Cache APT: ~355M. - Journal: ~153M. - `/home`: ~2.6G, con `/home/sebas` ~2.5G y `/home/linuxbrew` ~188M. - `/usr`: ~6.5G, con `/usr/lib` ~4.1G y `/usr/share` ~1.5G. Conclusion operativa: - Hoy **no** hay un DNS activo escuchando en `:53` ni un `unbound` en `:5335`. - El host **si** tiene servicios productivos activos y visibles, incluyendo `nginx` y OpenCode. - La LAN real esta en `wlan0` y con IP DHCP dinamica. Para DNS de toda la LAN, eso es inaceptable hasta fijar IP o reserva DHCP. - `systemd-resolved` no es el actor relevante aqui; el resolv local del host lo controla Tailscale. - Cualquier plan que meta Pi-hole debe tratar este host como servidor multiproposito en produccion, no como una Raspberry vacia para experimentar. ## Bloqueador actual - El root filesystem esta en ~`930 MB` libres. Ese numero por si solo ya invalida cualquier instalacion seria. - La interfaz LAN actual es `wlan0` con IP dinamica `192.168.1.33`. Mientras eso no sea IP estatica o reserva DHCP, este host no es apto para ser DNS anunciado por router. - `nginx` ya ocupa `:80`, asi que la web por defecto de Pi-hole chocaria con un servicio productivo existente. - Tailscale gestiona `/etc/resolv.conf`; tocar resolucion local de forma descuidada puede romper tanto el host como el comportamiento de Tailscale DNS. Conclusion: **no ejecutar este plan todavia**. Los bloqueos reales son disco escaso, IP LAN no fija y coexistencia delicada con `nginx` y Tailscale DNS. ## Objetivo real Desplegar un resolver DNS con bloqueo de anuncios para la red local, con estas propiedades: - Pi-hole como frontend DNS para clientes. - Unbound como resolver recursivo local. - Opcion de dar servicio tambien a clientes Tailscale. - Sin romper los servicios ya expuestos por la Raspberry Pi. - Con rollback claro si algo sale mal. ## Hallazgos y riesgos del plan anterior El plan anterior tenia varios agujeros operativos: - Ignoraba un posible conflicto en `:80`. Pi-hole suele levantar `lighttpd`; este host ya sirve cosas por `nginx` y no se puede asumir que el puerto este libre. - Mezclaba "LAN-wide" con "Tailscale clients" sin decidir el modelo de escucha de Pi-hole. `DNSMASQ_LISTENING=local` puede no cubrir bien Tailscale. - Asumia que bastaba con tocar `systemd-resolved`, pero no comprobaba si ya habia otro proceso escuchando en `:53`. - Usaba `curl | bash` como si eso fuera una practica seria sin inspeccion previa ni plan de rollback. - No definia criterio de exito, criterio de abortar ni pasos de validacion previos al cambio en el router. - No trataba el riesgo principal de negocio: si el router apunta solo a este DNS y el host falla, te tumbas la resolucion DNS de toda la LAN. - No separaba claramente cambios reversibles de cambios con impacto de red. - No trataba que en este host `systemd-resolved` no es el resolver local dominante; `resolv.conf` lo pisa Tailscale. - No trataba que la LAN real esta sobre `wlan0` con DHCP dinamico, lo cual es pesimo cimiento para un DNS central de red. ## Decisiones de diseno Antes de ejecutar, fijar estas decisiones: 1. La IP LAN del host debe ser estatica o reserva DHCP permanente. 2. Idealmente, el DNS central de la LAN no deberia depender de `wlan0`; al menos debe aceptarse ese riesgo si se sigue por Wi-Fi. 3. Pi-hole no debe apropiarse del puerto `:80` si `nginx` ya lo usa. 4. El host usa `resolv.conf` gestionado por Tailscale; cualquier cambio en DNS local debe respetar eso o reemplazarlo conscientemente. 5. El cambio en el router se hace al final, solo despues de validar localmente. 6. Si se habilita Tailscale, debe hacerse de forma explicita y comprobando que no se expone DNS mas alla de lo previsto. 7. Si se usa este host como unico DNS del router, se acepta que pasa a ser infraestructura critica. ## Estado actual observado - Espacio libre en `/`: ~930 MB. - Uso grande en primer nivel: - `/usr`: ~6.5G - `/var`: ~2.6G - `/home`: ~2.6G - Cache APT: ~355M. - `systemd-journal`: ~153M. Conclusion: **no instalar nada** hasta recuperar espacio. Con menos de 1 GB libre, una combinacion de `apt`, logs y paquetes puede dejar el sistema en estado mediocre o roto. ## Criterios de avance y abortado Avanzar solo si se cumplen todos: - `>= 2 GB` libres en `/` como minimo; preferible `>= 4 GB`. - La IP LAN del host esta fijada. - No hay proceso no deseado escuchando en `:53`. - Se entiende y acepta el impacto de que la LAN vaya por `wlan0`. - Se decide explicitamente que hacer con la web de Pi-hole: - deshabilitarla, o - moverla a otro puerto, o - integrarla detras de `nginx` mas tarde. Abortar si ocurre cualquiera de estos: - `:53` ya esta ocupado por un servicio que no quieres tocar. - `:80` esta ocupado y el instalador pretende levantar `lighttpd` ahi. - La IP LAN cambia o depende de DHCP volatil. - El host sigue con menos de `2 GB` libres. - No entiendes o no controlas como Tailscale gestiona `/etc/resolv.conf` en esta maquina. - No puedes validar `Unbound` localmente antes de tocar el router. ## Fase 0: Preflight serio ### Inventario de puertos y servicios ```bash sudo ss -lntup | rg ':(53|80|443|5335)\s' || true sudo systemctl --type=service --state=running | rg 'resolved|nginx|lighttpd|unbound|pihole' || true ``` Objetivo: - Confirmar si `nginx` ya ocupa `:80`. - Confirmar si `systemd-resolved` o cualquier otro servicio ocupa `:53`. - Confirmar si `:5335` esta libre para `unbound`. Dato ya verificado hoy: - Desde fuera del host, `:53/tcp` y `:5335/tcp` no responden en la Tailscale IP. - Eso **no demuestra** que esten libres localmente en LAN o loopback; solo demuestra que hoy no estan expuestos por ese camino. ### Inventario de red ```bash ip -4 addr show ip route hostname -I tailscale ip -4 2>/dev/null || true ``` Objetivo: - Identificar la interfaz LAN real. No asumir `eth0` a ciegas. - Confirmar IP LAN estable. - Separar claramente IP LAN de IP Tailscale. ### Inventario de disco ```bash df -h / sudo journalctl --disk-usage sudo du -xhd1 /home sudo du -xhd1 /usr sudo du -sh /var/cache/apt /var/cache/apt/archives /var/log/journal 2>/dev/null || true ``` Nota: el dato real de hoy sigue siendo ~930 MB libres. Ya no es un supuesto; es un bloqueo operativo actual. ## Fase 1: Recuperar espacio antes de tocar servicios ### Acciones de bajo riesgo ```bash sudo apt-get clean sudo journalctl --vacuum-size=50M df -h / ``` ### Si sigue justo - Limpiar primero en `/home` o mover ficheros grandes fuera del rootfs. - No borrar cosas en `/usr` "porque pesan" sin identificar el paquete propietario. - No seguir hasta superar el umbral minimo definido. ## Fase 2: Resolver conflicto de DNS local ### Comprobar quien usa `:53` ```bash sudo ss -lntup | rg ':53\s' || true sudo ss -lnup | rg ':53\s' || true ``` ### Estado real de este host - `systemd-resolved` esta inactivo. - No hay listener actual en `:53`. - `/etc/resolv.conf` no apunta al stub de `systemd-resolved`; esta generado por Tailscale. Comprobarlo: ```bash systemctl is-active systemd-resolved || true readlink -f /etc/resolv.conf sed -n '1,20p' /etc/resolv.conf ``` Implicacion: - El conflicto tipico con `127.0.0.53` **no es hoy el problema principal**. - El problema real es no romper la resolucion local gestionada por Tailscale mientras despliegas un DNS para otros clientes. - No asumir que hay que tocar `resolved.conf`; en este host probablemente no hace falta y podria ser ruido innecesario. ## Fase 3: Decidir la web de Pi-hole antes de instalar Este paso es obligatorio. El plan anterior fallaba aqui. ### Opcion recomendada - Mantener `nginx` como esta. - No depender de que Pi-hole levante su propia web en `:80`. - Si se quiere la UI de Pi-hole, moverla a otro puerto o dejarla para una integracion posterior. ### Comprobacion minima ```bash sudo ss -lntup | rg ':80\s' || true ``` Si `:80` ya esta ocupado por `nginx`, **no ejecutar una instalacion de Pi-hole que asuma `lighttpd` por defecto sin preparar antes la excepcion**. Dato ya verificado hoy: la homepage sigue respondiendo por `nginx`, asi que este riesgo sigue vivo y no es hipotetico. ## Fase 4: Variables de despliegue Obtener variables reales del host: ```bash LAN_IF="wlan0" LAN_CIDR="$(ip -4 -o addr show dev "$LAN_IF" | awk '{print $4}')" LAN_IP="${LAN_CIDR%%/*}" TS_IP="$(tailscale ip -4 2>/dev/null || true)" printf 'LAN_IF=%s\nLAN_IP=%s\nLAN_CIDR=%s\nTS_IP=%s\n' "$LAN_IF" "$LAN_IP" "$LAN_CIDR" "$TS_IP" ``` Valor real hoy: `LAN_IF=wlan0`, `LAN_IP=192.168.1.33`, `TS_IP=100.116.176.16`. Antes de seguir: - Verificar que `wlan0` seguira siendo la interfaz LAN objetivo. - Fijar IP estatica o reserva DHCP en el router. - Aceptar conscientemente que basar el DNS central en Wi-Fi es menos resiliente que hacerlo por Ethernet. ## Fase 5: Preparar configuracion de Pi-hole Principios: - Pi-hole escuchando para clientes LAN. - Unbound solo en loopback. - Logging reducido para proteger microSD. - Nada de asumir que la configuracion por defecto sirve para LAN y Tailscale a la vez. Crear `/etc/pihole/setupVars.conf`: ```bash sudo mkdir -p /etc/pihole sudo tee /etc/pihole/setupVars.conf >/dev/null <