Crear nuestra propia distribución Linux openSUSE con KIWI NG

Como bien lo dice su wiki, KIWI es una herramienta de línea de comandos que permite construir imágenes de sistemas operativos Linux aplicados para hardware físico, como así también para entornos virtuales.

Permite crear una distribución Linux con el mínimo sistema operativo y software necesario para tareas específicas (JeOS, Just enough Operating System), o bien crear y personalizar una distribución Linux a nuestro propio antojo.

Este artículo estará basado en la versión KIWI NG 9, y buscará documentar todos los conocimientos adquiridos durante el difícil proceso de aprendizaje por el cual tuve que pasar para aprender a utilizar esta herramienta a fin de continuar creando y actualizando mis propias distribuciones openSUSE Linux corporativas.

Y digo para continuar creando y actualizando mis propias distribuciones porque previamente utilizaba la herramienta web SUSE Studio para dicho fin, que básicamente era un frontend web que abstraía muchos de los pasos necesarios para crear y empaquetar una distribución Linux con todo el software requerido. Cuando SUSE Studio se descontinuó y finalmente su portal web dejó de funcionar el 15 de febrero del 2018, estaba en una posición en la que ya no podía seguir actualizando mis versiones Linux personalizadas con versiones de software y kernel más recientes, motivo por el cual comencé a investigar sobre la herramienta KIWI que sabía era la herramienta que SUSE Studio utilizaba en el backend para construir las imágenes del sistema operativo.

Por último quisiera aclarar que conozco SUSE Studio Express, publicitada en principio como la alternativa a SUSE Studio, pero que hasta el momento no es más que una landing page con enlaces al sitio de Open Build Service, sitio web que por cierto tengo que reconocer que me ha espantado de lo complejo y difícil de usar que era para el propósito que yo estaba necesitando.



Instalación de Kiwi:

  1. Como el objetivo de esta guía es personalizar una distribución basada en SUSE Leap 15.2, lo primero que tenemos que hacer es obtener e instalar el sistema operativo openSUSE Leap 15.2 x86_64, ya que de esta forma podremos utilizar sus archivos de configuración originales como ejemplos o modelos de configuración para la construcción de la imagen personalizada.

  2. Luego procedemos a instalar los paquetes KIWI NG, Git y YaST2 Firstboot:
    kiwi:~ # zypper in python3-kiwi git yast2-firstboot
  3. Podemos ver la versión instalada de la herramienta ejecutando el siguiente comando:
    kiwi:~ # kiwi -v
    KIWI (next generation) version 9.18.16
    kiwi:~ #
  4. Para facilitar la creación de nuestro primer proyecto Kiwi procedemos a clonar un repositorio Git que ya tiene proyectos pre-configurados a partir de los cuales podremos iniciar nuestro proyecto:
    kiwi:~ # git clone https://github.com/OSInside/kiwi-descriptions
    Clonando en 'kiwi-descriptions'...
    remote: Enumerating objects: 2028, done.
    remote: Total 2028 (delta 0), reused 0 (delta 0), pack-reused 2028
    Recibiendo objetos: 100% (2028/2028), 457.11 KiB | 354.00 KiB/s, listo.
    Resolviendo deltas: 100% (978/978), listo.
    kiwi:~ #
  5. Si verificamos el directorio kiwi-descriptions podremos observar varios proyectos pre-configurados:
    kiwi:~ # ls -l kiwi-descriptions/
    total 80
    drwxr-xr-x 3 root root  4096 jul 14 00:45 archlinux
    drwxr-xr-x 3 root root  4096 jul 14 00:45 centos
    drwxr-xr-x 6 root root  4096 jul 14 00:45 custom_boot
    drwxr-xr-x 3 root root  4096 jul 14 00:45 debian
    drwxr-xr-x 3 root root  4096 jul 14 00:45 fedora
    drwxr-xr-x 8 root root  4096 jul 14 00:45 .git
    -rw-r--r-- 1 root root    15 jul 14 00:45 .gitignore
    -rw-r--r-- 1 root root 35141 jul 14 00:45 LICENSE
    drwxr-xr-x 3 root root  4096 jul 14 00:45 mageia
    -rw-r--r-- 1 root root   173 jul 14 00:45 README.md
    drwxr-xr-x 4 root root  4096 jul 14 00:45 suse
    drwxr-xr-x 3 root root  4096 jul 14 00:45 ubuntu
    kiwi:~ #
  6. A partir de esos proyectos pre-configurados veremos como construir nuestro proyecto inicial en la siguiente sección.

Configuración y construcción de un proyecto básico:

  1. Si nos ponemos a mirar dentro de cada sub directorio de la carpeta kiwi-descriptions, como por ejemplo suse/x86_64, podremos observar varias versiones de SUSE Leap y Tumbleweed pre-configuradas como JeOS y para Docker:
    kiwi:~ # ls -l kiwi-descriptions/suse/x86_64/
    total 32
    drwxr-xr-x 3 root root 4096 jul 14 00:45 suse-leap-15.0-JeOS
    drwxr-xr-x 3 root root 4096 jul 14 00:45 suse-leap-15.1-JeOS
    drwxr-xr-x 3 root root 4096 jul 14 00:45 suse-leap-15.1-JeOS-vagrant
    drwxr-xr-x 3 root root 4096 jul 14 00:45 suse-leap-15.2-JeOS
    drwxr-xr-x 3 root root 4096 jul 14 00:45 suse-leap-42.2-JeOS
    drwxr-xr-x 3 root root 4096 jul 14 00:45 suse-leap-42.3-JeOS
    drwxr-xr-x 2 root root 4096 jul 14 00:45 suse-tumbleweed-docker
    drwxr-xr-x 3 root root 4096 jul 14 00:45 suse-tumbleweed-JeOS
    kiwi:~ #
  2. Creamos una copia del directorio suse-leap-15.2-JeOS en el home del usuario root renombrada como mysuseleap-15.2 y finalmente accedemos a la misma:
    kiwi:~ # cp -R ~/kiwi-descriptions/suse/x86_64/suse-leap-15.2-JeOS ~/mysuseleap-15.2
    kiwi:~ # cd mysuseleap-15.2
  3. Una vez dentro del directorio mysuseleap-15.2 ejecutamos el comando ls -l, donde deberíamos ver lo siguiente:
    kiwi:~/mysuseleap-15.2 # ls -l
    total 12
    -rwxr-xr-x 1 root root 1182 jul 14 21:25 config.sh
    -rw-r--r-- 1 root root 3836 jul 14 21:25 config.xml
    lrwxrwxrwx 1 root root   10 jul 14 21:25 leap-15.2-JeOS.kiwi -> config.xml
    drwxr-xr-x 3 root root 4096 jul 14 21:25 root
    kiwi:~/mysuseleap-15.2 #
    A continuación me gustaría explicar el propósito de cada uno de los archivos y del directorio root que se pueden observar mas arriba.

    El archivo config.xml permite definir la configuración de la imagen Linux que deseamos construir (nombre del sistema operativo, versión, repositorios, paquetes que se deben instalar, etc.), por su parte, el archivo config.sh es un script bash opcional cuyas directivas se ejecutan justo luego de que el proceso de construcción de la imagen haya terminado de empaquetar nuestra distro Linux, como veremos más adelante, yo haré bastante uso de ese archivo. En cuanto al archivo leap-15.2-JeOS.kiwi, pueden observar que es solo un enlace simbólico al archivo config.xml que en realidad no es en absoluto necesario, pero como no molesta lo dejamos ahí.

    En cuanto a la carpeta root, todo lo que pongamos ahí adentro será lo que conformará la estructura de directorio dentro de la raíz / de nuestra distribución Linux una vez que haya sido construida. De hecho, si verificamos su contenido veremos que ya existe la carpeta "etc", que luego de construir la imagen e instalar nuestro Linux personalizado pasará a ser su directorio /etc:
    kiwi:~/mysuseleap-15.2 # ls -l root
    total 4
    drwxr-xr-x 4 root root 4096 jul 14 21:25 etc
    kiwi:~/mysuseleap-15.2 #
    Pero no nos preocupemos tanto por eso ahora y avancemos al siguiente paso.

  4. Vamos a comenzar por editar el archivo config.xml:
    kiwi:~/mysuseleap-15.2 # vim config.xml
    Y dejar el contenido del mismo similar a lo que sigue, en el cual he resaltado los cambios que he realizado a partir de la configuración por defecto que venía:
    <?xml version="1.0" encoding="utf-8"?>
    
    <image schemaversion="6.8" name="tdb-openSUSE-Leap-15.2">
        <description type="system">
            <author>Gabriel</author>
            <contact>contacto(arroba)tormentadebits(dot)com</contact>
            <specification>
                TDB openSUSE Leap 15.2 es una distro Linux corporativa basada en openSUSE.
            </specification>
        </description>
        <preferences>
            <type image="iso" primary="true" flags="overlay" hybrid="true" firmware="efi" kernelcmdline="splash" hybridpersistent_filesystem="ext4" hybridpersistent="true" mediacheck="true"/>
            <version>1.0.0</version>
            <packagemanager>zypper</packagemanager>
            <locale>es_ES</locale>
            <keytable>es</keytable>
            <timezone>America/Asuncion</timezone>
            <rpm-excludedocs>false</rpm-excludedocs>
            <rpm-check-signatures>false</rpm-check-signatures>
            <bootsplash-theme>bgrt</bootsplash-theme>
            <bootloader-theme>openSUSE</bootloader-theme>
        </preferences>
        <preferences>
            <type image="vmx" filesystem="ext4" bootloader="grub2" kernelcmdline="splash" firmware="efi"/>
            <type image="oem" filesystem="ext4" initrd_system="dracut" installiso="true" bootloader="grub2" kernelcmdline="splash" firmware="efi">
                <oemconfig>
                    <!--oem-systemsize>2048</oem-systemsize-->
                    <oem-swap>true</oem-swap>
                    <oem-device-filter>/dev/ram</oem-device-filter>
                    <oem-multipath-scan>false</oem-multipath-scan>
                </oemconfig>
                <machine memory="512" guestOS="suse" HWversion="4">
                    <vmdisk id="0" controller="ide"/>
                    <vmnic driver="e1000" interface="0" mode="bridged"/>
                </machine>
            </type>
        </preferences>
        <users>
            <user password="$1$randomte$hVK3OIJW75TkyaZ.i7Kh20" home="/root" name="root" groups="root"/>
        </users>
        <repository type="rpm-md" alias="kiwi" priority="1">
            <source path="obs://Virtualization:Appliances:Builder/openSUSE_Leap_15.2"/>
        </repository>
        <repository type="rpm-md" alias="Leap_15_2" imageinclude="true">
            <source path="obs://openSUSE:Leap:15.2/standard"/>
        </repository>
        <repository type="rpm-md" alias="repo-update" imageinclude="true" priority="1">
            <source path="http://download.opensuse.org/update/leap/15.2/oss"/>
        </repository>
        <repository type="rpm-md" alias="repo-update-non-oss" imageinclude="true" priority="2">
            <source path="http://download.opensuse.org/update/leap/15.2/non-oss"/>
        </repository>
        <repository type="rpm-md" alias="repo-oss" imageinclude="true" priority="3">
            <source path="http://download.opensuse.org/distribution/leap/15.2/repo/oss"/>
    </repository> <repository type="rpm-md" alias="repo-non-oss" imageinclude="true" priority="4"> <source path="http://download.opensuse.org/distribution/leap/15.2/repo/non-oss"/>
    </repository> <repository type="rpm-md" alias="google-chrome" imageinclude="true" priority="5"> <source path="http://dl.google.com/linux/chrome/rpm/stable/x86_64"/> </repository> <repository type="rpm-md" alias="download.nvidia.com-leap" imageinclude="true" priority="6"> <source path="https://download.nvidia.com/opensuse/leap/15.2"/> </repository> <repository type="rpm-md" alias="repo-oss" imageinclude="true" priority="7"> <source path="http://packman.inode.at/suse/openSUSE_Leap_15.2"/> </repository> <repository type="rpm-md" alias="snappy" imageinclude="true" priority="8"> <source path="http://download.opensuse.org/repositories/system:/snappy/openSUSE_Leap_15.2"/> </repository>
    <packages type="image" patternType="plusRecommended"> <package name="checkmedia"/> <package name="plymouth-theme-bgrt"/> <package name="plymouth-dracut"/> <package name="grub2-branding-openSUSE"/> <package name="ifplugd"/> <package name="iputils"/> <package name="vim"/> <package name="grub2"/> <package name="grub2-x86_64-efi" arch="x86_64"/> <package name="grub2-i386-pc"/> <package name="syslinux"/> <package name="lvm2"/> <package name="plymouth"/> <package name="fontconfig"/> <package name="fonts-config"/> <package name="tar"/> <package name="parted"/> <package name="openssh"/> <package name="iproute2"/> <package name="less"/> <package name="bash-completion"/> <package name="dhcp-client"/> <package name="which"/> <package name="shim"/> <package name="kernel-default"/> <package name="timezone"/> <package name="ntp"/> <package name="man-pages"/> <package name="man-pages-posix"/> <package name="x11vnc"/> <package name="krfb"/> <package name="xinetd"/> <package name="bzip2"/> <package name="unrar"/> <package name="yast2-alternatives"/> <package name="yast2-firstboot"/> <package name="yast2-trans-es"/> <package name="pidgin"/> <package name="gajim"/> <package name="kwrite"/> <package name="firewalld"/> <package name="firewall-config"/> <package name="telnet"/> <package name="vncmanager"/> <package name="OpenPrintingPPDs"/> <package name="hplip"/> <package name="manufacturer-PPDs"/> <package name="epson-inkjet-printer-escpr"/> <package name="gutenprint"/> <package name="myspell-es"/> <package name="myspell-es_PY"/> <package name="aspell-es"/> <package name="ispell-spanish"/> <package name="nmap"/> <package name="rsync"/> <package name="tcpdump"/> <package name="libreoffice-writer-extensions"/> <package name="libreoffice-calc-extensions"/> <package name="libreoffice-l10n-es"/> <package name="gcc"/> <package name="gcc-c++"/> <package name="cups-backends"/> <package name="net-tools-deprecated"/> <package name="perl-Net-IP"/> <package name="perl-Net-IPv4Addr"/> <package name="snapd"/> <package name="virtualbox-kmp-default"/> <package name="virtualbox-guest-tools"/>
    <package name="virtualbox-guest-x11"/> <package name="sane-backends"/> <package name="xf86-video-amdgpu"/> <package name="xf86-video-ati"/> <package name="xf86-video-nouveau"/> <package name="xf86-video-nv"/> <package name="xf86-video-r128"/> <package name="xf86-video-intel"/>
    <namedCollection name="apparmor"/> <namedCollection name="apparmor_opt"/> <namedCollection name="base"/> <namedCollection name="basesystem"/> <namedCollection name="enhanced_base"/> <namedCollection name="enhanced_base_opt"/> <namedCollection name="fonts"/> <namedCollection name="fonts_opt"/> <namedCollection name="imaging"/> <namedCollection name="imaging_opt"/> <namedCollection name="kde"/> <namedCollection name="kde_imaging"/> <namedCollection name="kde_internet"/> <namedCollection name="kde_multimedia"/> <namedCollection name="kde_office"/> <namedCollection name="kde_plasma"/> <namedCollection name="kde_utilities"/> <namedCollection name="kde_utilities_opt"/> <namedCollection name="kde_yast"/> <namedCollection name="laptop"/> <namedCollection name="minimal_base"/> <namedCollection name="multimedia"/> <namedCollection name="multimedia_opt"/> <namedCollection name="office"/> <namedCollection name="sw_management"/> <namedCollection name="x11"/> <namedCollection name="x11_enhanced"/> <namedCollection name="x11_opt"/> <namedCollection name="x11_yast"/> <namedCollection name="yast2_basis"/>
    </packages> <packages type="delete"> <!--package name="PackageKit"/--> <!--package name="PackageKit-backend-zypp"/--> <!--package name="PackageKit-lang"/--> <!--package name="PackageKit-branding-openSUSE"/--> <package name="discover"/> <package name="discover-lang"/> <package name="tigervnc"/> <package name="kpat"/> <package name="kpat-lang"/> <package name="kdegames-carddecks-default"/> <package name="kmahjongg"/> <package name="kmahjongg-lang"/> <package name="kmines"/> <package name="kmines-lang"/> <package name="kreversi"/> <package name="kreversi-lang"/> <package name="ksudoku"/> <package name="ksudoku-lang"/> <namedCollection name="patterns-games-games"/> <namedCollection name="patterns-kde-kde_games"/> </packages> <packages type="iso"> <package name="gfxboot-branding-openSUSE"/> <package name="dracut-kiwi-live"/> </packages> <packages type="oem"> <package name="gfxboot-branding-openSUSE"/> <package name="dracut-kiwi-oem-repart"/> <package name="dracut-kiwi-oem-dump"/> </packages> <packages type="bootstrap"> <package name="udev"/> <package name="filesystem"/> <package name="glibc-locale"/> <package name="cracklib-dict-full"/> <package name="ca-certificates"/> <package name="openSUSE-release"/> </packages> </image>
    Todos los cambios resaltados más arriba a excepción de los muy obvios los iré detallado a continuación:
    - name="tdb-openSUSE-Leap-15.2": Permite nombrar a nuestro proyecto, nombre con el cual se generará la imagen.

    - <version>1.0.0</version>: Permite especificar la versión del proyecto de la imagen que estamos desarrollando, y que formará parte del nombre del archivo final.

    - <locale>es_ES</locale>: Nos permite configurar el idioma por defecto para el sistema operativo.

    - <keytable>es</keytable>: Nos permite configurar el idioma del teclado por defecto para el sistema operativo.

    - <timezone>America/Asuncion</timezone>: Permite especificar la zona horaria por defecto para el sistema operativo, en el ejemplo especifico la zona horaria de mi país, si quieren conocer las zonas horarias disponibles pueden visitar este link.

    - <rpm-excludedocs>false</rpm-excludedocs>: Es importante setear el valor false a esta directiva, ya que si la dejamos con el valor por defecto no nos incluirá ningún documento man de ayuda. A mi me ha pasado que he pasado horas tratando de entender porqué no había documentos man en el sistema operativo una vez generada la imagen, y se debía a esta directiva que tenía el valor por defecto true.

    - <!--oem-systemsize>2048</oem-systemsize-->: Esta directiva la recomiendo comentar o directamente eliminar para que el sistema de archivos auto asigne los tamaños de las particiones durante la instalación de la imagen, caso contrario creará una partición para la raíz de apenas 2 GB, algo que a toda costa es insuficiente a menos que nuestro proyecto lo requiera.

    - <user password=: Permite especificar la contraseña por defecto que se le asignará al usuario root apenas instalemos nuestra imagen Linux personalizada. La misma es básicamente un hash code que lo recomiendo reemplazar por una nueva contraseña encriptada que la podemos generar de la siguiente directiva:
    kiwi:~ # openssl passwd -1 -salt 'randomtext', tdbpass
    $1$randomte$hVK3OIJW75TkyaZ.i7Kh20
    kiwi:~ #

    - <repository type="rpm-md": En esta sección vamos configurando los repositorios que la herramienta Kiwi podrá disponer para obtener los paquetes e incorporarlos en la construcción de la imagen del sistema operativo, y si le agregamos la directiva imageinclude="true" como se observa en el ejemplo ya estarán configurados automáticamente en nuestro imagen. En este ejemplo se agregan como fuente de datos los repositorios repo-update, repo-update-non-oss, repo-oss, repo-non-oss, google-chrome, download.nvidia.com-leap, repo-oss y snappy.

    - <packages type="image" patternType="plusRecommended">: En esta sección es importante resaltar la directiva patternType="plusRecommended" que permite que durante la creación de la imagen no solo se incorporen los paquetes estrictamente necesarios, sino también aquellos recomendados para el buen funcionamiento y compatibilidad del sistema operativo final. Dentro de dicha sección de configuración podemos especificar la directiva "package name" para indicar nombres de paquetes específicos y la directiva "namedCollection name" para especificar nombres de colecciones de paquetes. Todos las directivas resaltadas en verde son justamente paquetes y colecciones de paquetes agregadas de forma adicional que no viene con la configuración por defecto.

    <packages type="delete">: En esta sección de configuración podemos especificar los nombres de paquetes y de las colecciones de paquetes que queremos que se excluyan durante la construcción de la imagen de nuestra distribución Linux personalizada.

  5. Para nuestro proyecto básico también tenemos que realizar un cambio menor al archivo config.sh. Procedemos a editarlo:
    kiwi:~/mysuseleap-15.2 # vim config.sh
    Una vez adentro buscamos la sección baseSetRunlevel y reemplazamos el nivel de ejecución 3 (Multiusuario con soporte de red sin interfaz gráfica, solo consola) por el nivel de ejecución 5 (Multiusuario con soporte de red y entorno gráfico):
    #======================================
    # Setup default target, multi-user
    #--------------------------------------
    baseSetRunlevel 5
  6. En el directorio home del usuario root creamos la carpeta kiwi-builds para albergar las imágenes que generará el comando de construcción de la imagen:
    kiwi:~/mysuseleap-15.2 # cd ..
    kiwi:~ # mkdir kiwi-builds
    
  7. Llegados hasta este punto ya estamos listos para proceder con la ejecución del comando que se encargará de construir la imagen de nuestra distribución Linux personalizada con una configuración básica inicial. Damos inicio al proceso de construcción ejecutando el siguiente comando:
    kiwi:~ # kiwi --type oem system build --description ~/mysuseleap-15.2 --target-dir ~/kiwi-builds
    La salida completa de la ejecución del comando anterior se puede observar a continuación:
    [ INFO    ]: 22:04:17 | Loading XML description
    [ INFO    ]: 22:04:17 | --> loaded /root/mysuseleap-15.2/config.xml
    [ INFO    ]: 22:04:17 | --> Selected build type: oem
    [ INFO    ]: 22:04:17 | Preparing new root system
    [ INFO    ]: 22:04:17 | Setup root directory: /root/kiwi-builds/build/image-root
    [ INFO ]: 22:04:18 | Setting up repository obs://Virtualization:Appliances:Builder/openSUSE_Leap_15.2 [ INFO ]: 22:04:18 | --> Type: rpm-md [ INFO ]: 22:04:18 | --> Priority: 1 [ INFO ]: 22:04:19 | --> Translated: http://download.opensuse.org/repositories/Virtualization:/Appliances:/Builder/openSUSE_Leap_15.2/ [ INFO ]: 22:04:19 | --> Alias: kiwi [ INFO ]: 22:04:19 | Setting up repository obs://openSUSE:Leap:15.2/standard [ INFO ]: 22:04:19 | --> Type: rpm-md [ INFO ]: 22:04:20 | --> Translated: http://download.opensuse.org/distribution/leap/15.2/repo/oss/ [ INFO ]: 22:04:20 | --> Alias: Leap_15_2 [ INFO ]: 22:04:20 | Setting up repository http://download.opensuse.org/update/leap/15.2/oss [ INFO ]: 22:04:20 | --> Type: rpm-md [ INFO ]: 22:04:20 | --> Priority: 1 [ INFO ]: 22:04:20 | --> Translated: http://download.opensuse.org/update/leap/15.2/oss [ INFO ]: 22:04:20 | --> Alias: repo-update [ INFO ]: 22:04:20 | Setting up repository http://download.opensuse.org/update/leap/15.2/non-oss [ INFO ]: 22:04:20 | --> Type: rpm-md [ INFO ]: 22:04:20 | --> Priority: 2 [ INFO ]: 22:04:20 | --> Translated: http://download.opensuse.org/update/leap/15.2/non-oss [ INFO ]: 22:04:20 | --> Alias: repo-update-non-oss [ INFO ]: 22:04:20 | Setting up repository http://download.opensuse.org/distribution/leap/15.2/repo/oss [ INFO ]: 22:04:20 | --> Type: rpm-md [ INFO ]: 22:04:20 | --> Priority: 3 [ INFO ]: 22:04:20 | --> Translated: http://download.opensuse.org/distribution/leap/15.2/repo/oss [ INFO ]: 22:04:20 | --> Alias: repo-oss [ INFO ]: 22:04:20 | Setting up repository http://download.opensuse.org/distribution/leap/15.2/repo/non-oss [ INFO ]: 22:04:20 | --> Type: rpm-md [ INFO ]: 22:04:20 | --> Priority: 4 [ INFO ]: 22:04:20 | --> Translated: http://download.opensuse.org/distribution/leap/15.2/repo/non-oss [ INFO ]: 22:04:20 | --> Alias: repo-non-oss [ INFO ]: 22:04:21 | Setting up repository http://dl.google.com/linux/chrome/rpm/stable/x86_64 [ INFO ]: 22:04:21 | --> Type: rpm-md [ INFO ]: 22:04:21 | --> Priority: 5 [ INFO ]: 22:04:21 | --> Translated: http://dl.google.com/linux/chrome/rpm/stable/x86_64 [ INFO ]: 22:04:21 | --> Alias: google-chrome [ INFO ]: 22:04:21 | Setting up repository http://download.nvidia.com/opensuse/leap/15.2 [ INFO ]: 22:04:21 | --> Type: rpm-md [ INFO ]: 22:04:21 | --> Priority: 6 [ INFO ]: 22:04:21 | --> Translated: http://download.nvidia.com/opensuse/leap/15.2 [ INFO ]: 22:04:21 | --> Alias: download.nvidia.com-leap [ INFO ]: 22:04:21 | Setting up repository http://packman.inode.at/suse/openSUSE_Leap_15.2 [ INFO ]: 22:04:21 | --> Type: rpm-md [ INFO ]: 22:04:21 | --> Priority: 7 [ INFO ]: 22:04:21 | --> Translated: http://packman.inode.at/suse/openSUSE_Leap_15.2 [ INFO ]: 22:04:21 | --> Alias: repo-oss [ INFO ]: 22:04:21 | Setting up repository http://download.opensuse.org/repositories/system:/snappy/openSUSE_Leap_15.2 [ INFO ]: 22:04:21 | --> Type: rpm-md [ INFO ]: 22:04:21 | --> Priority: 8 [ INFO ]: 22:04:21 | --> Translated: http://download.opensuse.org/repositories/system:/snappy/openSUSE_Leap_15.2 [ INFO ]: 22:04:21 | --> Alias: snappy [ INFO ]: 22:04:21 | Using package manager backend: zypper [ INFO ]: 22:04:21 | Installing bootstrap packages [ INFO ]: 22:04:21 | --> collection type: onlyRequired [ INFO ]: 22:04:21 | --> package: ca-certificates [ INFO ]: 22:04:21 | --> package: cracklib-dict-full [ INFO ]: 22:04:21 | --> package: filesystem [ INFO ]: 22:04:21 | --> package: glibc-locale [ INFO ]: 22:04:21 | --> package: openSUSE-release [ INFO ]: 22:04:21 | --> package: udev [ INFO ]: 22:04:21 | --> package: zypper [ INFO ]: Processing: [########################################] 100% [ INFO ]: 22:09:46 | Installing system (chroot) for build type: oem [ INFO ]: 00:25:38 | --> collection type: plusRecommended [ INFO ]: 00:25:38 | --> package: OpenPrintingPPDs [ INFO ]: 00:25:38 | --> package: aspell-es [ INFO ]: 00:25:38 | --> package: bash-completion [ INFO ]: 00:25:38 | --> package: bzip2 [ INFO ]: 00:25:38 | --> package: checkmedia [ INFO ]: 00:25:38 | --> package: cups-backends [ INFO ]: 00:25:38 | --> package: dhcp-client [ INFO ]: 00:25:38 | --> package: dracut-kiwi-oem-dump [ INFO ]: 00:25:38 | --> package: dracut-kiwi-oem-repart [ INFO ]: 00:25:38 | --> package: epson-inkjet-printer-escpr [ INFO ]: 00:25:38 | --> package: firewall-config [ INFO ]: 00:25:38 | --> package: firewalld [ INFO ]: 00:25:38 | --> package: fontconfig [ INFO ]: 00:25:38 | --> package: fonts-config [ INFO ]: 00:25:38 | --> package: gajim [ INFO ]: 00:25:38 | --> package: gcc [ INFO ]: 00:25:38 | --> package: gcc-c++ [ INFO ]: 00:25:38 | --> package: gfxboot-branding-openSUSE [ INFO ]: 00:25:38 | --> package: grub2 [ INFO ]: 00:25:38 | --> package: grub2-branding-openSUSE [ INFO ]: 00:25:38 | --> package: grub2-i386-pc [ INFO ]: 00:25:38 | --> package: grub2-x86_64-efi [ INFO ]: 00:25:38 | --> package: gutenprint [ INFO ]: 00:25:38 | --> package: hplip [ INFO ]: 00:25:38 | --> package: ifplugd [ INFO ]: 00:25:38 | --> package: iproute2 [ INFO ]: 00:25:38 | --> package: iputils [ INFO ]: 00:25:38 | --> package: ispell-spanish [ INFO ]: 00:25:38 | --> package: kernel-default [ INFO ]: 00:25:38 | --> package: krfb [ INFO ]: 00:25:38 | --> package: kwrite [ INFO ]: 00:25:38 | --> package: less [ INFO ]: 00:25:38 | --> package: libreoffice-calc-extensions [ INFO ]: 00:25:38 | --> package: libreoffice-l10n-es [ INFO ]: 00:25:38 | --> package: libreoffice-writer-extensions [ INFO ]: 00:25:38 | --> package: lvm2 [ INFO ]: 00:25:38 | --> package: man-pages [ INFO ]: 00:25:38 | --> package: man-pages-posix [ INFO ]: 00:25:38 | --> package: manufacturer-PPDs [ INFO ]: 00:25:38 | --> package: myspell-es [ INFO ]: 00:25:38 | --> package: myspell-es_PY [ INFO ]: 00:25:38 | --> package: net-tools-deprecated [ INFO ]: 00:25:38 | --> package: nmap [ INFO ]: 00:25:38 | --> package: ntp [ INFO ]: 00:25:38 | --> package: openssh [ INFO ]: 00:25:38 | --> package: parted [ INFO ]: 00:25:38 | --> package: perl-Net-IP [ INFO ]: 00:25:38 | --> package: perl-Net-IPv4Addr [ INFO ]: 00:25:38 | --> package: pidgin [ INFO ]: 00:25:38 | --> package: plymouth [ INFO ]: 00:25:38 | --> package: plymouth-dracut [ INFO ]: 00:25:38 | --> package: plymouth-theme-bgrt [ INFO ]: 00:25:38 | --> package: rsync [ INFO ]: 00:25:38 | --> package: sane-backends [ INFO ]: 00:25:38 | --> package: shim [ INFO ]: 00:25:38 | --> package: snapd [ INFO ]: 00:25:38 | --> package: syslinux [ INFO ]: 00:25:38 | --> package: tar [ INFO ]: 00:25:38 | --> package: tcpdump [ INFO ]: 00:25:38 | --> package: telnet [ INFO ]: 00:25:38 | --> package: timezone [ INFO ]: 00:25:38 | --> package: unrar [ INFO ]: 00:25:38 | --> package: vim [ INFO ]: 00:25:38 | --> package: virtualbox-guest-tools [ INFO ]: 00:25:38 | --> package: virtualbox-guest-x11 [ INFO ]: 00:25:38 | --> package: virtualbox-kmp-default [ INFO ]: 00:25:38 | --> package: vncmanager [ INFO ]: 00:25:38 | --> package: which [ INFO ]: 00:25:38 | --> package: x11vnc [ INFO ]: 00:25:38 | --> package: xf86-video-amdgpu [ INFO ]: 00:25:38 | --> package: xf86-video-ati [ INFO ]: 00:25:38 | --> package: xf86-video-intel [ INFO ]: 00:25:38 | --> package: xf86-video-nouveau [ INFO ]: 00:25:38 | --> package: xf86-video-nv [ INFO ]: 00:25:38 | --> package: xf86-video-r128 [ INFO ]: 00:25:38 | --> package: xinetd [ INFO ]: 00:25:38 | --> package: yast2-alternatives [ INFO ]: 00:25:38 | --> package: yast2-firstboot [ INFO ]: 00:25:38 | --> package: yast2-trans-es [ INFO ]: 00:25:38 | --> collection: apparmor [ INFO ]: 00:25:38 | --> collection: apparmor_opt [ INFO ]: 00:25:38 | --> collection: base [ INFO ]: 00:25:38 | --> collection: basesystem [ INFO ]: 00:25:38 | --> collection: enhanced_base [ INFO ]: 00:25:38 | --> collection: enhanced_base_opt [ INFO ]: 00:25:38 | --> collection: fonts [ INFO ]: 00:25:38 | --> collection: fonts_opt [ INFO ]: 00:25:38 | --> collection: imaging [ INFO ]: 00:25:38 | --> collection: imaging_opt [ INFO ]: 00:25:38 | --> collection: kde [ INFO ]: 00:25:38 | --> collection: kde_imaging [ INFO ]: 00:25:38 | --> collection: kde_internet [ INFO ]: 00:25:38 | --> collection: kde_multimedia [ INFO ]: 00:25:38 | --> collection: kde_office [ INFO ]: 00:25:38 | --> collection: kde_plasma [ INFO ]: 00:25:38 | --> collection: kde_utilities [ INFO ]: 00:25:38 | --> collection: kde_utilities_opt [ INFO ]: 00:25:38 | --> collection: kde_yast [ INFO ]: 00:25:38 | --> collection: laptop [ INFO ]: 00:25:38 | --> collection: minimal_base [ INFO ]: 00:25:38 | --> collection: multimedia [ INFO ]: 00:25:38 | --> collection: multimedia_opt [ INFO ]: 00:25:38 | --> collection: office [ INFO ]: 00:25:38 | --> collection: sw_management [ INFO ]: 00:25:38 | --> collection: x11 [ INFO ]: 00:25:38 | --> collection: x11_enhanced [ INFO ]: 00:25:38 | --> collection: x11_opt [ INFO ]: 00:25:38 | --> collection: x11_yast [ INFO ]: 00:25:38 | --> collection: yast2_basis [ INFO ]: Processing: [########################################] 100% [ INFO ]: 23:41:14 | Creating .profile environment [ INFO ]: 23:41:14 | Importing Image description to system tree [ INFO ]: 23:41:14 | --> Importing state XML description as image/config.xml [ INFO ]: 23:41:14 | --> Importing config.sh script as image/config.sh [ INFO ]: 23:41:14 | --> Importing script helper functions [ INFO ]: 23:41:14 | Copying user defined files to image tree [ INFO ]: 23:41:14 | Setting up user root [ INFO ]: 23:41:14 | --> Modifying user: root [root] [ INFO ]: 23:41:14 | Setting up keytable: es [ INFO ]: 23:41:14 | Setting up locale: en_ES [ INFO ]: 23:41:14 | Setting up timezone: America/Asuncion [ INFO ]: 23:41:14 | Check/Fix File Permissions [ INFO ]: 23:41:16 | Setting up image repository obs://openSUSE:Leap:15.2/standard [ INFO ]: 23:41:16 | --> Type: rpm-md [ INFO ]: 23:41:16 | --> Translated: http://download.opensuse.org/distribution/leap/15.2/repo/oss/ [ INFO ]: 23:41:16 | --> Alias: Leap_15_2 [ INFO ]: 23:41:17 | Calling config.sh script [ INFO ]: 23:41:17 | Force deleting system packages (chroot) [ INFO ]: 23:41:17 | Using package manager backend: zypper [ INFO ]: 23:41:17 | --> package: discover [ INFO ]: 23:41:17 | --> package: discover-lang [ INFO ]: 23:41:17 | --> package: kdegames-carddecks-default [ INFO ]: 23:41:17 | --> package: kmahjongg [ INFO ]: 23:41:17 | --> package: kmahjongg-lang [ INFO ]: 23:41:17 | --> package: kmines [ INFO ]: 23:41:17 | --> package: kmines-lang [ INFO ]: 23:41:17 | --> package: kpat [ INFO ]: 23:41:17 | --> package: kpat-lang [ INFO ]: 23:41:17 | --> package: kreversi [ INFO ]: 23:41:17 | --> package: kreversi-lang [ INFO ]: 23:41:17 | --> package: ksudoku [ INFO ]: 23:41:17 | --> package: ksudoku-lang [ INFO ]: 23:41:17 | --> package: tigervnc [ INFO ]: 23:41:17 | Force deleting system packages (chroot) [ INFO ]: Processing: [########################################] 100% [ INFO ]: 23:41:18 | Cleaning up SystemPrepare instance [ INFO ]: 23:41:19 | Creating system image [ INFO ]: 23:41:19 | Preparing boot system [ INFO ]: 23:41:19 | Creating .profile environment [ INFO ]: 23:41:19 | Precalculating required disk size [ INFO ]: 23:41:41 | --> system data with filesystem overhead needs 9788 MB [ INFO ]: 23:41:41 | --> legacy bios boot partition adding 2 MB [ INFO ]: 23:41:41 | --> EFI partition adding 20 MB [ INFO ]: 23:41:41 | Using calculated disk size: 9810 MB [ INFO ]: 23:41:41 | Creating raw disk image /root/kiwi-builds/tdb-openSUSE-Leap-15.2.x86_64-1.0.0.raw
    [ INFO ]: 23:41:43 | --> creating EFI CSM(legacy bios) partition [ INFO ]: 23:41:45 | --> creating EFI partition [ INFO ]: 23:41:47 | --> creating root partition [ INFO ]: 23:41:50 | Creating EFI(fat16) filesystem on /dev/mapper/loop0p2 [ INFO ]: 23:41:50 | Creating root(ext4) filesystem on /dev/mapper/loop0p3 [ INFO ]: 23:41:51 | Creating config.partids in boot system [ INFO ]: 23:41:51 | Export modprobe configuration [ INFO ]: 23:41:51 | Creating image identifier: 0x3cfb4ea2 [ INFO ]: 23:41:51 | Creating generic system etc/fstab [ INFO ]: 23:41:51 | Creating generic dracut initrd archive [ INFO ]: 23:44:23 | --> initrd archive as initrd-5.3.18-lp152.33-default [ INFO ]: 23:44:23 | Creating grub2 bootloader configuration [ INFO ]: 23:44:23 | Creating grub2 bootloader images [ INFO ]: 23:44:23 | --> Using prebuilt unsigned efi image [ INFO ]: 23:44:23 | Creating grub2 config file from template [ INFO ]: 23:44:23 | --> Using hybrid boot disk template [ INFO ]: 23:44:23 | Writing grub.cfg file [ INFO ]: 23:44:23 | Writing grub2 defaults file [ INFO ]: 23:44:23 | --> GRUB_CMDLINE_LINUX_DEFAULT:"splash root=UUID=458f7faa-e741-405b-ba75-d11a6616cacd rw " [ INFO ]: 23:44:23 | --> GRUB_THEME:/boot/grub2/themes/openSUSE/theme.txt [ INFO ]: 23:44:23 | --> GRUB_TIMEOUT:10 [ INFO ]: 23:44:23 | --> GRUB_USE_INITRDEFI:true [ INFO ]: 23:44:23 | --> GRUB_USE_LINUXEFI:true [ INFO ]: 23:44:23 | Writing sysconfig bootloader file [ INFO ]: 23:44:23 | --> DEFAULT_APPEND:"splash root=UUID=458f7faa-e741-405b-ba75-d11a6616cacd rw " [ INFO ]: 23:44:23 | --> FAILSAFE_APPEND:"splash root=UUID=458f7faa-e741-405b-ba75-d11a6616cacd rw ide=nodma apm=off noresume edd=off nomodeset 3 " [ INFO ]: 23:44:23 | --> LOADER_LOCATION:none [ INFO ]: 23:44:23 | --> LOADER_TYPE:grub2-efi [ INFO ]: 23:44:23 | Creating config.bootoptions [ INFO ]: 23:44:23 | Syncing system to image [ INFO ]: 23:44:23 | --> Syncing EFI boot data to EFI partition [ WARNING ]: 23:44:24 | Extended attributes not supported for target: /tmp/kiwi_mount_manager.hr6ebggl [ INFO ]: 23:44:24 | --> Syncing root filesystem data [ INFO ]: 23:49:57 | Installing grub2 on disk /dev/loop0 [ INFO ]: 23:50:01 | Cleaning up BootLoaderInstallGrub2 instance [ INFO ]: 23:50:01 | Saving boot image instance to file [ INFO ]: 23:50:01 | Export rpm packages metadata [ INFO ]: 23:50:05 | Export rpm verification metadata [ INFO ]: 23:55:02 | Cleaning up FileSystemExt4 instance [ INFO ]: 23:55:02 | Cleaning up FileSystemFat16 instance [ INFO ]: 23:55:02 | Cleaning up Disk instance [ INFO ]: 23:55:02 | Cleaning up LoopDevice instance [ INFO ]: 23:55:02 | Creating hybrid ISO installation image [ INFO ]: 23:55:02 | Creating disk image checksum [ INFO ]: 23:55:53 | Creating squashfs embedded disk image [ INFO ]: 01:25:51 | Setting up install image bootloader configuration [ INFO ]: 01:25:51 | Creating grub2 bootloader images [ INFO ]: 01:25:51 | --> Creating identifier file 0x5bdd7555 [ INFO ]: 01:25:52 | --> Creating bios image [ INFO ]: 01:25:52 | --> Using prebuilt unsigned efi image [ INFO ]: 01:25:53 | Creating grub2 install config file from template [ WARNING ]: 01:25:53 | root=UUID=<uuid> setup requested, but uuid is not provided [ INFO ]: 01:25:53 | --> Using standard boot install template [ INFO ]: 01:25:53 | Writing grub.cfg file [ INFO ]: 01:25:53 | Writing /root/kiwi-builds/kiwi_install_media.fvec9t0_/EFI/BOOT/grub.cfg file to be found by EFI firmware
    [ INFO ]: 01:25:53 | Writing grub2 defaults file [ INFO ]: 01:25:53 | --> GRUB_CMDLINE_LINUX_DEFAULT:"splash" [ INFO ]: 01:25:53 | --> GRUB_THEME:/boot/grub2/themes/openSUSE/theme.txt [ INFO ]: 01:25:53 | --> GRUB_TIMEOUT:10 [ INFO ]: 01:25:53 | --> GRUB_USE_INITRDEFI:true [ INFO ]: 01:25:53 | --> GRUB_USE_LINUXEFI:true [ INFO ]: 01:25:53 | Writing sysconfig bootloader file [ INFO ]: 01:25:53 | --> DEFAULT_APPEND:"splash" [ INFO ]: 01:25:53 | --> FAILSAFE_APPEND:"splash ide=nodma apm=off noresume edd=off nomodeset 3" [ INFO ]: 01:25:53 | --> LOADER_LOCATION:none [ INFO ]: 01:25:53 | --> LOADER_TYPE:grub2-efi [ INFO ]: 01:25:53 | Creating install image boot image [ INFO ]: 01:25:53 | Creating generic dracut initrd archive [ INFO ]: 01:28:35 | Creating ISO filesystem [ INFO ]: 01:29:14 | Cleaning up InstallImageBuilder instance [ INFO ]: 01:29:14 | Cleaning up BootImageDracut instance [ INFO ]: 01:29:14 | Cleaning up BootImageDracut instance [ INFO ]: 01:29:14 | Cleaning up BootImageDracut instance [ INFO ]: 01:29:14 | Result files: [ INFO ]: 01:29:14 | --> disk_image: /root/kiwi-builds/tdb-openSUSE-Leap-15.2.x86_64-1.0.0.raw
    [ INFO ]: 01:29:14 | --> image_packages: /root/kiwi-builds/tdb-openSUSE-Leap-15.2.x86_64-1.0.0.packages
    [ INFO ]: 01:29:14 | --> image_verified: /root/kiwi-builds/tdb-openSUSE-Leap-15.2.x86_64-1.0.0.verified
    [ INFO ]: 01:29:14 | --> installation_image: /root/kiwi-builds/tdb-openSUSE-Leap-15.2.x86_64-1.0.0.install.iso
    [ INFO ]: 01:29:14 | Cleaning up BootImageDracut instance
    Una vez concluida la construcción de la imagen, podemos verificar el contenido del directorio ~/kiwi-builds donde lo que importa es el archivo tdb-openSUSE-Leap-15.2.x86_64-1.0.0.install.iso, que es la imagen ISO personalizada de nuestro propio sistema operativo:
    kiwi:~ # ls -l ~/kiwi-builds
    total 9605896
    drwxr-xr-x 3 root root        4096 jul 29 01:17 build
    -rw-r--r-- 1 root root  2321940480 jul 29 01:29 tdb-openSUSE-Leap-15.2.x86_64-1.0.0.install.iso
    -rw-r--r-- 1 root root      421891 jul 28 23:50 tdb-openSUSE-Leap-15.2.x86_64-1.0.0.packages
    -rw-r--r-- 1 root root 10286530560 jul 28 23:50 tdb-openSUSE-Leap-15.2.x86_64-1.0.0.raw
    -rw-r--r-- 1 root root        4979 jul 28 23:55 tdb-openSUSE-Leap-15.2.x86_64-1.0.0.verified
    -rw-r--r-- 1 root root       16863 jul 29 01:29 kiwi.result
    kiwi:~ #
  8. Como podrán darse cuenta el proceso tardará bastante, ya que el proceso abarca desde la descarga de los paquetes requeridos para la construcción de la imagen en el directorio temporal /var/cache/kiwi/packages, la construcción de la estructura del sistema de archivos del sistema operativo, el empaquetamiento y la construcción de la imagen ISO final.

    Ahora bien, los paquetes que se descargan en el directorio /var/cache/kiwi/packages ya no se volverán a descargar durante la próxima compilación, ya estarán cacheados. De hecho, si queremos actualizar las versiones de los paquetes en posteriores compilaciones es recomendable renombrar la carpeta /var/cache/kiwi por /var/cache/kiwi-v1.0.0 y durante la próxima compilación dejar que el proceso vuelva a crear el directorio y descargar en él todos los paquetes actualizados a partir de los repositorios en línea.

  9. Un problema que suele presentarse cuando volvemos a ejecutar el comando kiwi luego de que haya fallado durante la primera ejecución, es que el comando nos informará que no puede avanzar debido a que ya existe una construcción en curso en el directorio ~/kiwi-builds:
    kiwi:~ # kiwi --type oem system build --description ~/mysuseleap-15.2 --target-dir ~/kiwi-builds
    [ INFO    ]: 22:03:25 | Loading XML description
    [ INFO    ]: 22:03:26 | --> loaded /root/mysuseleap-15.2/config.xml
    [ INFO    ]: 22:03:26 | --> Selected build type: oem
    [ INFO    ]: 22:03:26 | Preparing new root system
    [ INFO    ]: 22:03:26 | Setup root directory: /root/kiwi-builds/build/image-root
    [ ERROR   ]: 22:03:26 | KiwiRootDirExists: Root directory /root/kiwi-builds/build/image-root already exists
    [ INFO    ]: 22:03:26 | Cleaning up SystemPrepare instance
    kiwi:~ #
    Para solucionar este problema simplemente procedemos a eliminar la carpeta kiwi-builds con todo su contenido del proyecto de construcción que quedó a medio terminar y la volvemos a crear:
    kiwi:~ # rm -Rf ~/kiwi-builds
    kiwi:~ # mkdir kiwi-builds
    Si no nos permite eliminar la carpeta kiwi-builds es posible que existan unidades montadas por el sistema operativo dentro de dicha carpeta, requeridos para el proceso de construcción de la imagen. Para ver si existen unidades montadas podemos ejecutar el comando mount, identificar cual es la unidad que se encuentra montada dentro del directorio ~/kiwi-builds y desmontarla con el comando umount, con lo cual ya nos debería permitir eliminar y volver a crear la carpeta kiwi-builds para que el comando kiwi detallado en el paso 7 se ejecute sin problemas.

Configuración avanzada de un proyecto Kiwi:

Lo que hemos logrado con los pasos anteriores fue especificarle a Kiwi que deseamos que genere un sistema operativo con cierta configuración preliminar, con varios paquetes y repositorios predefinidos, una contraseña personalizada para el usuario root y poco más, nada demasiado especial.

Sin embargo, utilizando la posibilidad de agregar paquetes, instaladores, scripts, archivos de configuración, etc. dentro de la carpeta root (/) de nuestro proyecto kiwi, y de ejecutar comandos de consola durante la construcción de la imagen del sistema operativo mediante el script de configuración config.sh, podremos hacer cambios muy profundos al sistema operativo que estamos personalizando, algunos de los cuales veremos en este apartado.

Lo primero que es importante entender es que el script config.sh ya contiene algunas pocas directivas de configuración para la imagen del sistema operativo. Si lo editamos con el siguiente comando:
kiwi:~ # cd mysuseleap-15.2
kiwi:~/mysuseleap-15.2 # vim config.sh
Vemos que ya tiene secciones de configuración, como por ejemplo la sección "Setup baseproduct link" donde se ejecuta el script suseSetupProduct, la sección "Activate services" donde especifica la directiva para indicarle a kiwi que el servicio sshd debe estar activado e iniciado por defecto apenes termine la instalación del sistema operativo, o la sección "Setup default target, multi-user" donde se configura en que nivel de ejecución se debe iniciar el sistema operativo, opción que por cierto ya hemos modificado en los pasos previos.
#======================================
# Functions...
#--------------------------------------
test -f /.kconfig && . /.kconfig
test -f /.profile && . /.profile

#======================================
# Greeting...
#--------------------------------------
echo "Configure image: [$kiwi_iname]..."

#======================================
# Setup baseproduct link
#--------------------------------------
suseSetupProduct

#======================================
# Activate services
#--------------------------------------
suseInsertService sshd

#======================================
# Setup default target, multi-user
#--------------------------------------
baseSetRunlevel 5
Para facilitar la comprensión de los cambios que le estaremos haciendo a los archivos de configuración de nuestro sistema operativo y a las aplicaciones que vamos a instalar, me gusta agrupar todos los cambios dentro de una estructura de directorios dentro de la carpeta root de nuestro proyecto. A continuación agregaremos la carpeta kiwifiles que estará en el directorio raíz de nuestro sistema operativo y dentro de ella las carpetas os y user para agrupar los cambios que le haremos al sistema operativo en si y al entorno de los usuarios respectivamente:
kiwi:~ # cd mysuseleap-15.2/root
kiwi:~/mysuseleap-15.2/root # mkdir kiwifiles kiwi:~/mysuseleap-15.2/root # mkdir kiwifiles/os
kiwi:~/mysuseleap-15.2/root # mkdir kiwifiles/user
Pues bien, ya estamos listos para comenzar a ver diferentes ejemplos de como modificar nuestro sistema operativo durante el proceso de construcción de la imagen, a fin de ayudarnos a entender la cantidad de cambios que podemos hacer a nuestro sistema operativo sabiendo un poco de script bash y entiendo la estructura de los sistemas operativos basados en Linux.

Personalización del sistema operativo:

  1. RPMs: A veces tenemos que instalar archivos rpms manualmente para instalar aplicaciones que no suelen estar disponibles en los repositorios, como es el caso del instalador oficial de Java. Para ello lo que podemos hacer es crear el directorio rpms dentro del directorio ~/mysuseleap-15.2/root/kiwifiles/os y en él copiar el archivo rpm jre-8u261-linux-x64.rpm:
    kiwi:~ # mkdir ~/mysuseleap-15.2/root/kiwifiles/os/rpms
    kiwi:~ # cp ~/Downloads/jre-8u261-linux-x64.rpm ~/mysuseleap-15.2/root/kiwifiles/os/rpms
    
    Luego editamos el script config.sh:
    kiwi:~ # vim ~/mysuseleap-15.2/config.sh
    Y bien al final del script le agregamos las siguiente directiva para instalar automáticamente el paquete rpm jre-8u261-linux-x64.rpm:
    # ==================================================================
    # OS Configuration
    # ==================================================================
    
    # --> Instalación de <Java>
    zypper --non-interactive --no-gpg-checks in --allow-unsigned-rpm /kiwifiles/os/rpms/jre-8u261-linux-x64.rpm
    Si tendríamos más paquetes rpm que instalar solo bastaría con copiarlos en el misma carpeta y agregar más directivas como la anterior por cada paquete que deseamos instalar.

  2. CUPS: Si queremos que nuestro sistema operativo esté personalizado con impresoras por defecto, recomiendo crear el directorio cups dentro del directorio ~/mysuseleap-15.2/root/kiwifiles/os en el cual estaríamos copiando nuestros archivos de configuración personalizados de CUPS incluyendo los archivos de definición de las impresoras (.ppd). En este ejemplo dentro del host kiwi ya hemos configurado y probado una impresora, cuyos archivos de configuración se copian en la carpeta cups creada previamente:
    kiwi:~ # mkdir ~/mysuseleap-15.2/root/kiwifiles/os/cups
    kiwi:~ # cp /etc/cups/cupsd.conf ~/mysuseleap-15.2/root/kiwifiles/os/cups
    kiwi:~ # cp /etc/cups/printers.conf ~/mysuseleap-15.2/root/kiwifiles/os/cups
    kiwi:~ # cp /etc/cups/ppd/testprinter.ppd ~/mysuseleap-15.2/root/kiwifiles/os/cups
    Como anécdota quisiera mencionar que para la versión 2.2.7 de CUPS de openSUSE 15.0 tuvimos que agregar configuraciones relacionadas con el protocolo USB para que ciertas impresoras de la marca Zebra funcionaran adecuadamente, configuraciones que no eran necesarias en versiones previas de CUPS. Por un lado se tuvo que agregar las directivas "Option usb-no-reattach true" y "Option usb-unidir true" en la sección de la impresora en el archivo de configuración printers.conf, y por el otro se tuvo que modificar la configuración de las impresoras en el archivo org.cups.usb-quirks presente normalmente en el directorio /usr/share/cups/usb, donde en la sección correspondiente a la marca Zebra (0x0a5f) se le agregó a la directiva"0x0a5f unidir" la directiva "no-reattach", quedando "0x0a5f unidir no-reattach", en fin, estas cosas suelen pasar entre cambios de versiones, lo que para una versión funcionaba quizás para la siguiente ya no, lo que a veces puede ser frustrante. 

    Volviendo al tema procedemos a editar el script config.sh:
    kiwi:~ # vim ~/mysuseleap-15.2/config.sh
    Y al final del script le agregamos las siguientes directivas para copiar los archivos de configuración:
    # --> Configuración de <CUPS>
    cp /kiwifiles/os/cups/cupsd.conf /etc/cups/
    cp /kiwifiles/os/cups/printers.conf /etc/cups/
    cp /kiwifiles/os/cups/testprinter.ppd /etc/cups/ppd/
    chmod 640 /etc/cups/cupsd.conf chmod 600 /etc/cups/printers.conf chmod 644 /etc/cups/ppd/testprinter.ppd
    chown root:lp /etc/cups/cupsd.conf chown root:lp /etc/cups/printers.conf chown root:lp /etc/cups/ppd/testprinter.ppd
  3. SAMBA: El paso sería básicamente el mismo que los anteriores, creamos el directorio samba dentro del directorio ~/mysuseleap-15.2/root/kiwifiles/os y en él copiar el archivo personalizado smb.conf:
    kiwi:~ # mkdir ~/mysuseleap-15.2/root/kiwifiles/os/samba
    kiwi:~ # cp /etc/samba/smb.conf ~/mysuseleap-15.2/root/kiwifiles/os/samba
    
    Luego editamos el script config.sh:
    kiwi:~ # vim ~/mysuseleap-15.2/config.sh
    Y al final del script le agregamos las siguiente directiva para copiar el archivo smb.conf a ubicación correspondiente:
    # --> Configuración de <Samba>
    cp /kiwifiles/os/samba/smb.conf /etc/samba/ chmod 644 /etc/samba/smb.conf chown root:root /etc/samba/smb.conf
  4. HALT.LOCAL: A veces tenemos la necesidad de ejecutar comandos bash durante el apagado de la computadora para realizar ciertas tareas, como era mi caso donde al apagar la máquina solía ejecutar comandos para limpiar las órdenes de impresión colgadas que se enviaban a impresoras remotas a través del protocolo SAMBA.

    Para ello antes usaba al archivo halt.local que solía estar ubicado en el directorio /etc/init.d, sin embargo, desde la implementación de systemd ese archivo desapareció. Sin embargo, si verificamos el contenido del archivo /usr/lib/systemd/system/halt-local.service podremos comprobar que aún existe un servicio de systemd que utiliza y permite ejecutar directivas desde el archivo /etc/init.d/halt.local, solo falta crearlo. Pero como me gusta ordenarlo todo creamos el directorio services dentro del directorio ~/mysuseleap-15.2/root/kiwifiles/os y en él creamos el archivo halt.local y luego lo editamos:
    kiwi:~ # mkdir ~/mysuseleap-15.2/root/kiwifiles/os/services
    kiwi:~ # touch ~/mysuseleap-15.2/root/kiwifiles/os/services/halt.local
    kiwi:~ # vim ~/mysuseleap-15.2/root/kiwifiles/os/services/halt.local
    Y le agregamos un contenido similar a lo que sigue:
    #! /bin/sh
    #
    # La ejecucion del script halt.local se llama desde el servicio de 
    # systemd ubicado en /usr/lib/systemd/system/halt-local.service.
    
    # Se agrega el comando para la limpieza de las bases
    # de datos de impresoras samba al apagar el host.
    /bin/rm -f /var/lib/samba/printing/*.tdb
    Ahora ya podemos volver a editar el script config.sh:
    kiwi:~ # vim ~/mysuseleap-15.2/config.sh
    Y bien al final le agregamos las directivas que ubiquen el archivo halt.local en su posición correcta durante la construcción de la imagen del sistema operativo:
    # --> Configuración de <Services>
    cp /kiwifiles/os/services/halt.local /etc/init.d/ chmod 744 /etc/init.d/halt.local chown root:root /etc/init.d/halt.local
  5. FIREWALL: Algunos servicios como Samba, CUPS y VNC los suelo habilitar de forma adicional en el Firewall, como se puede apreciar en la configuración del archivo public.xml del directorio /etc/firewalld/zones:
    <?xml version="1.0" encoding="utf-8"?>
    <zone>
      <short>Public</short>
      <description>For use in public areas. You do not trust the other computers on networks to not harm your computer. Only selected incoming connections are accepted.</description>
      <service name="ssh"/>
      <service name="dhcpv6-client"/>
      <service name="vnc-server"/>
      <service name="samba-client"/>
      <service name="samba"/>
    </zone>
    Para mantener todo organizado podemos crear el directorio firewalld dentro del directorio ~/mysuseleap-15.2/root/kiwifiles/os y en él copiar todos los archivos de configuración de las zonas que deseamos:
    kiwi:~ # mkdir ~/mysuseleap-15.2/root/kiwifiles/os/firewalld
    kiwi:~ # cp /etc/firewalld/zones/public.xml ~/mysuseleap-15.2/root/kiwifiles/os/firewalld
    
    Luego editamos el script config.sh:
    kiwi:~ # vim ~/mysuseleap-15.2/config.sh
    Y al final del script le agregamos las directiva para copiar el archivo de configuración del servicio Firewalld a la ubicación correspondiente:
    # --> Configuración de <Firewalld>
    cp /kiwifiles/os/firewalld/public.xml /etc/firewalld/zones/
    chmod 644 /etc/firewalld/zones/public.xml
    chown root:root /etc/firewalld/zones/public.xml
  6. TIME SYNC & NTP: Para que nuestra distribución Linux personalizada sincronice automáticamente la fecha y la hora con servidores NTP remotos y corra como un servidor NTP local, procedemos a creamos el directorio ntp dentro del directorio ~/mysuseleap-15.2/root/kiwifiles/os y en él copiar todos los archivos de configuración modificados a nuestro gusto para dicho fin:
    kiwi:~ # mkdir ~/mysuseleap-15.2/root/kiwifiles/os/ntp
    kiwi:~ # cp /etc/adjtime/chrony.conf ~/mysuseleap-15.2/root/kiwifiles/os/ntp
    kiwi:~ # cp /etc/adjtime ~/mysuseleap-15.2/root/kiwifiles/os/ntp
    kiwi:~ # cp /etc/sysconfig/clock ~/mysuseleap-15.2/root/kiwifiles/os/ntp
    kiwi:~ # cp /etc/ntp.conf ~/mysuseleap-15.2/root/kiwifiles/os/ntp
    kiwi:~ # cp /etc/sysconfig/ntp ~/mysuseleap-15.2/root/kiwifiles/os/ntp
    Luego editamos el script config.sh:
    kiwi:~ # vim ~/mysuseleap-15.2/config.sh
    Y al final del script le agregamos las siguientes directivas:
    # --> Configuración de <NTP>
    cp /kiwifiles/os/ntp/chrony.conf /etc cp /kiwifiles/os/ntp/adjtime /etc cp /kiwifiles/os/ntp/clock /etc/sysconfig/ cp /kiwifiles/os/ntp/ntp.conf /etc/ cp /kiwifiles/os/ntp/ntp /etc/sysconfig/ chmod 640 /etc/chrony.conf chmod 644 /etc/adjtime chmod 644 /etc/sysconfig/clock chmod 640 /etc/ntp.conf chmod 644 /etc/sysconfig/ntp chown root:chrony /etc/chrony.conf chown root:root /etc/adjtime chown root:root /etc/sysconfig/clock chown root:root /etc/ntp.conf chown root:root /etc/sysconfig/ntp
  7. NETWORK: Una configuración de red que suelo pre-configurar en la imagen es la relacionada con el IP Forwarding, que en openSUSE la encontramos en el archivo sysctl.conf del directorio /etc (la configuración de la dirección IP y demás la veremos más adelante). Siguiendo la tradición de organizar todo bajo el directorio kiwifiles, creamos la carpeta network dentro del directorio ~/mysuseleap-15.2/root/kiwifiles/os y en él copiamos el mencionado archivo:
    kiwi:~ # mkdir ~/mysuseleap-15.2/root/kiwifiles/os/network
    kiwi:~ # cp /etc/sysctl.conf ~/mysuseleap-15.2/root/kiwifiles/os/network
    
    Luego editamos el script config.sh:
    kiwi:~ # vim ~/mysuseleap-15.2/config.sh
    Y al final del script le agregamos las directivas de copiado y seteo de siempre:
    # --> Configuración de <Network>
    cp /kiwifiles/os/network/sysctl.conf /etc/
    chmod 644 /etc/sysctl.conf
    chown root:root /etc/sysctl.conf
  8. SYSCONFIG: Creamos la carpeta sysconfig dentro del directorio ~/mysuseleap-15.2/root/kiwifiles/os :
    kiwi:~ # mkdir ~/mysuseleap-15.2/root/kiwifiles/os/sysconfig
    Y en él copiamos los archivos de configuración del directorio /etc/sysconfig que necesitamos, que en mi caso suelo incluir el archivo de configuración displaymanager que lleva seteado de forma personalizada las siguientes directivas:
    DISPLAYMANAGER_REMOTE_ACCESS="yes"
    DISPLAYMANAGER_ROOT_LOGIN_REMOTE="yes"
    DISPLAYMANAGER_AUTOLOGIN="gabriel"
    y el archivo keyboard que lleva personalizada la directiva KBD_NUMLOCK como se observa a continuación:
    KBD_NUMLOCK="yes"
    Podemos copiar los archivos del directorio /etc/sysconfig a partir del servidor que usamos para construir la imagen:
    kiwi:~ # cp /etc/sysconfig/displaymanager ~/mysuseleap-15.2/root/kiwifiles/os/sysconfig
    kiwi:~ # cp /etc/sysconfig/keyboard ~/mysuseleap-15.2/root/kiwifiles/os/sysconfig
    Luego editamos el script config.sh:
    kiwi:~ # vim ~/mysuseleap-15.2/config.sh
    Y al final del script le agregamos las directivas de copiado y seteos correspondientes:
    # --> Configuración de <Sysconfig>
    cp /kiwifiles/os/sysconfig/displaymanager /etc/sysconfig/
    cp /kiwifiles/os/sysconfig/keyboard /etc/sysconfig/
    chmod 644 /etc/sysconfig/displaymanager
    chmod 644 /etc/sysconfig/keyboard
    chown root:root /etc/sysconfig/displaymanager
    chown root:root /etc/sysconfig/keyboard
  9. ARTWORK: A un sistema operativo personalizado no le puede faltar un artwork que no sea personalizado. Yo he llegado a crear iconos (en formato png), así como fondos de pantalla y fondos para la ventana de logueo personalizados. Si creamos fondos de pantallas (wallpapers) es importante crear diferentes tamaños de imagen para que se adapten automáticamente a la resolución de nuestro monitor, deberían tener las extensión jpg con nombres similar a los siguientes: 1024x768.jpg, 1280x1024.jpg, 1280x800.jpg, 1366x768.jpg, 1440x900.jpg, 1600x1200.jpg, 1638x1024.jpg, 1680x1050.jpg, 1920x1080.jpg, 1920x1200.jpg, 2560x1440.jpg, 2560x1600.jpg, 3200x1800.jpg, 3200x2000.jpg y 3840x2160.jpg.

    La estructura que utilicé para organizar todo fue crear la carpeta artwork dentro del directorio ~/mysuseleap-15.2/root/kiwifiles/os y en él crear otras tres subcarpetas: icons, loginscreen y  wallpapers. En la subcarpeta icons van los iconos con extensión png, en la subcarpeta loginscreen las imagen personalizada con extensión jpg para la pantalla de logueo, y en la subcarpeta wallpapers todas las imágenes jpg diseñados para dicho fin:
    kiwi:~ # mkdir ~/mysuseleap-15.2/root/kiwifiles/os/artwork
    kiwi:~ # mkdir ~/mysuseleap-15.2/root/kiwifiles/os/artwork/icons
    kiwi:~ # mkdir ~/mysuseleap-15.2/root/kiwifiles/os/artwork/loginscreen
    kiwi:~ # mkdir ~/mysuseleap-15.2/root/kiwifiles/os/artwork/wallpapers
    Luego editamos el script config.sh:
    kiwi:~ # vim ~/mysuseleap-15.2/config.sh
    Y al final del script le agregamos las directivas de copiado y seteos similares a lo que sigue:
    # --> Configuración de <Artwork>
    mkdir -p /usr/share/icons/tdbsuse/
    cp -f /kiwifiles/os/artwork/wallpapers/*.jpg /usr/share/wallpapers/openSUSEdefault/contents/images/
    cp /kiwifiles/os/artwork/loginscreen/login-screen.jpg /usr/share/sddm/themes/breeze-openSUSE
    cp /kiwifiles/os/artwork/icons/*.png /usr/share/icons/tdbsuse/
    chmod 644 /usr/share/wallpapers/openSUSEdefault/contents/images/*.jpg
    chmod 644 /usr/share/sddm/themes/breeze-openSUSE/login-screen.jpg
    chmod 644 /usr/share/icons/tdbsuse/*.png
    chown root:root /usr/share/wallpapers/openSUSEdefault/contents/images/*.jpg
    chown root:root /usr/share/sddm/themes/breeze-openSUSE/login-screen.jpg
    chown root:root /usr/share/icons/tdbsuse/*.png
  10. SDDM: Podemos realizar algunos cambios al gestor de pantallas (Simple Desktop Display Manager). A continuación vamos a configurar el fondo de pantalla de la pantalla de desbloqueo de usuarios, para lo cual creamos la carpeta sddm dentro del directorio ~/mysuseleap-15.2/root/kiwifiles/os y en él creamos el archivo theme.conf.user para luego editarlo:
    kiwi:~ # mkdir ~/mysuseleap-15.2/root/kiwifiles/os/sddm
    kiwi:~ # touch ~/mysuseleap-15.2/root/kiwifiles/os/sddm/theme.conf.user
    kiwi:~ # vim ~/mysuseleap-15.2/root/kiwifiles/os/sddm/theme.conf.user
    Una vez abierto el archivo theme.conf.user le agregamos el siguiente contenido, donde se destaca la directiva background a la que se le asigna el nombre del archivo de imagen login-screen.jpg que se usará como fondo del SDDM, y que en el paso anterior ya habíamos copiado al directorio /usr/share/sddm/themes/breeze-openSUSE:
    [General]
    background=login-screen.jpg
    type=image
    Ahora solo nos resta guardar los cambios y editar el archivo config.sh:
    kiwi:~ # vim ~/mysuseleap-15.2/config.sh
    Al cual le agregamos las siguientes directivas de configuración:
    # --> Configuración de <SDDM>
    mkdir -p /usr/share/sddm/themes/breeze-openSUSE
    cp /kiwifiles/os/sddm/theme.conf.user /usr/share/sddm/themes/breeze-openSUSE
    chmod 644 /usr/share/sddm/themes/breeze-openSUSE/theme.conf.user chown root:root /usr/share/sddm/themes/breeze-openSUSE/theme.conf.user
  11. FIRSTBOOT: Esta funcionalidad nos permite personalizar el primer arranque de nuestro sistema operativo luego de que se haya instalado en una PC o en una máquina virtual. Comenzamos por crear la carpeta firstboot dentro del directorio ~/mysuseleap-15.2/root/kiwifiles/os, para luego copiar en él el archivo firstboot.xml y crear el archivo nuevo customs-after-firstboot:
    kiwi:~ # mkdir ~/mysuseleap-15.2/root/kiwifiles/os/firstboot
    kiwi:~ # cp /etc/YaST2/firstboot.xml ~/mysuseleap-15.2/root/kiwifiles/os/firstboot/firstboot.xml
    kiwi:~ # touch ~/mysuseleap-15.2/root/kiwifiles/os/firstboot
    /customs-after-firstboot
    Comenzamos por editar el archivo firstboot.xml:
    kiwi:~ # vim ~/mysuseleap-15.2/root/kiwifiles/os/firstboot/firstboot.xml
    La configuración del archivo firstboot.xml permite indicarle al módulo firstboot de YaST que interfaces de configuración deben mostrarse durante el primer arranque del sistema operativo. La configuración debería quedar similar a lo que sigue (para más información pueden consultar éste enlace):
    <?xml version="1.0"?>
    <productDefines  xmlns="http://www.suse.com/1.0/yast2ns"
        xmlns:config="http://www.suse.com/1.0/configns">
    
        <!--
        $Id$
        Work around for the text domain
        textdomain="firstboot"
        -->
    
        <textdomain>firstboot</textdomain>
    
        <globals>
    
            <!--
            If a variable root_password_as_first_user is present in globals section,
            inst_user step will have the check box
                "Use this password for system administrator"
            so you don't need to include root password step (fate#306297).
            If the variable is missing (commented), the check box won't appear.
    
            The value of the variable (true/false) will set the default value for the check box.
            <root_password_as_first_user config:type="boolean">true</root_password_as_first_user>
             -->
    
            <!-- The default value of "Automatic Login" checkbox -->
            <enable_autologin config:type="boolean">false</enable_autologin>
    
            <!--
            For more variables that can be in this section, look into the control file
            (/etc/YaST2/control.xml or root directory of installation media)
            -->
    
            <!--
            Definition of Automatic Configuration Steps follows - each step
            runs non-interactive configuration. To enable steps defined in
            Automatic Configuration, enable inst_automatic_configuration in the
            workflow.
            -->
            <automatic_configuration config:type="list">
    <!-- Configure network --> <ac_step> <text_id>ac_label_1</text_id>
    <icon>yast-lan</icon>
    <type>proposals</type>
    <ac_items config:type="list">
    <ac_item>lan</ac_item>
    </ac_items>
    </ac_step>
    <!-- Configure printer (needs configured network for net-detection) --> <ac_step> <text_id>ac_label_2</text_id>
    <icon>yast-hwinfo</icon>
    <type>proposals</type>
    <ac_items config:type="list">
    <ac_item>printer</ac_item>
    </ac_items>
    </ac_step> </automatic_configuration> </globals> <proposals config:type="list">
    <proposal> <name>
    firstboot_hardware</name> <mode>installation</mode> <stage>firstboot</stage> <label>Hardware Configuration</label> <proposal_modules config:type="list">
    <proposal_module>
    printer</proposal_module> </proposal_modules> </proposal> </proposals> <workflows config:type="list">
    <workflow> <defaults> <enable_back>
    yes</enable_back> <enable_next>yes</enable_next> <archs>all</archs> </defaults> <stage>firstboot</stage> <label>Configuration</label> <mode>installation</mode> <modules config:type="list"> <!-- <module> <label>Network Autosetup</label> <name>firstboot_setup_dhcp</name> </module> --> <module> <label>Language and Keyboard</label> <enabled config:type="boolean">false</enabled>
    <!-- step for configuration of both language and keyboard layout (fate#306296) --> <name>firstboot_language_keyboard</name> </module> <module> <label>Language</label> <enabled config:type="boolean">false</enabled>
    <name>
    firstboot_language</name> </module> <module> <label>Keyboard Layout</label> <enabled config:type="boolean">false</enabled>
    <name>
    firstboot_keyboard</name> </module> <!-- <module> <label>Welcome</label> <name>firstboot_welcome</name> </module> --> <module> <label>License Agreement</label> <enabled config:type="boolean">false</enabled>
    <name>
    firstboot_licenses</name> </module> <module> <!-- Rutina firstboot no compatible en openSUSE 15.2 --> <label>Host Name</label> <enabled config:type="boolean">false</enabled>
    <name>
    firstboot_hostname</name> </module> <module> <label>Network</label> <!-- this step only restarts service 'network' --> <name>firstboot_network_write</name> <enabled config:type="boolean">false</enabled>
    </module> <module> <enabled
    config:type="boolean">false</enabled>
    <name>
    firstboot_ssh</name> </module> <module> <!-- <label>Network</label> --> <label>Configuración de Red</label> <name>inst_lan</name> <enabled config:type="boolean">true</enabled> <arguments> <skip_detection>true</skip_detection> </arguments>
    </module> <module> <label>
    Automatic Configuration</label> <name>inst_automatic_configuration</name> <enabled config:type="boolean">false</enabled>
    </module> <module> <label>
    Time and Date</label> <enabled config:type="boolean">false</enabled>
    <name>
    firstboot_timezone</name> </module> <module> <label>NTP Client</label> <enabled config:type="boolean">false</enabled>
    <name>
    firstboot_ntp</name> </module> <module> <label>Desktop</label> <enabled config:type="boolean">false</enabled>
    <name>
    firstboot_desktop</name> </module> <!-- <module> <label>Network</label> <name>inst_lan</name> <enabled config:type="boolean">false</enabled> </module> --> <module> <label>Users</label> <enabled config:type="boolean">false</enabled>
    <name>
    firstboot_user</name> </module> <module> <label>Root Password</label> <enabled config:type="boolean">false</enabled> <name>firstboot_root</name> </module> <module> <label>Customer Center</label> <name>registration</name> <enabled config:type="boolean">false</enabled>
    </module> <module> <label>
    Hardware</label> <name>inst_proposal</name> <enabled config:type="boolean">false</enabled> <proposal>firstboot_hardware</proposal> </module> <module> <!-- <label>Finish Setup</label> --> <label>Configuración Final</label> <name>firstboot_write</name> <enable_back>no</enable_back> <enable_next>no</enable_next> </module> <!-- <module> <label>Finish Setup</label> <name>inst_congratulate</name> <enable_back>yes</enable_back> <enable_next>yes</enable_next> </module> --> </modules> </workflow> </workflows> <texts> <!-- Labels used during Automatic Configuration: use "text_id" from "ac_step" --> <ac_label_1><label>Configuring network...</label></ac_label_1>
    <ac_label_2><label>Configuring hardware...</label></ac_label_2>
    </texts> </productDefines>
    OBS: En la sección de la rutina inst_lan (resaltada en amarillo) se le tuvo que agregar el argumento <skip_detection>true</skip_detection> para permitir que el usuario pueda realizar la configuración manual de la red durante el primer arranque del sistema operativo, algo que en las versiones previas de openSUSE no era requerido y que tampoco se menciona como una observación en el archivo original firstboot.xml del directorio /etc/YaST2 de cualquier instalación oficial de openSUSE 15.2.

    Para identificar cual era la causa del porque la rutina inst_lan del archivo firstboot.xml no funcionaba como yo esperaba, tuve que verificar el código del programa inst_lan.rb de YaST2 ubicado en el directorio /usr/share/YaST2/clients (donde también están los programas de las demás rutinas), código que a su vez hacía referencia al archivo inst_lan.rb pero del directorio /usr/share/YaST2/lib/network/clients. Dentro de este último archivo los programadores pusieron la siguiente aclaración que trajo la luz luego de varias semanas de pruebas:
      # Client for configuring the network during installation.
      #
      # If the network configuration is managed by NetworkManager or some
      # connection config is already present the client skip the configuration
      # sequence.
      #
      # The configuration sequence can be forced passing the 'skip_detection'
      # argument.
      #
      # @example calling the client forcing the configuration sequence
      #   Yast::WFM.CallFunction("inst_lan", [args.merge("skip_detection" => true)])
      #
      # @example firsboot xml forcing the configuration sequence
      #   <module>
      #     <label>Network</label>
      #      <name>inst_lan</name>
      #      <arguments>
      #        <skip_detection>true</skip_detection>
      #      </arguments>
      #   </module>
    En fin, volviendo al tema pasamos a editar el archivo customs-after-firstboot:
    kiwi:~ # vim ~/mysuseleap-15.2/root/kiwifiles/os/firstboot/customs-after-firstboot
    Al que le podemos agregar un script bash similar al que sigue, que se ejecutará una sola vez durante el primer arranque del sistema operativo:
    #!/bin/bash
    
    echo ""
    echo "============================================"
    echo "STARTING CUSTOMS SCRIPT AFTER FIRSTBOOT!!"
    echo "============================================"
    
    # Creación del usuario gabriel y expiración de contraseña inicial gabrielpass.
    /usr/sbin/useradd --create-home --comment Gabriel -g users -G dialout -p $(/usr/bin/perl -e'print crypt("gabrielpass", "SALT_text")') gabriel
    
    # Expira la contraseña por defecto del  y solicita el cambio (Opción no soportada por SDDM)
    #/usr/bin/passwd --expire gabriel
    # Configuracion del servicio xinetd. systemctl enable xinetd.service systemctl start xinetd.service # Configuración del fecha y hora systemctl enable chronyd.service systemctl restart chronyd.service # Configuracion del servicio Cups. systemctl enable cups.service systemctl start cups.service # Configuracion del servicio Samba. systemctl enable smb.service systemctl enable nmb.service systemctl start smb.service systemctl start nmb.service # Configuración del firewall systemctl enable firewalld.service systemctl start firewalld.service # Se agregan entradas al archivo /etc/hosts para los sistemas. /bin/echo "192.168.1.222 miServidor" >> /etc/hosts # Se comenta la linea de la IP local 127.0.0.2 /bin/sed -i "s/127.0.0.2/#127.0.0.2/g" /etc/hosts # Configuracion de prioridades del JRE (Oracle). /usr/sbin/update-alternatives --remove-all java /usr/sbin/update-alternatives --install /usr/bin/java java /usr/java/jre1.8.0_261-amd64/bin/java 100 /usr/sbin/update-alternatives --install /usr/lib64/browser-plugins/libjavaplugin.so libjavaplugin.so.x86_64 /usr/java/jre1.8.0_261-amd64/lib/amd64/libnpjp2.so 20000 # Se cambia el grub a español y luego se cambia el timeout a solo 1 segundo. LANG=es_ES grub2-mkconfig -o /boot/grub2/grub.cfg /bin/sed -i 's/timeout=10/timeout=1/g' /boot/grub2/grub.cfg # Finalmente se elimina el script customs-after-firstboot. /bin/rm -f /usr/share/firstboot/scripts/customs-after-firstboot echo "============================================" echo "FINISH CUSTOMS SCRIPT AFTER FIRSTBOOT!!" echo "============================================" echo ""
    OBS: El motivo por el cual los servicios como samba, cups, xinetd, etc. se activan e inician desde el script customs-after-firstboot y no desde el script config.sh se debe al orden de ejecución de las directivas del archivo config.sh, primero se estarían arrancando los servicios y luego se estarían configurando, por lo que las configuraciones no estarían activas hasta tanto se reinicie el sistema operativo. Si bien existe la posibilidad de modificar el orden en que se realizan las configuraciones dentro del script config.sh, es interesante mostrar el potencial que permite la utilización del script customs-after-firstboot.

    Finalmente haremos la última edición al script config.sh en este apartado:
    kiwi:~ # vim ~/mysuseleap-15.2/config.sh
    Y al final del script le agregamos las directivas de copiado y seteos similares a lo que sigue:
    # --> Configuración de <Firstboot>
    cp /etc/YaST2/firstboot.xml /etc/YaST2/firstboot.xml.orig
    cp /kiwifiles/os/firstboot/firstboot.xml /etc/YaST2/
    cp /kiwifiles/os/firstboot/customs-after-firstboot /usr/share/firstboot/scripts
    touch /var/lib/YaST2/reconfig_system
    chmod 644 /etc/YaST2/firstboot.xml
    chmod 755 /usr/share/firstboot/scripts/customs-after-firstboot
    chown root:root /etc/YaST2/firstboot.xml
    chown root:root /usr/share/firstboot/scripts/customs-after-firstboot
    
  12. Y con esto terminamos el apartado de personalizaciones relacionadas con configuraciones del sistema operativo.

Personalización del entorno de usuario:

En mis primeros intentos solía hacer cambios a archivos de configuraciones del entorno de uno de los usuarios ya creados previamente, pero el inconveniente de este enfoque es que al momento de crear un nuevo usuario desde la línea de comandos o desde YaST ninguna de estas configuraciones se pasaban al nuevo usuario.

Investigando más sobre el tema pude averiguar que la configuración inicial de cualquier usuario que se crea está basado en la estructura de directorios y de configuración del directorio /etc/skel, entre otros directorios que veremos a continuación:
  1. Configuraciones de x11vnc en /etc/skel: Todo lo que agreguemos bajo este directorio se agregará automáticamente al home de un usuario al crearlo. Me gusta que al crear un usuario ya esté la configuración del servicio VNC, para lo cual suelo crear el directorio vnc dentro del directorio ~/mysuseleap-15.2/root/kiwifiles/user y copiar adentro los archivos de configuración requeridos por la aplicación x11vncpasswd, x11vnc.desktop y x11vncrc
    kiwi:~ # mkdir ~/mysuseleap-15.2/root/kiwifiles/user/vnc
    
    Luego editamos el script config.sh:
    kiwi:~ # vim ~/mysuseleap-15.2/config.sh
    Y bien al final del script le agregamos las siguientes directivas que crean las subcarpetas requeridas dentro del directorio /etc/skel, copian los archivos de configuración y configuran los permisos y el dueño de los archivos, que dentro de skel es root.
    # ==================================================================
    # User Configuration
    # ==================================================================
    
    # --> Configuración de <VNC>
    mkdir -p /etc/skel/.config/autostart/
    mkdir -p /etc/skel/.vnc
    cp /kiwifiles/user/vnc/passwd /etc/skel/.vnc
    cp /kiwifiles/user/vnc/x11vncrc /etc/skel/.x11vncrc
    cp /kiwifiles/user/vnc/x11vnc.desktop /etc/skel/.config/autostart/
    chmod -Rf 700 /etc/skel/.vnc
    chmod -Rf 600 /etc/skel/.vnc/passwd
    chmod 644 /etc/skel/.x11vncrc
    chmod 755 /etc/skel/.config/autostart/x11vnc.desktop
    chown -Rf root:root /etc/skel/.vnc
    chown root:root /etc/skel/.x11vncrc
    chown -Rf root:root /etc/skel/.config/autostart
  2. Configuraciones de Java en /etc/skel: A veces necesitamos que nuestro entorno Java ya esté pre configurado para cualquier usuario, para ello podemos almacenar todos los archivos de configuración y certificados en la carpeta java creada dentro del directorio ~/mysuseleap-15.2/root/kiwifiles/user:
    kiwi:~ # mkdir ~/mysuseleap-15.2/root/kiwifiles/user/java
    
    Editamos el script config.sh:
    kiwi:~ # vim ~/mysuseleap-15.2/config.sh
    Y bien al final le agregamos las directivas para crear nuevas subcarpetas en /etc/skel requeridas por Java, en las cuales luego se copian los archivos de configuración de Java:
    # --> Configuración de <Java>
    mkdir -p /etc/skel/.java/deployment/security
    cp /kiwifiles/user/java/deployment.properties /etc/skel/.java/deployment/
    cp /kiwifiles/user/java/exception.sites /etc/skel/.java/deployment/security
    cp /kiwifiles/user/java/trusted.cacerts /etc/skel/.java/deployment/security
    cp /kiwifiles/user/java/trusted.certs /etc/skel/.java/deployment/security
    chmod -Rf 755 /etc/skel/.java
    chmod 644 /etc/skel/.java/deployment/deployment.properties
    chmod 644 /etc/skel/.java/deployment/security/exception.sites
    chmod 644 /etc/skel/.java/deployment/security/trusted.cacerts
    chmod 644 /etc/skel/.java/deployment/security/trusted.certs
    chown -Rf root:root /etc/skel/.java
  3. Configuraciones de Konsole en /etc/skel: Muchas cosas podemos personalizar en la aplicación Konsole, podemos agregar un nuevo perfil de usuario que configure las funciones por defectos de teclas, esquema de colores, etc. Todos estos archivos los metemos en la carpeta konsole creada dentro del directorio ~/mysuseleap-15.2/root/kiwifiles/user:
    kiwi:~ # mkdir ~/mysuseleap-15.2/root/kiwifiles/user/konsole
    
    Editamos el script config.sh:
    kiwi:~ # vim ~/mysuseleap-15.2/config.sh
    Y bien al final le agregamos las directivas para crear subcarpetas y copiar los archivos de configuración en los directorios correspondientes:
    # --> Configuración de <Konsole>
    mkdir -p /etc/skel/.local/share/konsole/ cp /kiwifiles/user/konsole/konsoleui.rc /etc/skel/.local/share/konsole/ cp /kiwifiles/user/konsole/myprofile.keytab /etc/skel/.local/share/konsole/ cp /kiwifiles/user/konsole/myprofile.profile /etc/skel/.local/share/konsole/ cp /kiwifiles/user/konsole/WhiteOnBlack.colorscheme /etc/skel/.local/share/konsole/ cp /kiwifiles/user/konsole/konsolerc /etc/skel/.config chmod -Rf 700 /etc/skel/.local chmod 644 /etc/skel/.local/share/konsole/konsoleui.rc chmod 644 /etc/skel/.local/share/konsole/myprofile.keytab chmod 644 /etc/skel/.local/share/konsole/myprofile.profile chmod 644 /etc/skel/.local/share/konsole/WhiteOnBlack.colorscheme chmod 600 /etc/skel/.config/konsolerc chown -Rf root:root /etc/skel/.local chown root:root /etc/skel/.config/konsolerc
  4. Configuraciones de KDE en /etc/skel: En los entornos de usuario de mis sistemas operativos personalizados me gusta inhabilitar Kwallet (no hay peor molestia) y configurar el bloqueo automático de la pantalla por inactividad del usuario. Para ello dentro de la carpeta kde creada dentro del directorio ~/mysuseleap-15.2/root/kiwifiles/user ubicamos los archivos de configuración personalizados kwalletrc y kscreenlockerrc.
    kiwi:~ # mkdir ~/mysuseleap-15.2/root/kiwifiles/user/kde
    
    Editamos el script config.sh:
    kiwi:~ # vim ~/mysuseleap-15.2/config.sh
    Y bien al final le agregamos las directivas que copian los archivos de configuración kwalletrc kscreenlockerrc a sus debidos directorios dentro del directorio esqueleto.
    # --> Configuración de <Kde>
    cp /kiwifiles/user/kde/kwalletrc /etc/skel/.config cp /kiwifiles/user/kde/kscreenlockerrc /etc/skel/.config
  5. Configuraciones de PLASMA en /usr/share/plasma: ¿Que tal si nos gustaría pre configurar la altura de la barra de KDE? ¿Agregar y quitar widgets del escritorio? Para ello hay que modificar los archivos .js de Plasma layout.js y org.kde.plasma.desktop-layout.js, donde el primero permite configurar el aspecto del escritorio, como la altura de la barra (panel en términos de KDE) y su composición, agregando, reemplazando y/o quitando widgets:
    // -- OBS: Aumento del tamaño del panel inferior a 2.5.
    //panel.height = gridUnit * 2
    panel.height = gridUnit * 2.5
    
    // -- OBS: Se ha reemplazado el tipo de lanzador del menú de aplicaciones
    // predefinido "kickoff" por el más sencillo "kicker".
    //var kickoff = panel.addWidget("org.kde.plasma.kickoff")
    var kickoff = panel.addWidget("org.kde.plasma.kicker")
    kickoff.currentConfigGroup = ["Shortcuts"]
    kickoff.writeConfig("global", "Alt+F1")
    
    // -- OBS: Se comenta la directiva que agrega el widget 
    // Activity Manager que es poco útil para la mayoría.
    //panel.addWidget("org.kde.plasma.showActivityManager")
    panel.addWidget("org.kde.plasma.pager")
    panel.addWidget("org.kde.plasma.taskmanager")
    
    Y el segundo bloquear por defecto la modificación de los mismo agregando el código que sigue a continuación:
    // -- OBS: Bloqueo automático de widgets.
    sleep(2);
    locked = true;
    
    Una vez personalizados dichos archivos .js lo mejor es ubicarlos dentro del directorio plasma previamente creado dentro del directorio ~/mysuseleap-15.2/root/kiwifiles/user:
    kiwi:~ # mkdir ~/mysuseleap-15.2/root/kiwifiles/user/plasma
    
    Luego editamos el script config.sh:
    kiwi:~ # vim ~/mysuseleap-15.2/config.sh
    Y al final del script agregamos las directivas de copiado y seteo correspondientes:
    # --> Instalación de <Plasma>
    cp -b /kiwifiles/user/plasma/layout.js /usr/share/plasma/layout-templates/org.opensuse.desktop.defaultPanel/contents/
    cp -b /kiwifiles/user/plasma/org.kde.plasma.desktop-layout.js /usr/share/plasma/look-and-feel/org.openSUSE.desktop/contents/layouts/
    chmod 644 /usr/share/plasma/layout-templates/org.opensuse.desktop.defaultPanel/contents/layout.js
    chmod 644 /usr/share/plasma/look-and-feel/org.openSUSE.desktop/contents/layouts/org.kde.plasma.desktop-layout.js
    chown root:root /usr/share/plasma/layout-templates/org.opensuse.desktop.defaultPanel/contents/layout.js
    chown root:root /usr/share/plasma/look-and-feel/org.openSUSE.desktop/contents/layouts/org.kde.plasma.desktop-layout.js
  6. Configuraciones de accesos directos en /usr/share/kio_desktop: A mi me gusta que el usuario ya cuente con una serie de accesos directos a aplicaciones importantes y útiles en su escritorio. Si hemos creado nuestros propios accesos directos lo mejor sería colocarlos en la carpeta desktop, que la podemos crear dentro del directorio ~/mysuseleap-15.2/root/kiwifiles/os:
    kiwi:~ # mkdir ~/mysuseleap-15.2/root/kiwifiles/user/desktop
    
    Luego editamos el script config.sh:
    kiwi:~ # vim ~/mysuseleap-15.2/config.sh
    Y bien al final del script config.sh agregamos las siguientes directivas para que al crear un nuevo usuario automáticamente aparezcan en su escritorio nuestros accesos personalizados (en el ejemplo cups.desktop y tdb.desktop) y aquellos ya existentes dentro del OS como suelen ser los accesos directos de Konsole, Calc y Writer de Libre Office, Firefox, entre otros.
    # --> Instalación de <Desktop App Links>
    cp /kiwifiles/user/desktop/tdb.desktop /usr/share/kio_desktop/DesktopLinks
    cp /kiwifiles/user/desktop/cups.desktop /usr/share/kio_desktop/DesktopLinks
    cp /usr/share/applications/org.kde.konsole.desktop /usr/share/kio_desktop/DesktopLinks/konsole.desktop
    cp /usr/share/applications/org.kde.kwrite.desktop /usr/share/kio_desktop/DesktopLinks/kwrite.desktop
    cp /usr/share/applications/org.kde.kcalc.desktop /usr/share/kio_desktop/DesktopLinks/kcalc.desktop
    cp /usr/share/applications/calc.desktop /usr/share/kio_desktop/DesktopLinks
    cp /usr/share/applications/writer.desktop /usr/share/kio_desktop/DesktopLinks
    cp /usr/share/applications/firefox.desktop /usr/share/kio_desktop/DesktopLinks
    chmod 744 /usr/share/kio_desktop/DesktopLinks/tdb.desktop
    chmod 744 /usr/share/kio_desktop/DesktopLinks/cups.desktop
    chmod 744 /usr/share/kio_desktop/DesktopLinks/konsole.desktop
    chmod 744 /usr/share/kio_desktop/DesktopLinks/kwrite.desktop
    chmod 744 /usr/share/kio_desktop/DesktopLinks/kcalc.desktop
    chmod 744 /usr/share/kio_desktop/DesktopLinks/calc.desktop
    chmod 744 /usr/share/kio_desktop/DesktopLinks/writer.desktop
    chmod 744 /usr/share/kio_desktop/DesktopLinks/firefox.desktop
    chown root:root /usr/share/kio_desktop/DesktopLinks/tdb.desktop
    chown root:root /usr/share/kio_desktop/DesktopLinks/cups.desktop
  7. Y eso es todo por el momento en esta sección, ya dependerá de cada quien adaptar los ejemplos anteriores a sus propias necesidades.


Personalizaciones adicionales:

  1. Rehashing SSL Certificates: Esta configuración proviene de la herramienta web SUSE Studio, que al final del archivo config.sh agregaba la llamada al comando c_rehash, que en teoría sirve para actualizar los enlaces simbólicos de cada archivo .pem, .crt, .cer, o .crl presentes en la imagen empaquetada del OS.

    Para agregar la llamada a dicho comando comenzamos por editar el script config.sh:
    kiwi:~ # vim ~/mysuseleap-15.2/config.sh
    Y agregar el siguiente script:
    # ==================================================================
    # SSL Certificates Configuration
    # ==================================================================
    echo '** Rehashing SSL Certificates...'
    c_rehash
  2. motd: Si cada vez que nos conectemos por ssh al equipo queremos que en pantalla nos muestre un mensaje de bienvenida luego de loguearnos, podemos editar el archivo motd ubicado en el directorio ~/mysuseleap-15.2/root/etc:
    kiwi:~ # vim ~/mysuseleap-15.2/root/etc/motd
    Al que por ejemplo le podríamos agregar un mensaje de bienvenida similar al que sigue:
    +-------------------------------------------------------------+
    | Bienvenido a la distribucion Linux TDB openSUSE 15.2 x86_64 |
    |      personalizada para el blog tormentadebits(dot)com      |
    +-------------------------------------------------------------+
  3. Y eso es todo lo que tenía pensado comentar bajo esta sección.


Comandos útiles para personalizar un sistema operativo:

  1. Comando para buscar los últimos archivos modificados en el sistema operativo Linux desde hace 15 minutos atrás. Útil para identificar archivos de configuración recientemente modificados:
    kiwi:~ # find /etc -type f -mmin -15
  2. Comando para buscar texto en el contenido de los archivos de un directorio:
    kiwi:~ # grep -rnw /usr/share/plasma -e loadTemplate
  3. Comando para hallar los diez directorios con mayor tamaño de archivos:
    kiwi:~ # du -Sh | sort -rh | head -10
  4. Comando para generar el hash code de una cadena de texto que deseamos encriptar:
    kiwi:~ # openssl passwd -1 -salt 'salt_text', cadenta_de_texto_a_encriptar


Construcción de la imagen del sistema operativo personalizado de forma avanzada:

  1. Llegados hasta este punto ya estamos listos para volver a repetir la construir de la imagen de nuestra distribución Linux personalizada de forma avanzada, pero antes de seguir sería recomendable cambiar la versión de nuestro proyecto editando el archivo config.xml del directorio ~/mysuseleap-15.2:
    kiwi:~ # vim ~/mysuseleap-15.2/config.xml
    Donde en la línea resaltada en verde cambiamos a la versión 1.1.0 que separarla de la compilación anterior:
    <?xml version="1.0" encoding="utf-8"?>
    
    <image schemaversion="6.8" name="tdb-openSUSE-Leap-15.2">
        <description type="system">
            <author>Gabriel</author>
            <contact>contacto(arroba)tormentadebits(dot)com</contact>
            <specification>
                TDB openSUSE Leap 15.2 es una distro Linux corporativa basada en openSUSE.
            </specification>
        </description>
        <preferences>
            <type image="iso" primary="true" flags="overlay" hybrid="true" firmware="efi" kernelcmdline="splash" hybridpersistent_filesystem="ext4" hybridpersistent="true" mediacheck="true"/>
            <version>1.1.0</version>
  2. También sería interesante crear un archivo denominado Changelog.txt dentro del directorio ~/mysuseleap-15.2/root/kiwifiles, editarlo:
    kiwi:~ # touch ~/mysuseleap-15.2/root/kiwifiles/Changelog.txt
    kiwi:~ # vim ~/mysuseleap-15.2/root/kiwifiles/Changelog.txt
    Para agregarle todos los cambios que vamos incorporando en cada versión de nuestra distribución Linux personalizada:
    =================
    CHANGELOG
    =================
    
    Versión 1.1.0:
    ---------------
    - Incorporación de modificaciones avanzadas mediante el script config.sh.
    
    
    Versión 1.0.0:
    --------------
    - Personalización de nombre de la distribución, repositorios, paquetes, etc.
    mediante el archivo config.xml.
  3. Finalmente borramos la construcción anterior y volvemos a ejecutar el comando que se encargará de construir la nueva versión de la imagen:
    kiwi:~ # rm -Rf ~/kiwi-builds
    kiwi:~ # mkdir ~/kiwi-builds
    kiwi:~ # kiwi --type oem system build --description ~/mysuseleap-15.2 --target-dir ~/kiwi-builds
  4. Si queremos copiar la imagen en un pendrive para facilitar su arranque e instalación en otra computadora, podemos instalar la aplicación imagewriter, que al ejecutarla nos desplegará una ventana gráfica que detecta el pendrive conectado ,y a la cual podemos arrastrar el archivo .iso y posteriormente presionar el botón Write para iniciar el proceso de escritura:
    kiwi:~ # zypper in imagewriter
    kiwi:~ # imagewriter
  5. Y con eso finalizamos este extenso manual, a continuación les dejo con una serie de capturas de la distribución Linux personalizada tdb-openSUSE-Leap-15.2, espero que las disfruten:












 






Nota final del autor:

Para finalizar quisiera mencionar que me ha llevado mucho tiempo y esfuerzo estructurar la idea y redactar este manual. Ciertamente por la red no se encuentra mucha información respecto al tema, y por ello para mi ha sido casi una obligación redactarlo, como si fuese un hito para mi carrera, o todo lo contrario, como si fuese una barrera que no me dejara avanzar a nuevos proyectos hasta tanto la haya superado.

Y es que al final sentía que todo ese conocimiento que había adquirido con mucho sacrificio para crear un sistema operativo personalizado no debía quedar en el olvido, en especial para mí que muy ocupado y olvidadizo me encuentro durante estos en estos tiempo.


Referentes y enlaces de utilidad:

Comentarios

  1. Material de altisimo valor ... Excelente! Gracias!

    ResponderEliminar
    Respuestas
    1. Gracias Jose por tu comentario, me intriga saber el motivo por el cual llegaste hasta este artículo! Saludos.

      Eliminar

Publicar un comentario