Archivos de Categoría: Uncategorized

NFS en Debian: Molaba el siglo pasado

NFS es uno de esos protocolos que, a la chita callando, ahí sigue. Ciertamente, su interoperabilidad no es la de la familia SMB que Samba resolvió hace años, tras grandes esfuerzos que, sin duda, en ningún caso se dedicaron a su página web. Y en los tiempos que vivimos, en que el espacio tiene un coste tan ridículo, replicar datos al vuelo es tan barato y eficaz, y accederlos por protocolos más «web» es tan fácil, NFS se ha quedado con cuatro parcelitas de uso. Pero son sus cuatro parcelitas, y en ellas no hay nada mejor. Por ejemplo, si un servidor ha de acceder a los archivos de otro, especialmente si está cerquita, y posiblemente también modificarlos, de una manera interactiva y sin grandes complicaciones.

La implantación de NFS en Debian hereda costumbres de tiempos remotos. La página man de nfsd fue modificada por última vez en 2014, y es de las más modernas. No es de extrañar que lo que rodea a nfs, recuerde tiempos de redes confiables, en que lo más peligroso era el entusiasmo de un estudiante descubriendo el mundo de mover bits de un lado para otro.

NFS en Debian, por defecto, escucha en todos los interfaces de red que pilla. Que, como caso general, está bien; pero si lo que pretendemos es que dos servidores que son accesibles públicamente se hablen por una discreta red trasera, y sin exponer al público la posibilidad de buscarle las cosquillas, parecería que nuestro único recurso es poner delante un cortafuegos (unas reglas de nftables, por ejemplo).

Ah, pero la venerable página de nfsd dice que el servicio acepta -H seguida de una IP para escuchar. Guay, ¿y cómo se hace eso a la Debian?

Pues al menos hasta Debian 11, nfs se configura poniendo valores en los tradicionales ficheros de variables de /etc/default. Simple y efectivo. En el caso del servidor nfs, la apropiada es /etc/default/nfs-kernel-server. Ahora bien, ¿cuál es la variable?

Pues una que no está:

RPCNFSDOPTS="-H <IP>"

Por algún motivo, la variable, que es RPCNFSDOPTS, no está. Normalmente, todas las variables que tienen algo que ver con un fichero de variables de /etc/defaults están presentes, aunque estén vacías, y tienen algún comentario que explica su función.

Encontré en Server Fault a otro pobre y desamparado administrador de sistemas preguntándose cómo es posible que no hubiera forma de hacer esto, así que por lo que me costaba, lo dejé documentado para la posteridad.

Así en resumen, el fichero /etc/default/nfs-kernel-server, con RPCNFSDOPTS y todo lo demás, lo lee /usr/lib/systemd/scripts/nfs-utils_env.sh. Este incluye el valor de RPCNFSDOPTS en RPCNFSDARGS y manda esta y otras variables a /run/sysconfig/nfs-utils; de ahí lo lee el fichero unit de arranque de nfsd, /lib/systemd/system/nfs-server.service, y al arrancar /usr/sbin/rpc.nfsd, le pasa como parámetro el contenido de RPCNFSDARGS. Y el arzobispo de Constantinopla… Bueno, que me desvío.

Hacer que un servicio escuche solamente donde necesita, si es que tal cosa es posible, es la primera línea de defensa; anterior, incluso, al cortafuegos. Al fin y al cabo, si no hay posibilidad de comunicación, no hay posibilidad de intrusión. Y como rpc.nfsd lo permite, me he permitido sugerir a Debian que documente el uso de RPCNFSDOPTS en /etc/default/nfs-kernel-server.

Actualizado el día siguiente a la publicación de este artículo lo siguiente. Siempre tan abnegados, respondieron a mi sugerencia superando mis expectativas:

Since version 1:2.6.1-1 /etc/nfs.conf settings can be done, or in
dropins in /etc/nfs.conf.d/ so closing this bug with that version.
Starting there you can set the host= and other parameters in the
configuration file in the [nfsd] stanza.

Salvatore Bonaccorso, mantenedor de nfs-kernel-server

Bookworm tiene ya la versión 1:2.6.2-1+b1. Así que quien no pueda vivir sin esto, que corra esa versión, que en el momento de escribir esto, es la testing. Con la variable puesta como he descrito, por ahora, es suficiente; solo hay que acordarse de volver sobre este asunto cuando se empiece a instalar nfs en bookworm.

Broadcom BCM57800: Mola, salvo cuando se la pega

El chip BCM57800 es uno de los que, en el kernel de Linux, van gestionados por el controlador bnx2x. También es el que lleva una de las tarjetas rNDC de Dell para los r630 más populares, con 2 SFP+ y 2 «cobres» de 1G.

Estas tarjetas con BCM57[0-9][0-9][0-9]* «Everest» son uno de esos productos con una historia meandrosa. Los chips fueron diseñados por Broadcom, que los anunció en Diciembre de 2010, y en poco tiempo usados por Brocade en varios productos. En 2014, QLogic compró a Brocade el negocio de Fibre Channel y adaptadores de red, incluyendo tarjetas que usaban estos chips, cuyo controlador pasó a mantener como atestigua el .h del bnx2x. En Junio de 2016, QLogic fue adquirida por Cavium, que a su vez fue adquirida por Marvell. Por su parte, Brocade fue comprada por Broadcom en 2016. Las tarjetas con chips BCM57 eran de Cavium cuando Dell los empezó a utilizar, y de hecho algunos documentos técnicos en la web de Dell llevan la marca de Cavium.

Toda esta historia implica curiosidades como que en el código fuente, el copyright de Broadcom es anterior al de Qlogic.

Con toda esta florida historia de adquisiciones y pases, no es de extrañar que el bnx2x y sus hermanos hayan pasado por tantas manos que la calidad, al final, se haya resentido. Y esto es lo que parece haber ocurrido para que en algunas versiones, cuando reciben un poco de caña (digamos, una migración masiva de contenedores en un cluster de Proxmox), la red se caiga con todo el equipo:

May 30 20:35:20 hv149 kernel: [ 1272.998710] RAX: 0000000000000000 RBX: 0000000000000004 RCX: 0000000000000006
May 30 20:35:20 hv149 kernel: [ 1273.021980] call_timer_fn+0x32/0x130
May 30 20:35:20 hv149 kernel: [ 1273.052021] RDX: 0000012862241f85 RSI: 0000000037a6f674 RDI: 0000000000000000
May 30 20:35:20 hv149 kernel: [ 1273.075905] ---[ end trace 179564100f322278 ]---
May 30 20:35:20 hv149 kernel: [ 1273.099027] bnx2x: [bnx2x_panic_dump:1021(eno1)] indexes (0x0 0xe97e 0x0 0x0 0x0 0x186d 0x0 0x0)pf_id(0x0) vf_id(0xff) vf_valid(0x0) vnic_id(0x0) same_igu_sb_1b(0x1) state(0x1)
May 30 20:35:20 hv149 kernel: [ 1273.158754] INDEX[6] flags (0x3) timeout (0xc)
May 30 20:35:20 hv149 kernel: [ 1273.218986] bnx2x: [bnx2x_panic_dump:1015(eno1)] run indexes (0x7d14 0x0)
May 30 20:35:20 hv149 kernel: [ 1273.334124] bnx2x: [bnx2x_panic_dump:1004(eno1)]fp6: tx_pkt_prod(0x0) tx_pkt_cons(0x0) tx_bd_prod(0x0) tx_bd_cons(0x0) *tx_cons_sb(0x0)
May 30 20:35:20 hv149 kernel: [ 1273.365957] bnx2x: [bnx2x_panic_dump:1004(eno1)]fp7: tx_pkt_prod(0x0) tx_pkt_cons(0x0) tx_bd_prod(0x0) tx_bd_cons(0x0) *tx_cons_sb(0x0)
May 30 20:35:20 hv149 kernel: [ 1273.374863] SM[1] __flags (0x0) igu_sb_id (0x9) igu_seg_id(0x0) time_to_expire (0x4ad6f466) timer_value(0xff)
May 30 20:35:20 hv149 kernel: [ 1273.377750] INDEX[1] flags (0x2) timeout (0x6)
May 30 20:35:20 hv149 kernel: [ 1273.440571] bnx2x: [bnx2x_mc_assert:736(eno1)]XSTORM_ASSERT_INDEX 0x12 = 0x0002ef76 0x00000000 0x0400000d 0x000200

El caso es que esto no le hace mucha gracia a los contenedores ni, en cascada, a los cientos de servicios que hay en ellos corriendo; por lo que parece conveniente investigar un poco el problema.

El bnx2x sigue recibiendo un activo mantenimiento, y no pasa un mes sin que reciba una actualización. El comportamiento problemático ha sido observado en los kernel 5.4.78 (Noviembre de 2020) y 5.4.189 (Abril de 2022). Pero la rama 5.4, incluso en este último kernel, a pesar de su reciente factura, sigue llevando una versión de bnx2x fechada en 2014, y bnx2x ha recibido importantes actualizaciones en todas las versiones principales desde entonces. De hecho, ha dejado de tener su propio número de versión; su código fuente está en github, en el repo del bueno de Linus, desde hace más de diez años; si bien parece que le ha costado una temporada de su tortuosa historia armonizarse completamente.

Pero a día de hoy, las actualizaciones van siendo cada vez más tranquilas y de mantenimiento progresivo. En la rama 5.15, que en el momento de escribir este artículo es la más actual de mantenimiento «longterm», ha recibido actualizaciones en media docena de versiones. De todas, solamente llama la atención que en la 5.15.38, «While handling PCI errors (AER flow) driver tries to disable NAPI [napi_disable()] after NAPI is deleted [__netif_napi_del()] which causes unexpected system hang/crash.» Que es una bonita forma de decir que vaya, que fíjate, que el sistema entero se la puede pegar en cualquier momento; pero solamente después de que ya haya habido un error de PCI, es decir, que es un caso bastante raro. Todo lo demás son cosas del estilo de «Fix unused-function warning«, reorganizaciones, limpieza y usar las características más modernas del kernel.

TL;DR: No es de esperar que esto se la pegue tras actualizar a Proxmox 7.2, que lleva el kernel 5.15.35. Siguiente problema.

megacli: Mola, relativamente

Megacli sirve para todo, y no hay nada en un chip RAID de LSI/Avago/Comesellamenhoy que no pueda configurar. Esas son las buenas noticias.

Las regulares… Pues son chips complicadillos y por tanto la línea de comandos de megacli, tiene su complejidad. Incluso antes de que a alguien se le ocurrieran esas graciosas abreviaturas.

El ejercicio de hoy era sacar unos discos de una venerable 2108 de un Supermicro otoñal, y meterlos en un Dell r620 con su H310.

Al meterlos, hacen blip-blip y todo parece ir bien, pero… No aparecen por ninguna parte en /dev/pordondesemire.

Megacli sí los ve, o mejor dicho, su primo megaclisas-status: Unconfigured(good). No parece mala señal.

Bueno, pues solo hay que cambiarlos a JBOD para poder dárselos a Ceph, ZFS o a cualquier otra cosa con o sin Z, ¿no?.

No…

megacli -PDMakeJBOD -PhysDrv[32:6] -aALL
Adapter: 0: Failed to change PD state at EnclId-32 SlotId-6.

Vaya. Que no se puede. Aguanta y tira.

¿Y por qué no se puede? Je. Porque vienen con la meadit… Quiero decir, con la firma del RAID precedente. Podrían tener el detalle de decirlo, pero vaya, no, no lo tienen. Bueno.

¿Y tiene solución? Claro. La artesanal, que es meter el disco en alguna parte que no sepa nada de RAIDs y ciscarse la firma, por ejemplo con un dd; o la megacli, que ya que estamos…

root@lab.tecnocratica ~ # megacli -CfgForeign -Scan -a0

There are 1 foreign configuration(s) on controller 0.

Pues al carajo con ella, ¿no?

root@lab.tecnocratica ~ # megacli -CfgForeign -Clear -a0

Foreign configuration 0 is cleared on controller 0.

Pues a la carga de nuevo:

root@lab.tecnocratica ~ # megacli -PDMakeJBOD -PhysDrv[32:6] -a0

Adapter: 0: EnclId-32 SlotId-6 state changed to JBOD.

Pues como siempre, tras un rato de rompimiento de cabeza, eso era todo. La verdad es que estaría bien que si alguien busca por «controladora perc o raid de dell o de supermicro o de quien carajo sea no ve discos que vienen de otra controladora» encontrase esta receta, que le haría ahorrar algo de tiempo. Se intentará…

Ansible y autenticación mysql

Trabajando con una Debian o Ubuntu por defecto, la autenticación para el usuario root de MySQL/MariaDB está tan lejos como anteponer sudo a lo que sea que queramos hacer desde el shell.

Esto es, obviamente, enormemente práctico. ¿Queremos crear una base de datos? sudo mysql. ¿Examinar una base de datos? sudo mysqldump basedepatos. Etcétera. Sin necesidad de recordar ninguna clave o mantener una insegura.

Al pasar alguna de estas actividades a Ansible, sin embargo, tenemos todas las papeletas de encontrarnos de morros con esto:

fatal: [mizervio]: FAILED! => {"changed": false, "msg": "unable to connect to database, check login_user and login_password are correct or /root/.my.cnf has the credentials. Exception message: (1698, \"Access denied for user 'root'@'localhost'\")"}

¿Esto qué es? Bueno, pues que la magia de la que hablaba al principio es posible simplemente porque usamos el socket de autenticación… Y eso lo podemos hacer porque somos root. Este es el socket de autenticación de una Debian 10:

srwxrwxrwx 1 mysql mysql    0 sep  9 18:39 /run/mysqld/mysqld.sock

Pero Ansible no usa el socket de autenticación. A no ser que se lo digamos explícitamente. Por qué han elegido este comportamiento, en lugar de ser consistentes con la línea de comandos, probablemente tiene que ver con pensar en ello demasiado o demasiado poco, pero no seré yo quien arrample contra unos tipos a los que, la verdad, estoy más que agradecido por haber creado esta máquina de ahorrar tiempo y dolores de cabeza.

Además, la solución es fácil. Se coge la receta uno, y se añade en la tarea en que se llame a mysql_db:

login_unix_socket: /run/mysqld/mysqld.sock

Listo, resuelto todo. De nada.

ixgbe failed to load because an unsupported SFP+ or QSFP module type was detected

En la más pura y molesta tradición de Cisco, ahora el controlador que lleva el kernel (para valores de «ahora» cercanos a 4.19) para los chips de red de Intel (disculpad la palabrota), solamente quiere usar SFPs que hayan probado anteriormente.

Si tienes un SFP de «su» marca, todo va bien. Si no es de «su» marca, ya puede ser el mejor del mercado, que el controlador decide que a la mierda:

ixgbe 0000:01:00.0: failed to load because an unsupported SFP+ or QSFP module type was detected.

Señor, dame paciencia que para mala leche ya tenemos a Linus.

Total. Que, si nos pasa eso, tenemos dos posibles soluciones:

  • La buena, que es decirle al controlador que se verifique al que lo programó:

echo "options ixgbe allow_unsupported_sfp=1" > /etc/modprobe.d/ixgbe.conf

Si nuestro kernel lleva el controlador de marras compilado dentro, tendremos que pasarlo por la línea de comandos del kernel. Prosaicamente, añadir donde toque (/etc/default/grub, /etc/grub.d/ o lo que tenga nuestra distro) ixgbe.allow_unsupported_sfp=1.

Y con eso, y si acaso el juicioso uso de rmmod ixgbe ; insmod ixgbe, estaremos de nuevo en marcha, como si no se les hubiera ocurrido… Bueno, no sigo, que me enciendo.

ModSecurity con Nginx en Buster

Tras pasarlo regular para poner a marchar un Nginx como proxy inverso con ModSecurity, he pensado que sería buena idea volcar en alguna parte mis notas para ahorrar quebraderos de cabeza a otros sufridos informáticos.

Buster lleva la librería ModSecurity 3 ya empaquetada. No hay más que instalarla con apt.

Lo que da un poco de problema es compilar el módulo de nginx. Esto, por ahora, hay que hacerlo a mano.

Primero, instalar nginx con apt.

Luego, bajar el fuente de la versión exacta. En el momento de escribir esto:

dpkg -l nginx
Desired=Unknown/Install/Remove/Purge/Hold
| Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend
|/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad)
||/ Name Version Architecture Description
+++-==============-============-============-==========================================
ii nginx 1.14.2-2 all small, powerful, scalable web/proxy server

Por tanto:

wget -O - http://nginx.org/download/nginx-1.14.2.tar.gz | tar xzf -

También necesitamos el conector de ModSecurity con nginx:

git clone https://github.com/SpiderLabs/ModSecurity-nginx.git

Ahora, ojo a esto. Aparte de build-essential, hacen falta otras cosas. La más enrevesada es libpcre3-dev. Sin esto, no compila el módulo de nginx. Pero es que además hay que configurar el conector para que la use. Es decir, ya en el directorio del fuente de nginx (nginx-1.14.2 en este caso):

./configure --with-compat --add-dynamic-module=../ModSecurity-nginx --without-http_rewrite_module --without-http_gzip_module --with-pcre

Hecho esto, ya no hay más que hacer make.

Otra cosa que despista un poco es dónde pone Debian las cosas. El módulo compilado lo podemos encontrar en objs/ngx_http_modsecurity_module.so y casi cualquier guía que encontremos por ahí dice de ponerlo en /etc/nginx/modules. Pero nop, eso no es el estilo Debian. Para hacerlo al estilo Debian, hay que soltarlo en /usr/lib/nginx/modules/ y seguidamente crear la configuración que lo carga. Verbigracia:

echo "load_module modules/ngx_http_modsecurity_module.so;" > /tmp/mod-security.conf && sudo mv /tmp/mod-security.conf /etc/nginx/modules-available/

cd /etc/nginx/modules-enabled/

sudo ln -s ../modules-available/mod-security.conf 50-mod-security.conf

sudo service nginx restart

Y nuestros esfuerzos se verán recompensados con un:

nginx: [emerg] module "/usr/share/nginx/modules/ngx_http_modsecurity_module.so" is not binary compatible in /etc/nginx/modules-enabled/50-mod-security.conf:1

Mierda.

Total. Que vamos a ver cómo está construído el nginx del paquete Debian:

nginx -V

Esto suelta el chorizo que en su día le pasaron al nginx al compilarlo para empaquetarlo. Y nos dice que:

configure arguments: --with-cc-opt='-g -O2 -fdebug-prefix-map=/build/nginx-sWHVb6/nginx-1.14.2=. -fstack-protector-strong -Wformat -Werror=format-security -fPIC -Wdate-time -D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-z,relro -Wl,-z,now -fPIC' --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=/var/log/nginx/error.log --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --modules-path=/usr/lib/nginx/modules --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-debug --with-pcre-jit --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_v2_module --with-http_dav_module --with-http_slice_module --with-threads --with-http_addition_module --with-http_geoip_module=dynamic --with-http_gunzip_module --with-http_gzip_static_module --with-http_image_filter_module=dynamic --with-http_sub_module --with-http_xslt_module=dynamic --with-stream=dynamic --with-stream_ssl_module --with-stream_ssl_preread_module --with-mail=dynamic --with-mail_ssl_module --add-dynamic-module=/build/nginx-sWHVb6/nginx-1.14.2/debian/modules/http-auth-pam --add-dynamic-module=/build/nginx-sWHVb6/nginx-1.14.2/debian/modules/http-dav-ext --add-dynamic-module=/build/nginx-sWHVb6/nginx-1.14.2/debian/modules/http-echo --add-dynamic-module=/build/nginx-sWHVb6/nginx-1.14.2/debian/modules/http-upstream-fair --add-dynamic-module=/build/nginx-sWHVb6/nginx-1.14.2/debian/modules/http-subs-filter

Vale

Pues nada, paciencia y vamos a ver.

make clean

./configure --add-dynamic-module=../ModSecurity-nginx --with-cc-opt='-g -O2 -fdebug-prefix-map=/build/nginx-sWHVb6/nginx-1.14.2=. -fstack-protector-strong -Wformat -Werror=format-security -fPIC -Wdate-time -D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-z,relro -Wl,-z,now -fPIC' --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=/var/log/nginx/error.log --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --modules-path=/usr/lib/nginx/modules --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-debug --with-pcre-jit --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_v2_module --with-http_dav_module --with-http_slice_module --with-threads --with-http_addition_module --with-http_geoip_module=dynamic --with-http_gunzip_module --with-http_gzip_static_module --with-http_image_filter_module=dynamic --with-http_sub_module --with-http_xslt_module=dynamic --with-stream=dynamic --with-stream_ssl_module --with-stream_ssl_preread_module --with-mail=dynamic --with-mail_ssl_module

make

sudo cp objs/ngx_http_modsecurity_module.so /usr/lib/nginx/modules/ngx_http_modsecurity_module.so

sudo service nginx start

Los otros –add-dynamic-module se pueden omitir. Y ya estamos listos.

Configurar un proxy inverso con ModSecurity es otra historia y debe ser contada en otra ocasión.

Debian live a medida

En Github se pueden encontrar excelentes instrucciones para instalar una Debian Stretch con ZFS en la raíz. ZFS no está disponible directa y cómodamente en el instalador de Debian porque las licencias de Linux y de ZFS no son compatibles, por lo que es necesario <suspiro>hacerlo a mano</suspiro>.

En fin. La forma más normal, como se puede leer, es arrancar con una Debian «live» y seguidamente hacer un montón de cosas a mano. Esto es muy instructivo, pero cuando a uno le esperan una pila de hipervisores, que pesan más que uno y hasta algunos opinan que valen más, en todos los cuales hay que instalar una ZFS en raíz… Es de recibo buscar un método algo más automático.

El primer paso es automatizar un poquejo las acciones con un script en bash. Esto es bastante agradecido ya; no hay más que copiar y pegar, y la instalación se hace sola (más o menos).

Pero arrancar con una Debian Live tiene sus cosillas, como que hay que instalar una tonelada de software todas y cada una de las veces. Entre otras cosas, el zfs va en dkms y depende de un montón de cosas.

Así que, redondeando, decidí hacerme una Debian Live a medida. Esto no es tan difícil, especialmente si alguien lo ha hecho antes y ha vivido para documentarlo, como por ejemplo ha hecho Will Haley con este excelente artículo. Partiendo del mismo, he hecho unas pocas adaptaciones y esto es lo que me funciona, para hacer una ISO de Debian Stretch (live) especialmente para instalar Debian con ZFS en raíz, utilizando una Debian Stretch.

En el artículo de Will Haley también explica cómo generar un pincho USB. Pero como la ISO me soluciona la papeleta (porque uso iDRAC, IPMIs, iLOs y otras cosas que comienzan con i), aquí me he quedado.

La cosa empieza instalando en la Debian en que vayamos a trabajar lo necesario:

sudo apt install debootstrap squashfs-tools xorriso grub-pc-bin grub-efi-amd64-bin mtools

Nos creamos un directorio (voy a usar el mismo nombre que Will Haley para no liarnos) y algunos subdirectorios que usaremos más adelante:

mkdir -p LIVE_BOOT/{scratch,image/live}

Nos montamos una Debian básica en ese directorio:

sudo debootstrap --arch=amd64 --variant=minbase stretch LIVE_BOOT/chroot http://deb.debian.org/debian/

En lugar de http://deb.debian.org/debian, podemos usar nuestro mirror favorito si tenemos uno. Igualmente, la arquitectura puede ser i386 o lo que necesitemos.

Nos vamos al chroot:

sudo chroot LIVE_BOOT/chroot

Elegimos un nombre para nuestra Debian «viva» y lo ponemos:

echo "viva" > /etc/hostname
echo "127.0.1.1 viva" >> /etc/hosts

Le instalamos un kernel y lo necesario para el arranque:

apt install --no-install-recommends linux-image-amd64 live-boot systemd-sysv

Añadimos a la masa los paquetes que queramos tener disponibles. Varios de los siguientes son prácticamente una necesidad para poder tener red y esas cosillas:

sudo apt install curl openssh-client openssh-server iproute2 iputils-ping less sudo emacs-nox

Y si queremos usar esta imagen para instalar un ZFS en raíz, podemos ir metiendo algunos paquetes que necesitaremos. Desafortunadamente, zfs-dkms no se puede compilar en un chroot (ni idea de por qué), pero al menos se puede dejar instalado:

sudo apt install debootstrap gdisk dpkg-dev linux-headers-amd64
sudo apt install zfs-dkms

Limpiamos:

sudo apt clean

Creamos un usuario si nos apetece y le damos sudo. O bien simplemente ponemos una clave a root:

sudo useradd -m -s /bin/bash debian
sudo passwd debian
sudo passwd root
sudo visudo

Y nos salimos del chroot

exit

Ya tenemos nuestro entorno listo; ahora hacemos con él un sistema de ficheros:

sudo mksquashfs LIVE_BOOT/chroot LIVE_BOOT/image/live/filesystem.squashfs -e boot

Copiamos el kernel y el sistema de ficheros al directorio donde guardamos lo que irá en la imagen:

sudo cp LIVE_BOOT/chroot/boot/vmlinuz-* LIVE_BOOT/image/vmlinuz
sudo cp LIVE_BOOT/chroot/boot/initrd.img-* LIVE_BOOT/image/initrd

Ahora vamos a crear la configuración de GrUB. El comando search lo que hace es buscar un sistema de ficheros que contenga el fichero /DEBIAN_CUSTOM que vamos a crear seguidamente. Lo podríamos haber llamado /Susana o cualquier otra cosa. El caso es que el sistema de ficheros que pesque con ese fichero creado es el que va a llamar raíz:

cat <<'EOF' > LIVE_BOOT/scratch/grub.cfg
search --set=root --file /DEBIAN_CUSTOM
insmod all_video
set default="0"
set timeout=5
menuentry "Debian Live" {
linux /vmlinuz boot=live quiet nomodeset
initrd /initrd
}
EOF

Creamos el fichero prometido en su sitio:

touch LIVE_BOOT/image/DEBIAN_CUSTOM

Creamos la imagen UEFI con GrUB:

sudo grub-mkstandalone --format=x86_64-efi --output=LIVE_BOOT/scratch/bootx64.efi --locales="" --fonts="" "boot/grub/grub.cfg=LIVE_BOOT/scratch/grub.cfg"

Creamos la imagen de arranque UEFI, de 16 bits de toda la vida (al menos de la vida del PC):

cd LIVE_BOOT/scratch
dd if=/dev/zero of=efiboot.img bs=1M count=10
mkfs.vfat efiboot.img
mmd -i efiboot.img efi efi/boot
mcopy -i efiboot.img ./bootx64.efi ::efi/boot/
cd ../..

Y una imagen de arranque para BIOS:

sudo grub-mkstandalone --format=i386-pc --output=LIVE_BOOT/scratch/core.img --install-modules="linux normal iso9660 biosdisk memdisk search tar ls" --modules="linux normal iso9660 biosdisk search" --locales="" --fonts=""  "boot/grub/grub.cfg=LIVE_BOOT/scratch/grub.cfg"

Concatenamos la imagen de arranque de CD con el sistema de ficheros que hemos creado:

cat /usr/lib/grub/i386-pc/cdboot.img LIVE_BOOT/scratch/core.img > /tmp/bios.img
sudo mv /tmp/bios.img LIVE_BOOT/scratch/bios.img

Y por fin, crear la ISO:

sudo xorriso -as mkisofs -iso-level 3 -full-iso9660-filenames -volid "DEBIAN_CUSTOM" -eltorito-boot boot/grub/bios.img -no-emul-boot         -boot-load-size 4 -boot-info-table --eltorito-catalog boot/grub/boot.cat --grub2-boot-info --grub2-mbr /usr/lib/grub/i386-pc/boot_hybrid.img -eltorito-alt-boot -e EFI/efiboot.img -no-emul-boot -append_partition 2 0xef ${HOME}/LIVE_BOOT/scratch/efiboot.img     -output "${HOME}/LIVE_BOOT/debian-custom.iso" -graft-points         "${HOME}/LIVE_BOOT/image"  /boot/grub/bios.img=$HOME/LIVE_BOOT/scratch/bios.img  /EFI/efiboot.img=$HOME/LIVE_BOOT/scratch/efiboot.img

Listo para usar. A pasarlo pipa.

Algunas notas actualizadas a finales de 2020:

Todo esto funciona perfectamente con buster, sin más que cambiar «stretch» por «buster» en la llamada a debootstrap.

Estando en el chroot, algunos paquetes más que encuentro útil instalar, para tenerlos disponibles luego:

isc-dhcp-client sudo

Los controladores virtuales en máquinas virtuales

«Modern OS should use virtio-scsi anyways«. Esta cita de Tom, de Proxmox, originalmente en su foro, suena a buena idea. Ya que hemos sustituído una controladora SCSI (SATA para nosotros, los humildes), con su correspondiente disco, por una mera simulación software de la misma y un trocito de un disco más grande, suena correcto que el controlador por el que el virtual pide sus operaciones sea virtual.

Hay varias razones para ello. La más sencilla es que una controladora virtual (una LSI, por ejemplo, que es una de las más populares) no deja de ser un software cuya tarea es interpretar lo que pide el sistema virtualizado, usando unos comandos que se diseñaron pensando en discos que giran y tienen cabezas magnéticas, y convertirlo a lo que el hipervisor ofrece. Esto último, cada vez más, son discos que se parecen mucho más a la memoria RAM que a lo que tenían en mente los que diseñaron el protocolo SCSI para discos. Entonces, podemos librarnos de un montón de trabajo en todos y cada uno de los accesos del virtual al disco si simplemente cogemos lo que el virtual quiere y directamente se lo damos al hipervisor en su lenguaje nativo. Menos uso de recursos, menos código y menos complejo, igual a menos errores. ¿Algún inconveniente?

Claro que sí. La cosa tiene sus inconvenientes. Los aficionados a la retroinformática («mi ERP requiere Windows Server 2003») descubrirán en seguida que los sistemas operativos diseñados hace quince años no llevan controladores para almacenamiento virtualizado. Oh, por supuesto hay solución: No hay más que bajarse la ISO con los últimos controladores, meterla en el CD virtual, e instalar. Si se trata de una máquina que ya existe, solo queda pegar el cambiazo. Ya de paso, podemos meter el controlador de red virtual. Este último mejorará un pelo el rendimiento y uso de recursos de la red, a cambio de… Su propio interface, por supuesto. Ah, ¿que estábamos hablando de una máquina virtual que ya existe? En ese caso, el ejercicio de malabarismo para cambiar uno por otro sin perder la conexión, lo dejo de manos del lector.

Feliz cumpleaños, Víctor.

dehydrated y Apache

Si antes lo digo… Hoy he tenido que instalar dehydrated con Apache.

Igual que con nginx, es la sencillez misma; pero no puedo pasar la oportunidad de dejar testimonio de una configuración de ejemplo con Apache, típicamente guardada (en Debian) en /etc/apache2/sites-available/midominio.mitld.conf:

<VirtualHost *:80>
 ServerAdmin webmaster@localhost
 ServerName midominio.mitld
 DocumentRoot /var/www
 <Directory />
  Options FollowSymLinks
  AllowOverride None
 </Directory>
 <Directory /var/www/>
  Options Indexes FollowSymLinks MultiViews
  AllowOverride None
  Order allow,deny
  allow from all
 </Directory>

 ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
 <Directory "/usr/lib/cgi-bin">
  AllowOverride None
  Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
  Order allow,deny
  Allow from all
 </Directory>

 ErrorLog ${APACHE_LOG_DIR}/midominio.mitld.errores
 LogLevel warn

 CustomLog ${APACHE_LOG_DIR}/midominio.mitld.accesos combined
 Alias /.well-known/acme-challenge /var/lib/dehydrated/acme-challenges/
 <Directory "/var/lib/dehydrated/acme-challenges">
  Require all granted
 </Directory>
</VirtualHost>
<IfModule mod_ssl.c>
<VirtualHost _default_:443>
 ServerAdmin webmaster@localhost
 ServerName midominio.mitld
 DocumentRoot /var/www
 <Directory />
  Options FollowSymLinks
  AllowOverride None
 </Directory>
 <Directory /var/www/>
  Options Indexes FollowSymLinks MultiViews
  AllowOverride None
  Order allow,deny
  allow from all
 </Directory>

 ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
 <Directory "/usr/lib/cgi-bin">
  AllowOverride None
  Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
  Order allow,deny
  Allow from all
 </Directory>

 ErrorLog ${APACHE_LOG_DIR}/midominio.mitld-ssl.errores
 LogLevel warn

 CustomLog ${APACHE_LOG_DIR}/midominio.mitld-ssl.accesos combined

 # SSL Engine Switch:
 # Enable/Disable SSL for this virtual host.
 SSLEngine on

 SSLCertificateFile /var/lib/dehydrated/certs/midominio.mitld/cert.pem
 SSLCertificateKeyFile /var/lib/dehydrated/certs/midominio.mitld/privkey.pem

 <FilesMatch "\.(cgi|shtml|phtml|php)$">
  SSLOptions +StdEnvVars
 </FilesMatch>
 <Directory /usr/lib/cgi-bin>
  SSLOptions +StdEnvVars
 </Directory>
</VirtualHost>
</IfModule>

El resto de la instalación es idéntico a nginx, y también con la salvedad de activar el módulo SSL después de ejecutar dehydrated -c por primera vez y por tanto tener el certificado ya preparado.

En Debian, esto se hace con la sencilla y elegante instrucción

sudo a2enmod ssl

Seguido, si es menester, por

sudo apache2ctl restart

A pasarlo pipa.

Ruby y Python

Muchos, me atrevo a decir casi todos los que trabajamos en empresas relativamente pequeñas, estamos acostumbrados a que, tarde o temprando, hay que hacer de todo. Sin ir más lejos, hoy he cambiado un interruptor de luz de la oficina. Bien, pues eso es la parte de hardware: ahora, vamos con el software.

Hace poco, me empeñé en automatizar ciertas operaciones relacionadas con dominios que se hacían a mano, con el riesgo de retrasos y errores que ello entraña y que, de hecho, han generado más de un alzar de cejas. Es un affaire en el fondo sencillo, pero hay que hablar con tres APIs de tres proveedores diferentes en tres países distintos, que no siguen una norma común más allá de lo habitual del uso de json y la filosofía REST tan de nuestros días. Ah, y generar un XML.

La primera versión, para probar, la hice en Bash a base de curl. Curl es una herramienta potente, pero el manejo de flujos de programa en Bash recuerda demasiado que el inefable señor Bourne (nada que ver con el señor Burns, aunque suene casi igual), todo lo que necesitaba era una shell para hacer cuatro cosas. Bastante lejos ha llegado.

Total, que inspirado por un programita que me enseñó un compañero, me puse a hacer una siguiente versión en Python, sin haberlo usado en la vida anteriormente, usando la metodología que nuestro desarrollador de referencia llamó SODD: Stack Overflow Driven Development. Pues bien, Python fue un lenguaje sumamente fácil de aprender para hacer algo sencillo. Las cosas salen con facilidad y, a pesar de que no pretendo ser un programador, me resultó fluido hacer un programa de 200 líneas que cumple su cometido.

Me he limitado a las librerías que vienen de serie con una instalación normal en Debian, y aún así, ha sido razonablemente sencillo encontrar lo necesario para manipulación de fechas, POST, GET, un poco de números y cadenas.

Luego decidí aprovechar para aprender Ruby, y porté el programita.

Me sucedieron inmediatamente varias cosas:

  1. No entiendo Ruby. Hay cosas que no sé por qué funcionan, y otras que no sé por qué no. Hay partes de la sintaxis que me resultan alienígenas. Esta sensación no la tengo con Python.
  2. Hay cosas extrañamente difíciles de hacer. Un POST, por ejemplo. No he encontrado forma de hacerlo si no es con una gema aparte, o con un bloque de código, o con ambas cosas. También enviar correo se me hace algo aparatoso comparado con Python.
  3. El programa engordó un 15%, especialmente por los bloques necesarios para los POST.
  4. La escritura de xml es mucho más elegante (siempre hablando de las librerías que vienen «de serie»), si bien con una sintaxis algo alienígena, pero elegante y ligera.

Total. Que el programita de los demoniosdominios lo voy a mantener con Python, y le daré otra oportunidad a Ruby cuando tenga que hacer (si llega el caso, que llegará) alguna pequeña aplicación web. Según me ha asegurado el antes aludido, en ese momento las cosas van a favor de Ruby (on Rails). Claro, que también ha dicho que el futuro es Rust… Así que no sé. Estoy por volver a Bash.