Existen varias estrategias para hacer que un sistema con systemd se inicie en modo sólo lectura. Puede ser algo no tan trivial como parece debido a su esencia paralelista y a que puede no haber un orden único de ejecución de los procesos, tras probar varios métodos, finalmente opté por una solución sencilla y que me funciona en CentOS7.
En systemd se utiliza el archivo /etc/fstab para generar units de tipo mount. Simplemente editando dicho archivo es posible indicar que la partición que contiene el punto de montaje «/» se haga como sólo lectura. Ejemplo de fstab:
/dev/mapper/centos-root / ext3 defaults,ro 1 1 /dev/sda1 /boot ext3 defaults,ro 1 tmpfs /dev/shm tmpfs defaults 0 0 sysfs /sys sysfs defaults 0 0 proc /proc proc defaults 0 0
Parece sencillo, pero no lo es tanto. Esta estrategia, propia de sistemas embebidos, requiere una adecuación de los directorios necesarios en escritura del sistema. Además de /proc y /sys, los cuales ya son volátiles, necesitamos que el home del root, /tmp y /var se vean escribibles tras el arranque se sistema operativo. Una manera de hacerlo es habiendo comprimido antes dichos directorios y descomprimiéndolos sobre unidades RAMdisk en el arranque. Pero además necesitamos que dicha descompresión se haga en un punto suficientemente temprano en el arranque, ya que si no, algunos servicios fallarán al no poder leer y escribir en el tmp y var. Para ello usaremos un servicio que llamaré setupRamdisk.service que se inicie con el sysinit.service y ejecute un script que haga lo necesario.
Primero, un script de preparación de estos directorios en RAMdisk puede ser el siguiente:
# Creación y adecuación de la unidad RAM mknod /dev/ram0 b 1 2 mkdir /RAMDISK mount /dev/ram0 -t tmpfs /RAMDISK mkfs.ext3 -F -m0 -q /dev/ram0 # Movemos el contenido de RW a disco RAM mv /tmp /RAMDISK mv /var /RAMDISK mv /root /RAMDISK # Creamos los enlaces hacia la unidad RAM ln -s /RAMDISK/tmp /tmp ln -s /RAMDISK/var /var ln -s /RAMDISK/root /root # Enlaces extras necesarios para el /VAR ln -sf /run/lock /RAMDISK/var/lock ln -sf /run/ /RAMDISK/var/run # Comprimimos el home del root y el /var tar -czf /tmpvar.tar.gz /RAMDISK/var tar -czf /root.tar.gz /RAMDISK/root # Copiamos nuestros ficheros personalizados en sistema: cp -f fstab /etc/fstab cp -f setupRamdisk /usr/bin/ cp -f setupRamdisk.service /etc/systemd/system/ cp -f systemd-random-seed.service /usr/lib/systemd/system # Esto equivale al systemctl enable setupRamdisk.service: ln -s /etc/systemd/system/setupRamdisk.service /etc/systemd/system/sysinit.target.wants/setupRamdisk.service
El script que se ejecutará en el arranque para la restauración de los directorios en RAM puede ser el siguiente:
# Preparar la unidad RAM mknod /dev/ram0 b 1 2 mount /dev/ram0 -t tmpfs /RAMDISK/ mkfs.ext3 -F -m0 -q /dev/ram0 # Crear los directorios de volcado mkdir /RAMDISK/etc mkdir /RAMDISK/root mkdir /RAMDISK/tmp # Descompresión de los contenidos en R/W tar -xzpf /tmpvar.tar.gz tar -xzpf /root.tar.gz # Adecuamos algunos permisos chmod 1777 /RAMDISK/tmp chmod 755 /RAMDISK/var/lock chmod 1777 /RAMDISK/var/tmp
Finalmente el servicio setupRamdisk.service que será llamado desde sysinit.service:
[Unit] Description=RAMDisk folders init DefaultDependencies=no Wants=local-fs-pre.target Before=local-fs-pre.target shutdown.target systemd-random-seed.service Conflicts=shutdown.target [Service] Type=oneshot ExecStart=/usr/bin/setupRamdisk [Install] WantedBy=sysinit.target
Como vemos, además de esto, se ha tenido que tocar el servicio systemd-random-seed.service. Este servicio es llamado desde sysinit.service mediante un «wants» pero en su definición se especifica Before=sysinit.target y cuando se ejecuta, el /tmp sobre el que escribe aún no estaba disponible. Tenemos que decirle explícitamente que espere, añadiendo nuestro servicio setupRamdisk.service en la directiva After:
[Unit] Description=Load/Save Random Seed Documentation=man:systemd-random-seed.service(8) man:random(4) DefaultDependencies=no RequiresMountsFor=/var/lib/systemd/random-seed Conflicts=shutdown.target After=systemd-readahead-collect.service systemd-readahead-replay.service systemd-remount-fs.service setupRamdisk.service Before=sysinit.target shutdown.target [Service] Type=oneshot RemainAfterExit=yes ExecStart=/usr/lib/systemd/systemd-random-seed load ExecStop=/usr/lib/systemd/systemd-random-seed sabe
Todo esto funcionó, y obtuve un sistema arrancable desde flash en sólo lectura y completamente funcional. Para la instalación de nuevos paquetes o acceso de lectura/escritura sólo hay que ejecutar «mount -o rw,remount /«. Pero puede haber estrategias más elegantes, como el uso de systemd-tmpfiles, el cual no he probado y promete ser más intuitivo.