Instalación desatendida con preseed en Debian

El preseed es el mecanismo mediante el cual el instalador de Debian (debian-installer, también llamado d-i) acepta respuestas a sus preguntas antes de que se formulen. No es un script que ejecuta pasos post-instalación: es una base de datos de respuestas que el instalador consulta durante su propio flujo, como si alguien estuviera sentado frente a la pantalla respondiendo cada diálogo. La diferencia es que ese “alguien” es un archivo de texto que preparaste con antelación.

El mecanismo funciona porque debian-installer usa internamente debconf para gestionar toda su configuración, igual que cualquier paquete de Debian. Cada pregunta tiene una clave (template), un tipo y un valor. Un archivo preseed no es más que una lista de esas claves con los valores que quieres inyectar, en el formato que debconf-set-selections entiende. Cuando el instalador llega a una pregunta cuya clave ya tiene respuesta en el preseed, la salta. Cuando no la tiene, se detiene y espera. Por eso el objetivo es cubrir exactamente el conjunto de preguntas que el instalador haría en tu configuración objetivo.

¿Cuándo lo necesitas? Cuando tienes más de uno o dos servidores físicos que configurar con la misma base. Con tres servidores puedes hacer click a mano. Con treinta, no puedes. Con trescientos, cada instalación manual es también un vector de error: alguien selecciona la partición equivocada, otro elige el locale incorrecto. El preseed convierte la instalación en algo reproducible y auditable: el archivo vive en git, los cambios tienen historial, y todas las máquinas salen exactamente iguales.

Lo que se rompe cuando lo haces mal es costoso de depurar. Si una clave está mal escrita, el instalador simplemente no la reconoce y hace la pregunta en pantalla, bloqueando la instalación desatendida. Si la prioridad de debconf está mal configurada, preguntas de baja prioridad que asumiste que se saltarían aparecen igualmente. Si el particionado preseed tiene una ambigüedad sobre qué disco usar, el instalador puede destruir el disco equivocado sin preguntar. En un despliegue de cien máquinas eso es un problema serio.

# ── Estructura del escenario ──────────────────────────────────────────
# Objetivo: preparar un archivo preseed completo para instalar Debian 12
# (Bookworm) de forma totalmente desatendida. Lo serviremos via HTTP
# desde una máquina ya operativa en la red (192.168.1.10).
# La máquina objetivo tiene un único disco NVMe (/dev/nvme0n1).
#
# PASO 1: Instalar un servidor HTTP mínimo para servir el archivo
# ─────────────────────────────────────────────────────────────────────
apt-get install -y nginx

# Directorio desde donde serviremos el preseed
mkdir -p /var/www/html/preseed

# ── PASO 2: Crear el archivo preseed ─────────────────────────────────
cat > /var/www/html/preseed/bookworm-servidor.cfg << 'EOF'
#### Locale y teclado
d-i debian-installer/locale string es_ES.UTF-8
d-i keyboard-configuration/xkb-keymap select es

#### Configuración de red
# Usar DHCP durante la instalación; la configuración estática
# se aplica después via preseed/late_command o cloud-init/Ansible
d-i netcfg/choose_interface select auto
d-i netcfg/get_hostname string servidor-base
d-i netcfg/get_domain string infra.ejemplo.local

# Evitar que d-i pregunte si la interfaz no tiene DHCP
d-i netcfg/dhcp_failed note
d-i netcfg/dhcp_options select Configure network manually

#### Mirror de Debian
d-i mirror/country string manual
d-i mirror/http/hostname string deb.debian.org
d-i mirror/http/directory string /debian
d-i mirror/http/proxy string

#### Cuenta de root y usuario
# Contraseña de root hasheada (genera con: openssl passwd -6 'tu-contraseña')
d-i passwd/root-password-crypted password $6$rHbGqZvT$ejemplo_hash_aqui_reemplazar
d-i passwd/make-user boolean false   # sin usuario regular; gestionamos con Ansible

#### Reloj
d-i clock-setup/utc boolean true
d-i time/zone string Europe/Madrid
d-i clock-setup/ntp boolean true

#### Particionado
# Usar el disco NVMe completo, esquema LVM, sin cifrado
# PELIGRO: recipe 'atomic' borra TODO el disco sin confirmación
d-i partman-auto/disk string /dev/nvme0n1
d-i partman-auto/method string lvm
d-i partman-lvm/device_remove_lvm boolean true
d-i partman-md/device_remove_md boolean true
d-i partman-lvm/confirm boolean true
d-i partman-lvm/confirm_nooverwrite boolean true
# Recipe 'atomic': / única partición. Para layout personalizado,
# sustituir por una 'recipe' explícita (ver documentación de partman)
d-i partman-auto/choose_recipe select atomic
d-i partman-auto-lvm/guided_size string max
d-i partman/default_filesystem string ext4

# Confirmaciones automáticas de partman: sin estas líneas, partman
# detiene la instalación aunque tengas todo lo demás respondido
d-i partman-partitioning/confirm_write_new_label boolean true
d-i partman/choose_partition select finish
d-i partman/confirm boolean true
d-i partman/confirm_nooverwrite boolean true

#### Selección de paquetes
# Solo instalamos el sistema base + SSH; el resto lo gestiona Ansible
tasksel tasksel/first multiselect standard
d-i pkgsel/include string openssh-server sudo python3
d-i pkgsel/upgrade select full-upgrade
# No enviar datos de uso de paquetes
popularity-contest popularity-contest/participate boolean false

#### GRUB
d-i grub-installer/only_debian boolean true
d-i grub-installer/bootdev string /dev/nvme0n1

#### late_command: se ejecuta en el entorno instalado (chroot) al final
# Aquí inyectamos la clave pública SSH del usuario root para acceso
# inmediato post-instalación desde el nodo de Ansible
d-i preseed/late_command string \
    mkdir -p /target/root/.ssh && \
    chmod 700 /target/root/.ssh && \
    echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... ansible@infra" \
        > /target/root/.ssh/authorized_keys && \
    chmod 600 /target/root/.ssh/authorized_keys && \
    in-target systemctl enable ssh

#### Reinicio automático al finalizar
d-i finish-install/reboot_in_progress note
EOF

# ── PASO 3: Verificar que nginx sirve el archivo ──────────────────────
systemctl reload nginx
curl -s http://192.168.1.10/preseed/bookworm-servidor.cfg | head -5

# ── PASO 4: Arrancar la máquina objetivo con el instalador Debian ─────
# En el menú de arranque del instalador (GRUB de netboot o ISO),
# añadir al kernel estos parámetros (en una línea, sin salto):
#
#   auto=true priority=critical
#   url=http://192.168.1.10/preseed/bookworm-servidor.cfg
#
# Con netboot/PXE, se añaden en el archivo pxelinux.cfg/default:
#
#   LABEL bookworm-auto
#     KERNEL debian-installer/amd64/linux
#     APPEND vga=788 initrd=debian-installer/amd64/initrd.gz \
#            auto=true priority=critical \
#            url=http://192.168.1.10/preseed/bookworm-servidor.cfg \
#            DEBCONF_DEBUG=5
#
# DEBCONF_DEBUG=5 solo durante pruebas: vuelca en /var/log/syslog
# cada pregunta que debconf procesa y si encontró respuesta en el preseed

# ── PASO 5: Herramienta de validación offline ─────────────────────────
# debconf-set-selections puede validar la sintaxis del preseed sin
# necesitar ejecutar una instalación real
apt-get install -y debconf
debconf-set-selections --checkonly /var/www/html/preseed/bookworm-servidor.cfg
# Salida limpia = sintaxis válida. Advertencias = claves desconocidas
# en este sistema (normal para claves específicas de d-i).

Qué hace cada decisión del ejemplo

El bloque de red usa DHCP deliberadamente durante la instalación. No porque la infraestructura vaya a quedarse con DHCP, sino porque el instalador solo necesita conectividad para descargar paquetes. La IP estática definitiva la aplicamos después via Ansible o cloud-init. Mezclar la configuración de red permanente dentro del preseed la hace frágil: si cambias el esquema de direccionamiento, tienes que regenerar el preseed para cada máquina.

Las dos líneas d-i passwd/make-user boolean false y la contraseña de root hasheada reflejan una decisión de seguridad: en un servidor gestionado por Ansible, no necesitas un usuario interactivo. El hash se genera con openssl passwd -6 (SHA-512); nunca pongas contraseñas en claro en un preseed que va a vivir en un servidor HTTP sin autenticación.

El bloque de partman es donde más falla la gente. Las líneas confirm_nooverwrite son distintas de las líneas confirm: la primera responde a la pregunta “hay datos existentes, ¿confirmas?”, la segunda a “¿aplicar los cambios?”. Si falta cualquiera de ellas, partman se detiene. El recipe select atomic es conveniente para servidores donde todo va en /, pero para entornos con requisitos de auditoría que exigen /var/log separado, necesitas escribir una receta explícita de partman, que es un mini-DSL documentado en /usr/share/doc/partman-auto/.

El late_command opera en dos contextos dependiendo de cómo lo escribes: sin prefijo in-target, el comando se ejecuta en el entorno del instalador vivo, con /target montado pero sin estar en chroot. Con in-target, el comando se ejecuta con chroot /target, por lo que las rutas son las del sistema instalado. El ejemplo mezcla ambos deliberadamente: mkdir -p /target/root/.ssh opera desde fuera del chroot (más confiable para crear directorios), mientras que in-target systemctl enable ssh necesita el chroot porque systemctl requiere el árbol de systemd del sistema instalado.

El parámetro priority=critical en la línea de arranque del kernel es el que realmente elimina las preguntas interactivas: instruye a debconf a que solo muestre preguntas de prioridad critical o superior. La mayoría de preguntas del instalador son high o medium, así que con priority=critical desaparecen del flujo interactivo. Si el preseed no tiene respuesta para una pregunta critical, esa sí aparecerá en pantalla porque debconf no puede tomar una decisión de ese peso por cuenta propia. DEBCONF_DEBUG=5 durante las primeras pruebas es imprescindible: sin él, cuando el instalador se detiene a mitad, no sabes qué pregunta falta en el preseed.

La validación con debconf-set-selections --checkonly no puede simular una instalación real, pero detecta errores de sintaxis antes de perder tiempo arrancando una máquina. Las advertencias sobre claves desconocidas son esperables cuando validas en un sistema que no tiene debian-installer instalado como paquete; lo que no puedes ignorar son los errores de formato.

[Ubuntu]: Ubuntu desde 20.04 usa autoinstall con el instalador Subiquity, que tiene un formato YAML completamente distinto (/autoinstall.yaml servido via cloud-init o HTTP). El concepto de inyectar respuestas antes de la instalación es el mismo, pero el mecanismo no es compatible; un archivo preseed de Debian no funciona con Subiquity y viceversa.

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio