<?xml version="1.0" encoding="utf-8"?> 
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <generator uri="https://gohugo.io/" version="0.140.2">Hugo</generator><title type="html"><![CDATA[Donde Viven Desvaríos y CachaRreo]]></title>
    
    
    
            <link href="https://dvdcr.gitlab.io/" rel="alternate" type="text/html" title="html" />
            <link href="https://dvdcr.gitlab.io/feed.xml" rel="self" type="application/atom+xml" title="atom" />
    <updated>2026-08-14T22:06:43+00:00</updated>
    
    <id>https://dvdcr.gitlab.io/</id>
        
        <entry>
            <title type="html"><![CDATA[Sustituir almacenamiento interno de Steam Deck]]></title>
            <link href="https://dvdcr.gitlab.io/posts/2023/steam-deck-sustituir-almacenamiento-interno/?utm_source=atom_feed" rel="alternate" type="text/html"  hreflang="en" />
            <id>https://dvdcr.gitlab.io/posts/2023/steam-deck-sustituir-almacenamiento-interno/</id>
            
            <published>2023-09-03T00:00:00+01:00</published>
            <updated>2023-09-03T00:00:00+01:00</updated>
            
            
            <content type="html"><![CDATA[<p><img src="/img/steam/steam-deck-logo.svg#center" alt=""  /></p>
<p>Uno de los puntos fuertes de la <a class="gblog-markdown__link" href="https://www.steamdeck.com">Steam Deck</a> es la reparabilidad y posibilidad de sustituir algunas de sus piezas sujetas a desgaste.
El almacenamiento interno es una de ellas y en mi caso lo he sustituido por uno de mayor tamaño. Incluyo aquí enlaces a los tutoriales de iFixit donde se detalla el proceso y alguna de las tareas posteriores que he realizado.</p>
<p> </p>
<p><strong>Contenido</strong></p>



  <div class="gblog-toc gblog-toc__level--6"><nav id="TableOfContents">
  <ul>
    <li>
      <ul>
        <li><a href="#extracción-del-dispositivo-de-almacenamiento-incluido-de-serie">Extracción del dispositivo de almacenamiento incluido de serie</a></li>
        <li><a href="#copia-de-datos-al-nuevo-dispositivo-de-almacenamiento">Copia de datos al nuevo dispositivo de almacenamiento</a></li>
        <li><a href="#instalación-del-nuevo-dispositivo-de-almacenamiento">Instalación del nuevo dispositivo de almacenamiento</a></li>
        <li><a href="#verificación-de-archivos-de-los-juegos-instalados">Verificación de archivos de los juegos instalados</a></li>
        <li><a href="#conclusión">Conclusión</a></li>
        <li><a href="#referencias">Referencias</a></li>
      </ul>
    </li>
  </ul>
</nav><hr></div>


<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="extracción-del-dispositivo-de-almacenamiento-incluido-de-serie">
        Extracción del dispositivo de almacenamiento incluido de serie
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2023/steam-deck-sustituir-almacenamiento-interno/#extracción-del-dispositivo-de-almacenamiento-incluido-de-serie" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Extracción del dispositivo de almacenamiento incluido de serie" href="#extracci%c3%b3n-del-dispositivo-de-almacenamiento-incluido-de-serie">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>La <a class="gblog-markdown__link" href="https://www.steamdeck.com">Steam Deck</a> puede venir con un almacenamiento interno eMMC de 64 GB, un disco M.2 2230 de 256 Gb o un disco M.2 2230 de 512 GB. En mi caso tengo la versión de 256 Gb, pero alguno de los juegos que utilizo con relativa frecuencia, consume ya casi 150 Gb y otro por encima de los 100, lo que me obligaba a veces a desinstalar e instalar periódicamente.</p>
<p>La solución más directa y recomendada por Valve es incorporar una tarjeta microSD, pero quería comprobar si realmente era sencillo sustituir el almacenamiento interno por uno de mayor tamaño. Además, ¿desde cuándo he buscado la solución sencilla pudiendo complicarlo, aprender por el camino y romper algo?</p>
<p>En <a class="gblog-markdown__link" href="https://www.youtube.com/watch?v=Dxnr2FAADAs">este video</a> Valve no recomienda abrir la consola si no es necesario y que en caso de ser necesario lo haga un profesional. Además indica los posibles problemas que puede plantear utilizar otro dispositivo de almacenamiento diferente del incluido de serie:</p>
<ul>
<li>Posibles interferencias con la comunicación inalámbrica (WiFi y Bluetooth) ya que los dispositivos de comunicación están cerca de la unidad de almacenamiento.</li>
<li>Interferencia mecánica con otros componentes de la placa por vibraciones del dispositivo o por presión debido a un diferente tamaño físico que el original.</li>
<li>Mayor consumo de energía del dispositivo adicional y por lo tanto reducción de la vida de la batería.</li>
</ul>
<p>A pesar de ello en ese video indica cómo hacer el proceso de desmontaje de un joystick y del almacenamiento interno, si bien de forma muy breve.</p>
<p>En mi caso compré un <a class="gblog-markdown__link" href="https://www.corsair.com/es/es/p/data-storage/cssd-f1000gbmp600mn/mp600-mini-1tb-gen4-pcie-x4-nvme-m-2-2230-ssd-cssd-f1000gbmp600mn">Corsair MP600 Mini de 1 Tb</a> directamente en la tienda de Corsair.</p>
<p>No voy a detallar los pasos de la sustitución propiamente dichos. En iFixit están descritos paso por paso tanto en video como en tutorial web con fotos y descripción de los pasos en español (formato que me encanta y que está en vías de extinción en pro del vídeo).</p>
<p>El primer paso es <a class="gblog-markdown__link" href="https://es.ifixit.com/Gu%C3%ADa/C%C3%B3mo&#43;habilitar&#43;el&#43;modo&#43;de&#43;almacenamiento&#43;de&#43;bater%C3%ADa/149962">habilitar el modo de almacenamiento de la batería</a> para evitar que una pulsación accidental del botón de encendido pueda activar la consola mientras la tenemos abierta y pueda producirse un cortocircuito.</p>
<p>El siguiente paso es el <a class="gblog-markdown__link" href="https://es.ifixit.com/Gu%C3%ADa/Reemplazo&#43;del&#43;SSD&#43;del&#43;Steam&#43;Deck/148989">desmontaje propiamente dicho de la máquina y la extracción del almacenamiento M.2</a>.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="copia-de-datos-al-nuevo-dispositivo-de-almacenamiento">
        Copia de datos al nuevo dispositivo de almacenamiento
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2023/steam-deck-sustituir-almacenamiento-interno/#copia-de-datos-al-nuevo-dispositivo-de-almacenamiento" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Copia de datos al nuevo dispositivo de almacenamiento" href="#copia-de-datos-al-nuevo-dispositivo-de-almacenamiento">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>El método más sencillo para configurar el nuevo almacenamiento es <a class="gblog-markdown__link" href="https://help.steampowered.com/es/faqs/view/1B71-EDF2-EB6D-2BB3">descargar la imagen de recuparación e instalar el sistema desde cero</a>.
En mi caso quería conservar si era posible toda la información que tenía y por ello quería hacer una copia exacta de todo el contenido del almacenamiento actual en el nuevo.
Opté por utilizar carcasa externa USB para almacenamiento M2/NVMe que ya tenía y en la que inserté el almacenamiento de 256 Gb (Kingston).</p>
<p>La conecté a mi ordenador y verifiqué con <code>lsblk</code> qué dispositivo se le había asignado (<code>/dev/sdb</code> en mi caso).</p>
<p>Visto esto, hice un volcado completo del disco a un archivo con:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo dd <span class="k">if</span><span class="o">=</span>/dev/sdb <span class="nv">of</span><span class="o">=</span><span class="nv">$HOME</span>/iso/steamdeck.iso <span class="nv">oflag</span><span class="o">=</span>sync <span class="nv">bs</span><span class="o">=</span>128M <span class="nv">status</span><span class="o">=</span>progress
</span></span></code></pre></div><p>Otra opción es hacer este mismo procedimiento desde el modo escritorio de la Steam Deck antes de extraer el almacenamiento incluido de serie, concectando un pendrive o disco duro externo a través de un hub USB o un dock.</p>
<p>Una vez terminado, desconecté la carcasa externa, saqué el disco de de 256 Gb de la carcasa y en su lugar inserté el disco de 1 Tb.</p>
<p>Tras conectar la carcasa al ordenador verifiqué el nombre de dispositivo asignado al almacenamiento USB con el comando <code>lsblk</code>, que fue <code>/dev/sdb</code>.</p>
<p>A continuación restauramos el archivo de imagen en el nuevo disco con:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo dd <span class="nv">of</span><span class="o">=</span>/dev/sdb <span class="k">if</span><span class="o">=</span><span class="nv">$HOME</span>/iso/steamdeck.iso <span class="nv">oflag</span><span class="o">=</span>sync <span class="nv">bs</span><span class="o">=</span>128M <span class="nv">status</span><span class="o">=</span>progress
</span></span></code></pre></div><p>Una vez terminado el proceso, tendremos el disco de 1 Tb con la misma información que el de 256 Gb, pero con el mismo tamaño de particiones que el antiguo, por lo que no estaremos aprovechando el espacio extra de la unidad..</p>
<p>En mi caso recurrí a <a class="gblog-markdown__link" href="https://gparted.org">gparted</a> para expandir sin pérdida de datos la partición <code>/home</code> hasta el máximo de tamaño ofrecido por el almacenamiento. Con ésto, el disco queda preparado para instalarlo en la Steam Deck.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="instalación-del-nuevo-dispositivo-de-almacenamiento">
        Instalación del nuevo dispositivo de almacenamiento
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2023/steam-deck-sustituir-almacenamiento-interno/#instalación-del-nuevo-dispositivo-de-almacenamiento" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Instalación del nuevo dispositivo de almacenamiento" href="#instalaci%c3%b3n-del-nuevo-dispositivo-de-almacenamiento">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>La instalación se hace siguiendo los mismos pasos del tutorial de <a class="gblog-markdown__link" href="https://es.ifixit.com/Gu%C3%ADa/Reemplazo&#43;del&#43;SSD&#43;del&#43;Steam&#43;Deck/148989">desmontaje  y extracción del almacenamiento M.2</a>.
Si el desmontaje se hizo con cuidado, se puede aprovechar la envoltura que aisla de interferencias para la nueva unidad.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="verificación-de-archivos-de-los-juegos-instalados">
        Verificación de archivos de los juegos instalados
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2023/steam-deck-sustituir-almacenamiento-interno/#verificación-de-archivos-de-los-juegos-instalados" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Verificación de archivos de los juegos instalados" href="#verificaci%c3%b3n-de-archivos-de-los-juegos-instalados">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Una vez montado, para arrancar la Steam Deck y salir del modo de almacenamiento de la batería, hay que conectarla a un cargador y pulsar el botón de arranque.</p>
<p>En mi caso la Steam Deck arrancó perfectamente y reconoció la cantidad de almacenamiento disponible, pero al intentar arrancar los juegos, no llegaban a funcionar y volvían a la pantalla de inicio del juego, sin mostrar ningún error aparente.</p>
<p>Aunque no he llegado a consultar los logs para confirmar cual era el problema, sospeché del sistema de verificación que tiene Steam, por lo que el primer paso fue verificar los archivos instalados de un juego de pequeño tamaño.</p>
<p>Para ello, pulsamos en el icono de configuración (rueda dentada):</p>
<p><img src="/img/steam/steamdeck-bitblaster-01-config.jpg#center" alt=""  /></p>
<p>En el menú que se despliega elegimos <em>Propiedades</em>.</p>
<p><img src="/img/steam/steamdeck-bitblaster-02-config-propiedades.jpg#center" alt=""  /></p>
<p>Seleccionamos en el menú de la izquierda la opción <em>Archivos Instalados</em> y en la sección <em>Comprobar si los archivos de este juego están instalados correctamente</em> pulsamos el botón <em>Verificar integridad de los archivos del juego</em>:</p>
<p><img src="/img/steam/steamdeck-bitblaster-03-boton-verificar-integridad.jpg#center" alt=""  /></p>
<p>Se iniciará el procedimiento de verificación. El motivo de elegir un juego de pequeño tamaño es verificar si éste era el problema en el menor tiempo posible.</p>
<p><img src="/img/steam/steamdeck-bitblaster-04-verificando-integridad.jpg#center" alt=""  /></p>
<p>Se informa de que no se han podido validar una serie de archivos y que por lo tanto se volverán a descargar</p>
<p><img src="/img/steam/steamdeck-bitblaster-05-descargar-archivos.jpg#center" alt=""  /></p>
<p>Una vez finalizada la descarga, el juego funciona correctamente.</p>
<p>Esto mismo lo aplico a otros juegos y en todos los casos acaban funcionando correctamente tras este proceso.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="conclusión">
        Conclusión
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2023/steam-deck-sustituir-almacenamiento-interno/#conclusión" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Conclusión" href="#conclusi%c3%b3n">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>La sustitución del almacenamiento interno de la Steam Deck es un proceso sencillo, aunque no exento de riesgos y que puede traer efectos negativos como la reducción de la duración de la batería.</p>
<p>Cualquier daño realizado durante la sustitución puede implicar la pérdida de la garantía, por lo que lo más sencillo es incluir una tarjeta microSD de gran tamaño.</p>
<p>No he hecho pruebas serias, pero en experiencia de uso no he notado de momento pérdida de autonomía ni ningún otro tipo de problema.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="referencias">
        Referencias
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2023/steam-deck-sustituir-almacenamiento-interno/#referencias" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Referencias" href="#referencias">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><ul>
<li>Web oficial de Steam Deck <a class="gblog-markdown__link" href="https://www.steamdeck.com">https://www.steamdeck.com</a></li>
<li>Vídeo de Steam sobre desmontaje de Steam Deck <a class="gblog-markdown__link" href="https://www.youtube.com/watch?v=Dxnr2FAADAs">https://www.youtube.com/watch?v=Dxnr2FAADAs</a></li>
<li>Tutorial de iFixit sobre habilitar modo de almacenamiento de batería <a class="gblog-markdown__link" href="https://es.ifixit.com/Gu%C3%ADa/C%C3%B3mo&#43;habilitar&#43;el&#43;modo&#43;de&#43;almacenamiento&#43;de&#43;bater%C3%ADa/149962">https://es.ifixit.com/Gu%C3%ADa/C%C3%B3mo+habilitar+el+modo+de+almacenamiento+de+bater%C3%ADa/149962</a></li>
<li>Tutorial de iFixit sobre reemplazo del almacenamiento interno de Steam Deck <a class="gblog-markdown__link" href="https://es.ifixit.com/Gu%C3%ADa/Reemplazo&#43;del&#43;SSD&#43;del&#43;Steam&#43;Deck/148989">https://es.ifixit.com/Gu%C3%ADa/Reemplazo+del+SSD+del+Steam+Deck/148989</a></li>
<li>Instalación desde cero del sistema de Steam Deck <a class="gblog-markdown__link" href="https://help.steampowered.com/es/faqs/view/1B71-EDF2-EB6D-2BB3">https://help.steampowered.com/es/faqs/view/1B71-EDF2-EB6D-2BB3</a></li>
</ul>]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://dvdcr.gitlab.io/tags/principal" term="principal" label="principal" />
                            
                        
                    
                
            
        </entry>
    
        
        <entry>
            <title type="html"><![CDATA[Contraseña de API obsoleta en Home Assistant 2023.02]]></title>
            <link href="https://dvdcr.gitlab.io/posts/2023/iot-hass-2023-02-api-password-deprecated/?utm_source=atom_feed" rel="alternate" type="text/html"  hreflang="en" />
            <id>https://dvdcr.gitlab.io/posts/2023/iot-hass-2023-02-api-password-deprecated/</id>
            
            <published>2023-02-17T00:00:00+01:00</published>
            <updated>2023-02-17T00:00:00+01:00</updated>
            
            
            <content type="html"><![CDATA[<p><img src="/img/hass_logo.svg#center" alt=""  /></p>
<p>Al acceder a nuestro <em>Home Assistant</em> tras actualizar a la versión <em>2023.2</em>, si utilizamos dispositivos <em>ESPHome</em> con contraseña de API, nos podemos encontrar con una alerta en la sección de <em>Ajustes</em> con el mensaje:</p>
<pre tabindex="0"><code>API Password deprecated on &lt;nombre dispositivo&gt;
</code></pre><p>En este artículo vemos cómo resolverlo.</p>
<p>En el menú de <em>Ajustes</em> nos puede aparecer en la parte superior un elemento como el siguiente:</p>
<p><img src="/img/iot-hass-2023-02-api-password-deprecation-lista-reparaciones.png#center" alt=""  /></p>
<p>Si pulsamos en el mensaje, nos mostrará el siguiente diálogo:</p>
<p><img src="/img/iot-hass-2023-02-api-password-deprecation-alert.png#center" alt=""  /></p>
<p>y en el enlace <code>SABER MÁS</code> nos enviará a <a class="gblog-markdown__link" href="https://esphome.io/components/api.html">https://esphome.io/components/api.html</a>, la documentación de la API.
En el apartado <em>Configuration variables</em> nos informará de la variable <code>encryption</code>, que contiene a su vez el parámetro <code>key</code>, una cadena de 32 bytes codificada en <em>base64</em> como ésta: <code>VEY3yb8yK1x+s03pORI3IBCrGaCHIfBnjaijQaedTRI=</code>. Este valor se genera aleatoriamente cada vez que visitamos la página, por lo que se podría utilizar directamente, pero lo habitual será generar o escribir una nosotros mismos con la misma longitud.</p>
<p>La solución pasa por reemplazar en el archivo <code>YAML</code> de configuración del dispositivo <em>ESPHome</em>, el apartado:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># Enable Home Assistant API</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">api</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;ABCDEfghi1234&#34;</span><span class="w">
</span></span></span></code></pre></div><p>por</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># Enable Home Assistant API</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">api</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">encryption</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;VEY3yb8yK1x+s03pORI3IBCrGaCHIfBnjaijQaedTRI=&#34;</span><span class="w">
</span></span></span></code></pre></div><p>y cargar el nuevo código en nuestro dispositivo ejecutando:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">esphome run archivo.yaml
</span></span></code></pre></div><p>Una vez actualizado el firmware en nuestro dispositivo, aparecerá una notificación en <em>Home Assistant</em> indicando que una integración requiere reconfiguración:</p>
<p><img src="/img/iot-hass-2023-02-notification-integration-requires-reconfiguration.png#center" alt=""  /></p>
<p>Si pulsamos en el enlace <em>Check it out</em> nos enviará directamente a al apartado <code>Ajustes</code> -&gt; <code>Integraciones</code>, donde aparecerá la integración que requiere aplicar una reconfiguración:</p>
<p><img src="/img/iot-hass-2023-02-integraciones-atencion-requerida.png#center" alt=""  /></p>
<p>pulsamos en el botón <code>RECONFIGURAR</code> y nos mostrará un diálogo que pedirá la clave de cifrado que hayamos especificado:</p>
<p><img src="/img/iot-hass-2023-02-clave-cifrado.png#center" alt=""  /></p>
<p>Introducimos la clave y pulsamos el enlace <code>ENVIAR</code>. Si la clave es correcta, nos mostrará un diálogo de confirmación:</p>
<p><img src="/img/iot-hass-2023-02-autenticacion-correcta.png#center" alt=""  /></p>
<p>Sólo queda pulsar en <code>CERRAR</code>. Nuestro dispositivo estará operativo de nuevo y la alerta desaparece.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="referencias">
        Referencias
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2023/iot-hass-2023-02-api-password-deprecated/#referencias" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Referencias" href="#referencias">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><ul>
<li><a class="gblog-markdown__link" href="https://esphome.io/components/api.html">https://esphome.io/components/api.html</a></li>
</ul>]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://dvdcr.gitlab.io/tags/IoT" term="IoT" label="IoT" />
                            
                        
                    
                
            
        </entry>
    
        
        <entry>
            <title type="html"><![CDATA[Actualizar versión de Fedora]]></title>
            <link href="https://dvdcr.gitlab.io/posts/2022/12/linux-fedora-upgrade/?utm_source=atom_feed" rel="alternate" type="text/html"  hreflang="en" />
            <id>https://dvdcr.gitlab.io/posts/2022/12/linux-fedora-upgrade/</id>
            
            <published>2022-12-11T00:00:00+01:00</published>
            <updated>2025-11-16T00:00:00+01:00</updated>
            
            
            <content type="html"><![CDATA[<p><img src="/img/fedora-logo-wordmark.svg#center" alt=""  /></p>
<p>Fedora libera una nueva versión aproximadamente cada 6 meses y proporciona actualizaciones de paquetes para las dos últimas versiones estables disponibles, dando un mes adicional para la versión que queda fuera de soporte. Esto hace que si queremos el software al día, debemos subir de versión como máximo cada 13 meses.</p>
<p>En este artículo se describen algunos detalles básicos del ciclo de vida de versiones en Fedora y cómo realizar la actualización.</p>
<p> </p>
<p><strong>Contenido</strong></p>



  <div class="gblog-toc gblog-toc__level--6"><nav id="TableOfContents">
  <ul>
    <li>
      <ul>
        <li><a href="#ciclo-de-vida-de-las-versiones-de-fedora">Ciclo de vida de las versiones de Fedora</a></li>
        <li><a href="#recomendaciones-previas">Recomendaciones previas</a></li>
        <li><a href="#restricciones-de-las-actualizaciones">Restricciones de las actualizaciones</a></li>
        <li><a href="#proceso-de-actualización">Proceso de actualización</a></li>
        <li><a href="#tareas-post-actualización-opcionales">Tareas post-actualización opcionales</a>
          <ul>
            <li><a href="#actualizar-los-archivos-de-configuración-de-sistema">Actualizar los archivos de configuración de sistema.</a></li>
            <li><a href="#actualizar-el-gestor-de-arranque-grub-en-sistemas-bios">Actualizar el gestor de arranque GRUB en sistemas BIOS</a></li>
            <li><a href="#limpiar-paquetes-retirados">Limpiar paquetes retirados</a></li>
            <li><a href="#limpiar-paquetes-anticuados">Limpiar paquetes anticuados</a></li>
            <li><a href="#limpiar-kernels-antiguos">Limpiar kernels antiguos</a></li>
            <li><a href="#limpiar-enlaces-simbólicos-antiguos">Limpiar enlaces simbólicos antiguos</a></li>
          </ul>
        </li>
        <li><a href="#resolver-problemas-posteriores-a-la-actualización">Resolver problemas posteriores a la actualización</a>
          <ul>
            <li><a href="#rebuilding-the-rpm-database">Rebuilding the RPM database</a></li>
            <li><a href="#utilizar-distro-sync-para-resolver-problemas-de-dependencias">Utilizar distro-sync para resolver problemas de dependencias</a></li>
            <li><a href="#reetiquetar-archivos-con-la-última-política-selinux">Reetiquetar archivos con la última política SELinux</a></li>
          </ul>
        </li>
        <li><a href="#referencias">Referencias</a></li>
      </ul>
    </li>
  </ul>
</nav><hr></div>


<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="ciclo-de-vida-de-las-versiones-de-fedora">
        Ciclo de vida de las versiones de Fedora
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/12/linux-fedora-upgrade/#ciclo-de-vida-de-las-versiones-de-fedora" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Ciclo de vida de las versiones de Fedora" href="#ciclo-de-vida-de-las-versiones-de-fedora">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Como se ha indicado anteriormente, Fedora libera una nueva versión estable cada 6 meses aproximadamente.
Mantiene actualizaciones de paquetes en las dos últimas versiones disponibles. Una vez publicada una nueva versión N, la versión N - 2 mantiene actualizaciones durante un mes más para dar tiempo a actualizarse, pero a partir de ahí deja de recibirlas.</p>
<p>Podemos consultar las fechas de fin de soporte de las diferentes versiones en <a class="gblog-markdown__link" href="https://docs.fedoraproject.org/en-US/releases/eol/">https://docs.fedoraproject.org/en-US/releases/eol/</a>.</p>
<p>Estos detalles hacen que una versión de Fedora deje de recibir actualizaciones aproximadamente cada 13 meses.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="recomendaciones-previas">
        Recomendaciones previas
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/12/linux-fedora-upgrade/#recomendaciones-previas" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Recomendaciones previas" href="#recomendaciones-previas">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Antes de hace una actualización es recomendable hacer una copia de seguridad de los archivos importantes de datos y configuración, porque siempre existe el riesgo de problemas durante la actualización.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="restricciones-de-las-actualizaciones">
        Restricciones de las actualizaciones
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/12/linux-fedora-upgrade/#restricciones-de-las-actualizaciones" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Restricciones de las actualizaciones" href="#restricciones-de-las-actualizaciones">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Las actualizaciones a una versión consecutiva superior (por ejemplo de la 42 a la 43) o saltando una versión (de 41 a 43) están soportadas, pero es recomendable hacer la actualización antes de que lleguen a su fin de fida (EOL). Esto ocurre un mes después de que se liberen dos versiones posteriores a la actual, como se ha mencionado anteriormente.
Las actualizaciones a más de dos versiones superiores a la vez no están soportadas. De hecho, para la actualización a dos versiones superiores una vez finalizado el mes de margen puede ser necesario importar la clave GPG para la versión a la que se quiere actualizar. Esto se puede hacer con:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-N-primary
</span></span></code></pre></div><p>Para Fedora 43 utilizaríamos:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-43-primary
</span></span></code></pre></div><p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="proceso-de-actualización">
        Proceso de actualización
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/12/linux-fedora-upgrade/#proceso-de-actualización" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Proceso de actualización" href="#proceso-de-actualizaci%c3%b3n">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>En primer lugar debemos actualizar los paquetes:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo dnf upgrade --refresh
</span></span></code></pre></div><p>y reiniciar el equipo. Esto es importante, ya que realiza la actualización de algunos paquetes y claves a la última versión disponibles, que pueden ser necesarias durante la instalación.</p>
<p>Una vez reiniciado el equipo instalamos el paquete <code>dnf-plugin-system-upgrade package</code> si no se encuentra ya instalado:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo dnf install dnf-plugin-system-upgrade
</span></span></code></pre></div><p>Descargamos los paquetes actualizados para la nueva versión a la que queremos actualizar, en este ejemplo, la versión 43:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo dnf system-upgrade download --releasever<span class="o">=</span><span class="m">43</span>
</span></span></code></pre></div><p>Si alguno de los paquetes tienen dependencias que no se hayan podido conseguir, la actualización rechazará continuar a menos que se ejecute de nuevo con el parámetro <code>--allowerasing</code>.
Esto ocurre con paquetes instalados de repositorios de terceros para los que no se han publicado aún repositorios actualizados a la nueva versión.
Esta opción lo que haría es eliminar los paquetes que no se pueden actualizar, por lo que hay que estudiar la salida con cuidado y tener presente qué paquetes se eliminarían.</p>
<p>En caso de dependencias no satisfechas, se puede a veces ver más detalles si se añade la opción <code>--best</code>.</p>
<p>Si es necesario añadir o eliminar paquetes antes de ejecutar de nuevo <code>dnf system-upgrade download</code>, es recomendable hacer esas operaciones añadiendo a dnf la opción <code>--setopt=keepcache=1</code>. Si no se utiliza, se eliminará la caché de paquetes completa después de la operación y se tendrá que descargar todos los paquetes de nuevo.</p>
<p>Para lanzar el procedimiento de actualización, habrá que cerrar todos los programas y lanzar el proceso con la instrucción:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo dnf5 offline reboot
</span></span></code></pre></div><p>Esto realizará la actulización durante el siguiente arranque del sistema. Cuando termine la actualización, realizará un nuevo reinicio en la nueva versión.</p>
<div class="gblog-post__anchorwrap">
    <h2 id="tareas-post-actualización-opcionales">
        Tareas post-actualización opcionales
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/12/linux-fedora-upgrade/#tareas-post-actualización-opcionales" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Tareas post-actualización opcionales" href="#tareas-post-actualizaci%c3%b3n-opcionales">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Fedora en su documentación enumera una serie de tareas que podemos realizar una vez finalizada la actualización para optimizar el sistema y limpiar archivos obsoletos. Podemos consultarlos en <a class="gblog-markdown__link" href="https://docs.fedoraproject.org/en-US/quick-docs/dnf-system-upgrade/#sect-optional-post-upgrade-tasks">https://docs.fedoraproject.org/en-US/quick-docs/dnf-system-upgrade/#sect-optional-post-upgrade-tasks</a>.</p>
<div class="gblog-post__anchorwrap">
    <h3 id="actualizar-los-archivos-de-configuración-de-sistema">
        Actualizar los archivos de configuración de sistema.
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/12/linux-fedora-upgrade/#actualizar-los-archivos-de-configuración-de-sistema" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Actualizar los archivos de configuración de sistema." href="#actualizar-los-archivos-de-configuraci%c3%b3n-de-sistema">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h3>
</div><p>En caso de haber cambiado manualmente archivos de configuración situados en <code>/etc</code>, RPM crea archivos con la extensión <code>.rpmnew</code> (para la nueva versión del archivo) y <code>.rpmsave</code> (para la versión previa que hubiéramos modificado). Buscando esos archivos podemos examinarlos manualmente y resolver los conflictos, si los hay.
Otra opción es utilizar la herramienta <code>rpmconf</code> diseñada para simplificar este proceso. Se instala con:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo dnf install rpmconf
</span></span></code></pre></div><p>Y se utiliza con:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo rpmconf -a
</span></span></code></pre></div><p>Al utilizar <code>rpmconf</code> para actualizar la configuración del sistema, algunos archivos de configuración pueden cambiar. Después de actualizar hay que verificar especialmente los archivos <code>/etc/ssh/sshd_config</code>, <code>/etc/nsswitch.conf</code> y <code>/etc/ntp.conf</code>. En el caso de OpenSSH, tras la actualización el archivo <code>sshd_config</code>que da con su configuración por defecto en la que por ejemplo no está habilitada la autenticación por clave y permite la autenticación por contraseña.</p>
<div class="gblog-post__anchorwrap">
    <h3 id="actualizar-el-gestor-de-arranque-grub-en-sistemas-bios">
        Actualizar el gestor de arranque GRUB en sistemas BIOS
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/12/linux-fedora-upgrade/#actualizar-el-gestor-de-arranque-grub-en-sistemas-bios" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Actualizar el gestor de arranque GRUB en sistemas BIOS" href="#actualizar-el-gestor-de-arranque-grub-en-sistemas-bios">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h3>
</div><p>En sistemas con firmware BIOS, el cargador de arranque no se actualiza de manera automática, por lo que es aconsejable actualizarlo entre versiones de Fedora.</p>
<p>Si no estamos seguros de si nuestro sistema es BIOS o UEFI, un modo de verificarlo es ejecutar:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ sudo efibootmgr
</span></span><span class="line"><span class="cl">BootCurrent: <span class="m">0000</span>
</span></span><span class="line"><span class="cl">Timeout: <span class="m">0</span> seconds
</span></span><span class="line"><span class="cl">BootOrder: 0000,0017,0018,0019,001A,001B,001D,001E,001F,0001,001C
</span></span><span class="line"><span class="cl">Boot0000* Fedora	HD<span class="o">(</span>1,GPT,76400879-6149-42fe-8307-1e0aa5ef1ed8,0x800,0x320b8<span class="o">)</span>/File<span class="o">(</span><span class="se">\E</span>FI<span class="se">\f</span>edora<span class="se">\s</span>himx64.efi<span class="o">)</span>
</span></span><span class="line"><span class="cl">Boot0001* Linux-Firmware-Updater	HD<span class="o">(</span>1,GPT,76400898-6149-42fe-8307-1e0aa5ef1ed8,0x800,0x320b8<span class="o">)</span>/File<span class="o">(</span><span class="se">\E</span>FI<span class="se">\f</span>edora<span class="se">\f</span>wupdx64.efi<span class="o">)</span>
</span></span><span class="line"><span class="cl">...
</span></span></code></pre></div><p>La respuesta anterior es para un sistema con UEFI.
En caso de ser BIOS, la respuesta será:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">EFI variables are not supported on this system
</span></span></code></pre></div><p>Para actualizar el cargador de arranque, el primer paso es localizar el archivo de dispositivo en el que está ubicado el directorio /boot/:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ sudo mount <span class="p">|</span> grep <span class="s2">&#34;/boot &#34;</span>
</span></span><span class="line"><span class="cl">/dev/sda4 on /boot <span class="nb">type</span> ext4 <span class="o">(</span>rw,relatime,seclabel<span class="o">)</span>
</span></span></code></pre></div><p>En este caso es <code>/dev/sda4</code>. Hay que reinstalar el cargador de arranque especificando el archivo de dispositivo sin el número:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ sudo grub2-install /dev/sda
</span></span><span class="line"><span class="cl">Installing <span class="k">for</span> i386-pc platform.
</span></span><span class="line"><span class="cl">Installation finished. No error reported.
</span></span></code></pre></div><div class="gblog-post__anchorwrap">
    <h3 id="limpiar-paquetes-retirados">
        Limpiar paquetes retirados
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/12/linux-fedora-upgrade/#limpiar-paquetes-retirados" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Limpiar paquetes retirados" href="#limpiar-paquetes-retirados">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h3>
</div><p>Cada versión, Fedora retira algunos paquetes. Puede que el paquete pase a estar obsoleto, que el proyecto original desaparezca o el mantenedor baje la calidad. En esos casos Fedora deja de distribuir esos paquetes, pero continúan en el sistema. Nunca se recibirán actualizaciones, ni siquiera de seguridad, por lo que es recomendable eliminar esos paquetes. Para facilitar esta operación podemos instalar el paquete <code>remove-retired-packages</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo dnf install remove-retired-packages
</span></span></code></pre></div><p>y realizar una limpieza con esta herramienta:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">remove-retired-packages
</span></span></code></pre></div><p>Buscará paquetes retirados y si los encuentra preguntará interactivamente si se desean conservar o eliminar.</p>
<div class="gblog-post__anchorwrap">
    <h3 id="limpiar-paquetes-anticuados">
        Limpiar paquetes anticuados
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/12/linux-fedora-upgrade/#limpiar-paquetes-anticuados" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Limpiar paquetes anticuados" href="#limpiar-paquetes-anticuados">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h3>
</div><p>Pueden quedar paquetes procedentes de la versión o versiones anteriores. Antes de buscar paquetes anticuados es recomendable ejecutar:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo dnf upgrade
</span></span></code></pre></div><p>ya que la lista que obtengamos sólo es válida si el sistema está totalmente actualizado.</p>
<p>Podemos consultar paquetes duplicados (paquetes que tienen múltiples versiones instaladas) con:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo dnf repoquery --duplicates
</span></span></code></pre></div><p>En caso de que aparezca alguno, lo podemos eliminar.</p>
<p>Puede que haya paquetes que todavía estén en el sistema pero que no aparezcan nunca más en los repositorios. Podemos verlos con:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo dnf list extras
</span></span></code></pre></div><p>Si aparece algún paquete no necesario, se puede eliminar con:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo dnf remove <span class="k">$(</span>sudo dnf repoquery --extras --exclude<span class="o">=</span>kernel,kernel-<span class="se">\*</span><span class="k">)</span>
</span></span></code></pre></div><p>Se pueden eliminar de forma segura todos los paquetes con:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo dnf autoremove
</span></span></code></pre></div><div class="gblog-post__anchorwrap">
    <h3 id="limpiar-kernels-antiguos">
        Limpiar kernels antiguos
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/12/linux-fedora-upgrade/#limpiar-kernels-antiguos" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Limpiar kernels antiguos" href="#limpiar-kernels-antiguos">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h3>
</div><p>Después de arrancar con el último kernel y comprobar el sistema, se pueden eliminar los kernels previos. Los kernels antiguos no se eliminan con <code>dnf autoremove</code> para evitar eliminaciones accidentales.</p>
<p>Una de las formas más sencillas de eliminar los kernels antiguos es con un script que mantenga el último kernel.
El script siguiente es un ejemplo de ello:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="nv">old_kernels</span><span class="o">=(</span><span class="k">$(</span>dnf repoquery --installonly --latest-limit<span class="o">=</span>-1 -q<span class="k">)</span><span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[</span> <span class="s2">&#34;</span><span class="si">${#</span><span class="nv">old_kernels</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span> -eq <span class="m">0</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;No old kernels found&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">exit</span> <span class="m">0</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> ! dnf remove <span class="s2">&#34;</span><span class="si">${</span><span class="nv">old_kernels</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;Failed to remove old kernels&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">exit</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Removed old kernels&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">exit</span> <span class="m">0</span>
</span></span></code></pre></div><div class="gblog-post__anchorwrap">
    <h3 id="limpiar-enlaces-simbólicos-antiguos">
        Limpiar enlaces simbólicos antiguos
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/12/linux-fedora-upgrade/#limpiar-enlaces-simbólicos-antiguos" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Limpiar enlaces simbólicos antiguos" href="#limpiar-enlaces-simb%c3%b3licos-antiguos">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h3>
</div><p>Puede haber algunos enlaces simbólicos en el sistema de ficheros que queden colgados tras una actualización.</p>
<p>Para localizarlos, podemos instalar la herramienta <code>symlinks</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo dnf install symlinks
</span></span></code></pre></div><p>Una vez instalada se pueden auditar con un comando como el siguiente (el parámetro <code>-r</code> significa recursivo):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo symlinks -r /usr <span class="p">|</span> grep dangling
</span></span></code></pre></div><p>Después de verificar la lista, se pueden eliminar con el parámetro <code>-d</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo symlinks -r -d /usr
</span></span></code></pre></div><p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="resolver-problemas-posteriores-a-la-actualización">
        Resolver problemas posteriores a la actualización
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/12/linux-fedora-upgrade/#resolver-problemas-posteriores-a-la-actualización" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Resolver problemas posteriores a la actualización" href="#resolver-problemas-posteriores-a-la-actualizaci%c3%b3n">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>A continuación se describen algunas tareas que SÓLO deben ejecutarse si se producen problemas en el sistema actualizado.</p>
<div class="gblog-post__anchorwrap">
    <h3 id="rebuilding-the-rpm-database">
        Rebuilding the RPM database
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/12/linux-fedora-upgrade/#rebuilding-the-rpm-database" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Rebuilding the RPM database" href="#rebuilding-the-rpm-database">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h3>
</div><p>Si aparecen advertencias cuando se trabaja con las herramientas rpm o dnf, puede que la base de datos esté corrupta. Es posible reconstruirla y ver si esto resuelve los problemas. Siempre hay que hacer primero un backup del directorio <code>/var/lib/rpm/</code>.
Se puede reconstruir con:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo rpm --rebuilddb
</span></span></code></pre></div><div class="gblog-post__anchorwrap">
    <h3 id="utilizar-distro-sync-para-resolver-problemas-de-dependencias">
        Utilizar distro-sync para resolver problemas de dependencias
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/12/linux-fedora-upgrade/#utilizar-distro-sync-para-resolver-problemas-de-dependencias" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Utilizar distro-sync para resolver problemas de dependencias" href="#utilizar-distro-sync-para-resolver-problemas-de-dependencias">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h3>
</div><p>La herramienta de actualización de sistema utiliza <code>dnf distro-sync</code> por defecto. Si el sistema se actualiza parcialmente o se aprecian algunos problemas de dependencia de paquetes, se puede intentar ejecutar el comando <code>distro-sync</code> manualmente para ver se resuelven. Esto intentará hacer que los paquetes instalados tengan la misma versión que la de los repositorios actualmente habilitados, incluso si para ello debe hacer un downgrade de algunos paquetes:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo dnf distro-sync
</span></span></code></pre></div><p>Se puede además especificar la opción <code>--allowerasing</code> que eliminará paquetes con dependencias que no puedan ser resueltas. Siempre hay que revisar qué paquetes se eliminarán confirmándolo de esta manera:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo dnf distro-sync --allowerasing
</span></span></code></pre></div><div class="gblog-post__anchorwrap">
    <h3 id="reetiquetar-archivos-con-la-última-política-selinux">
        Reetiquetar archivos con la última política SELinux
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/12/linux-fedora-upgrade/#reetiquetar-archivos-con-la-última-política-selinux" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Reetiquetar archivos con la última política SELinux" href="#reetiquetar-archivos-con-la-%c3%baltima-pol%c3%adtica-selinux">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h3>
</div><p>Si aparece alguna advertencia referentes a políticas SELinux, puede que algunos archivos tengan permisos SELinux incorrectos. Esto puede ocurrir si Ss.fedoraprojELinux fue inhabilitado en el pasado. Para reetiquetar SELinux en el sistema, hay que ejecutar el siguiente comando y después reiniciar:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo fixfiles -B onboot
</span></span></code></pre></div><p>El proceso de arranque puede llevar mucho tiempo, ya que comprueba y corrige las etiquetas de permisos SELinux en todos los archivos del sistema.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="referencias">
        Referencias
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/12/linux-fedora-upgrade/#referencias" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Referencias" href="#referencias">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><ul>
<li><a class="gblog-markdown__link" href="https://docs.fedoraproject.org/en-US/quick-docs/dnf-system-upgrade/">https://docs.fedoraproject.org/en-US/quick-docs/dnf-system-upgrade/</a></li>
<li><a class="gblog-markdown__link" href="https://docs.fedoraproject.org/en-US/releases/lifecycle/">https://docs.fedoraproject.org/en-US/releases/lifecycle/</a></li>
</ul>
<p> </p>]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://dvdcr.gitlab.io/tags/linux" term="linux" label="linux" />
                            
                        
                    
                
            
        </entry>
    
        
        <entry>
            <title type="html"><![CDATA[Configurar Sensor de Fugas de Agua Aqara SJCGQ11LM]]></title>
            <link href="https://dvdcr.gitlab.io/posts/2022/11/iot-aquara-sensor-fugas-agua/?utm_source=atom_feed" rel="alternate" type="text/html"  hreflang="en" />
            <id>https://dvdcr.gitlab.io/posts/2022/11/iot-aquara-sensor-fugas-agua/</id>
            
            <published>2022-11-06T00:00:00+01:00</published>
            <updated>2022-12-11T00:00:00+01:00</updated>
            
            
            <content type="html"><![CDATA[<p><img src="/img/iot-aqara-sensor-fugas-agua.png#center" alt=""  /></p>
<p>El sensor Aqara SJCGQ11LM es un dispositivo con conectividad Zigbee que permite detectar fugas de agua.
En este artículo veremos como configurarlo para que publique valores a través de <strong>Zigbee2mqtt</strong>, lo que permitirá automatizar avisos en caso de detección, ya sea a través de scripts o aplicaciones propias o de <strong>Home Assistant</strong>.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="requisitos-previos">
        Requisitos previos
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/11/iot-aquara-sensor-fugas-agua/#requisitos-previos" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Requisitos previos" href="#requisitos-previos">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Debemos disponer de los siguientes componentes ya instalados y configurados:</p>
<ul>
<li><strong>Broker MQTT</strong>. En <a class="gblog-markdown__link" href="https://dvdcr.gitlab.io/posts/2022/01/iot-rpi-mqtt/">este artículo</a> se explica cómo instalar <strong>mosquitto</strong> en una Raspberry Pi.</li>
<li><strong>Zigbee2mqtt</strong>. La instalación y configuración básica se describen en <a class="gblog-markdown__link" href="https://dvdcr.gitlab.io/posts/2022/02/iot-rpi-zigbee2mqtt/">este artículo</a></li>
</ul>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="emparejamiento">
        Emparejamiento
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/11/iot-aquara-sensor-fugas-agua/#emparejamiento" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Emparejamiento" href="#emparejamiento">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Mantenemos pulsada la parte superior del sensor, donde está el icono de la gota de agua, hasta que el led azul que está justo al lado del icono empiece a parpadear. Esto hará que se inicie el emparejamiento. En el log de <strong>zigbee2mqtt</strong> aparecerá:</p>
<pre tabindex="0"><code>info  2022-09-12 07:28:39: Device &#39;0x00158d0008059a5b&#39; joined
info  2022-09-12 07:28:39: MQTT publish: topic &#39;zigbee2mqtt/bridge/event&#39;, payload &#39;{&#34;data&#34;:{&#34;friendly_name&#34;:&#34;0x00158d0008059a5b&#34;,&#34;ieee_address&#34;:&#34;0x00158d0008059a5b&#34;},&#34;type&#34;:&#34;device_joined&#34;}&#39;
info  2022-09-12 07:28:39: Starting interview of &#39;0x00158d0008059a5b&#39;
info  2022-09-12 07:28:39: MQTT publish: topic &#39;zigbee2mqtt/bridge/event&#39;, payload &#39;{&#34;data&#34;:{&#34;friendly_name&#34;:&#34;0x00158d0008059a5b&#34;,&#34;ieee_address&#34;:&#34;0x00158d0008059a5b&#34;,&#34;status&#34;:&#34;started&#34;},&#34;type&#34;:&#34;device_interview&#34;}&#39;
info  2022-09-12 07:28:40: MQTT publish: topic &#39;zigbee2mqtt/bridge/event&#39;, payload &#39;{&#34;data&#34;:{&#34;friendly_name&#34;:&#34;0x00158d0008059a5b&#34;,&#34;ieee_address&#34;:&#34;0x00158d0008059a5b&#34;},&#34;type&#34;:&#34;device_announce&#34;}&#39;
info  2022-09-12 07:28:43: MQTT publish: topic &#39;zigbee2mqtt/enchufe&#39;, payload &#39;{&#34;current&#34;:0,&#34;energy&#34;:0.12,&#34;linkquality&#34;:72,&#34;power&#34;:0,&#34;power_outage_memory&#34;:&#34;restore&#34;,&#34;state&#34;:&#34;OFF&#34;,&#34;voltage&#34;:360}&#39;
info  2022-09-12 07:28:58: MQTT publish: topic &#39;zigbee2mqtt/enchufe&#39;, payload &#39;{&#34;current&#34;:0,&#34;energy&#34;:0.12,&#34;linkquality&#34;:69,&#34;power&#34;:0,&#34;power_outage_memory&#34;:&#34;restore&#34;,&#34;state&#34;:&#34;OFF&#34;,&#34;voltage&#34;:358}&#39;
info  2022-09-12 07:29:10: Successfully interviewed &#39;0x00158d0008059a5b&#39;, device has successfully been paired
info  2022-09-12 07:29:10: Device &#39;0x00158d0008059a5b&#39; is supported, identified as: Xiaomi Aqara water leak sensor (SJCGQ11LM)
info  2022-09-12 07:29:10: MQTT publish: topic &#39;zigbee2mqtt/bridge/event&#39;, payload &#39;{&#34;data&#34;:{&#34;definition&#34;:{&#34;description&#34;:&#34;Aqara water leak sensor&#34;,&#34;exposes&#34;:[{&#34;access&#34;:1,&#34;description&#34;:&#34;Remaining battery in %&#34;,&#34;name&#34;:&#34;battery&#34;,&#34;property&#34;:&#34;battery&#34;,&#34;type&#34;:&#34;numeric&#34;,&#34;unit&#34;:&#34;%&#34;,&#34;value_max&#34;:100,&#34;value_min&#34;:0},{&#34;access&#34;:1,&#34;description&#34;:&#34;Indicates whether the device detected a water leak&#34;,&#34;name&#34;:&#34;water_leak&#34;,&#34;property&#34;:&#34;water_leak&#34;,&#34;type&#34;:&#34;binary&#34;,&#34;value_off&#34;:false,&#34;value_on&#34;:true},{&#34;access&#34;:1,&#34;description&#34;:&#34;Indicates if the battery of this device is almost empty&#34;,&#34;name&#34;:&#34;battery_low&#34;,&#34;property&#34;:&#34;battery_low&#34;,&#34;type&#34;:&#34;binary&#34;,&#34;value_off&#34;:false,&#34;value_on&#34;:true},{&#34;access&#34;:1,&#34;description&#34;:&#34;Voltage of the battery in millivolts&#34;,&#34;name&#34;:&#34;voltage&#34;,&#34;property&#34;:&#34;voltage&#34;,&#34;type&#34;:&#34;numeric&#34;,&#34;unit&#34;:&#34;mV&#34;},{&#34;access&#34;:1,&#34;description&#34;:&#34;Measured temperature value&#34;,&#34;name&#34;:&#34;temperature&#34;,&#34;property&#34;:&#34;temperature&#34;,&#34;type&#34;:&#34;numeric&#34;,&#34;unit&#34;:&#34;°C&#34;},{&#34;access&#34;:1,&#34;description&#34;:&#34;Link quality (signal strength)&#34;,&#34;name&#34;:&#34;linkquality&#34;,&#34;property&#34;:&#34;linkquality&#34;,&#34;type&#34;:&#34;numeric&#34;,&#34;unit&#34;:&#34;lqi&#34;,&#34;value_max&#34;:255,&#34;value_min&#34;:0}],&#34;model&#34;:&#34;SJCGQ11LM&#34;,&#34;options&#34;:[],&#34;supports_ota&#34;:false,&#34;vendor&#34;:&#34;Xiaomi&#34;},&#34;friendly_name&#34;:&#34;0x00158d0008059a5b&#34;,&#34;ieee_address&#34;:&#34;0x00158d0008059a5b&#34;,&#34;status&#34;:&#34;successful&#34;,&#34;supported&#34;:true},&#34;type&#34;:&#34;device_interview&#34;}&#39;
info  2022-09-12 07:29:10: MQTT publish: topic &#39;homeassistant/sensor/0x00158d0008059a5b/battery/config&#39;, payload &#39;{&#34;availability&#34;:[{&#34;topic&#34;:&#34;zigbee2mqtt/bridge/state&#34;}],&#34;device&#34;:{&#34;identifiers&#34;:[&#34;zigbee2mqtt_0x00158d0008059a5b&#34;],&#34;manufacturer&#34;:&#34;Xiaomi&#34;,&#34;model&#34;:&#34;Aqara water leak sensor (SJCGQ11LM)&#34;,&#34;name&#34;:&#34;0x00158d0008059a5b&#34;,&#34;sw_version&#34;:&#34;3000-0001&#34;},&#34;device_class&#34;:&#34;battery&#34;,&#34;enabled_by_default&#34;:true,&#34;entity_category&#34;:&#34;diagnostic&#34;,&#34;name&#34;:&#34;0x00158d0008059a5b battery&#34;,&#34;state_class&#34;:&#34;measurement&#34;,&#34;state_topic&#34;:&#34;zigbee2mqtt/0x00158d0008059a5b&#34;,&#34;unique_id&#34;:&#34;0x00158d0008059a5b_battery_zigbee2mqtt&#34;,&#34;unit_of_measurement&#34;:&#34;%&#34;,&#34;value_template&#34;:&#34;{{ value_json.battery }}&#34;}&#39;
info  2022-09-12 07:29:10: MQTT publish: topic &#39;homeassistant/binary_sensor/0x00158d0008059a5b/water_leak/config&#39;, payload &#39;{&#34;availability&#34;:[{&#34;topic&#34;:&#34;zigbee2mqtt/bridge/state&#34;}],&#34;device&#34;:{&#34;identifiers&#34;:[&#34;zigbee2mqtt_0x00158d0008059a5b&#34;],&#34;manufacturer&#34;:&#34;Xiaomi&#34;,&#34;model&#34;:&#34;Aqara water leak sensor (SJCGQ11LM)&#34;,&#34;name&#34;:&#34;0x00158d0008059a5b&#34;,&#34;sw_version&#34;:&#34;3000-0001&#34;},&#34;device_class&#34;:&#34;moisture&#34;,&#34;name&#34;:&#34;0x00158d0008059a5b water leak&#34;,&#34;payload_off&#34;:false,&#34;payload_on&#34;:true,&#34;state_topic&#34;:&#34;zigbee2mqtt/0x00158d0008059a5b&#34;,&#34;unique_id&#34;:&#34;0x00158d0008059a5b_water_leak_zigbee2mqtt&#34;,&#34;value_template&#34;:&#34;{{ value_json.water_leak }}&#34;}&#39;
info  2022-09-12 07:29:10: MQTT publish: topic &#39;homeassistant/binary_sensor/0x00158d0008059a5b/battery_low/config&#39;, payload &#39;{&#34;availability&#34;:[{&#34;topic&#34;:&#34;zigbee2mqtt/bridge/state&#34;}],&#34;device&#34;:{&#34;identifiers&#34;:[&#34;zigbee2mqtt_0x00158d0008059a5b&#34;],&#34;manufacturer&#34;:&#34;Xiaomi&#34;,&#34;model&#34;:&#34;Aqara water leak sensor (SJCGQ11LM)&#34;,&#34;name&#34;:&#34;0x00158d0008059a5b&#34;,&#34;sw_version&#34;:&#34;3000-0001&#34;},&#34;device_class&#34;:&#34;battery&#34;,&#34;entity_category&#34;:&#34;diagnostic&#34;,&#34;name&#34;:&#34;0x00158d0008059a5b battery low&#34;,&#34;payload_off&#34;:false,&#34;payload_on&#34;:true,&#34;state_topic&#34;:&#34;zigbee2mqtt/0x00158d0008059a5b&#34;,&#34;unique_id&#34;:&#34;0x00158d0008059a5b_battery_low_zigbee2mqtt&#34;,&#34;value_template&#34;:&#34;{{ value_json.battery_low }}&#34;}&#39;
info  2022-09-12 07:29:10: MQTT publish: topic &#39;homeassistant/sensor/0x00158d0008059a5b/voltage/config&#39;, payload &#39;{&#34;availability&#34;:[{&#34;topic&#34;:&#34;zigbee2mqtt/bridge/state&#34;}],&#34;device&#34;:{&#34;identifiers&#34;:[&#34;zigbee2mqtt_0x00158d0008059a5b&#34;],&#34;manufacturer&#34;:&#34;Xiaomi&#34;,&#34;model&#34;:&#34;Aqara water leak sensor (SJCGQ11LM)&#34;,&#34;name&#34;:&#34;0x00158d0008059a5b&#34;,&#34;sw_version&#34;:&#34;3000-0001&#34;},&#34;device_class&#34;:&#34;voltage&#34;,&#34;enabled_by_default&#34;:false,&#34;entity_category&#34;:&#34;diagnostic&#34;,&#34;name&#34;:&#34;0x00158d0008059a5b voltage&#34;,&#34;state_class&#34;:&#34;measurement&#34;,&#34;state_topic&#34;:&#34;zigbee2mqtt/0x00158d0008059a5b&#34;,&#34;unique_id&#34;:&#34;0x00158d0008059a5b_voltage_zigbee2mqtt&#34;,&#34;unit_of_measurement&#34;:&#34;mV&#34;,&#34;value_template&#34;:&#34;{{ value_json.voltage }}&#34;}&#39;
info  2022-09-12 07:29:10: MQTT publish: topic &#39;homeassistant/sensor/0x00158d0008059a5b/temperature/config&#39;, payload &#39;{&#34;availability&#34;:[{&#34;topic&#34;:&#34;zigbee2mqtt/bridge/state&#34;}],&#34;device&#34;:{&#34;identifiers&#34;:[&#34;zigbee2mqtt_0x00158d0008059a5b&#34;],&#34;manufacturer&#34;:&#34;Xiaomi&#34;,&#34;model&#34;:&#34;Aqara water leak sensor (SJCGQ11LM)&#34;,&#34;name&#34;:&#34;0x00158d0008059a5b&#34;,&#34;sw_version&#34;:&#34;3000-0001&#34;},&#34;device_class&#34;:&#34;temperature&#34;,&#34;enabled_by_default&#34;:true,&#34;name&#34;:&#34;0x00158d0008059a5b temperature&#34;,&#34;state_class&#34;:&#34;measurement&#34;,&#34;state_topic&#34;:&#34;zigbee2mqtt/0x00158d0008059a5b&#34;,&#34;unique_id&#34;:&#34;0x00158d0008059a5b_temperature_zigbee2mqtt&#34;,&#34;unit_of_measurement&#34;:&#34;°C&#34;,&#34;value_template&#34;:&#34;{{ value_json.temperature }}&#34;}&#39;
info  2022-09-12 07:29:10: MQTT publish: topic &#39;homeassistant/sensor/0x00158d0008059a5b/linkquality/config&#39;, payload &#39;{&#34;availability&#34;:[{&#34;topic&#34;:&#34;zigbee2mqtt/bridge/state&#34;}],&#34;device&#34;:{&#34;identifiers&#34;:[&#34;zigbee2mqtt_0x00158d0008059a5b&#34;],&#34;manufacturer&#34;:&#34;Xiaomi&#34;,&#34;model&#34;:&#34;Aqara water leak sensor (SJCGQ11LM)&#34;,&#34;name&#34;:&#34;0x00158d0008059a5b&#34;,&#34;sw_version&#34;:&#34;3000-0001&#34;},&#34;enabled_by_default&#34;:false,&#34;entity_category&#34;:&#34;diagnostic&#34;,&#34;icon&#34;:&#34;mdi:signal&#34;,&#34;name&#34;:&#34;0x00158d0008059a5b linkquality&#34;,&#34;state_class&#34;:&#34;measurement&#34;,&#34;state_topic&#34;:&#34;zigbee2mqtt/0x00158d0008059a5b&#34;,&#34;unique_id&#34;:&#34;0x00158d0008059a5b_linkquality_zigbee2mqtt&#34;,&#34;unit_of_measurement&#34;:&#34;lqi&#34;,&#34;value_template&#34;:&#34;{{ value_json.linkquality }}&#34;}&#39;
</code></pre><p>Si en el log aparece el mensaje <code>device_joined</code> será indicativo de que se ha emparejado correctamente.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="asignación-de-un-nombre-amigable">
        Asignación de un nombre amigable
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/11/iot-aquara-sensor-fugas-agua/#asignación-de-un-nombre-amigable" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Asignación de un nombre amigable" href="#asignaci%c3%b3n-de-un-nombre-amigable">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Para dar un nombre más amigable al dispositivo, paramos el servicio <strong>zigbee2mqtt</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo systemctl stop zigbee2mqtt
</span></span></code></pre></div><p>Editamos el archivo de configuración de <strong>zigbee2mqtt</strong>, que en este caso está en <code>/opt/zigbee2mqtt/data/configuration.yaml</code>.</p>
<p>Al final del archivo, en la sección <code>devices</code> nos aparecerá una entrada del tipo:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vim" data-lang="vim"><span class="line"><span class="cl">  <span class="s1">&#39;0x00158d0008059a5b&#39;</span>:
</span></span><span class="line"><span class="cl">    <span class="nx">friendly_name</span>: <span class="s1">&#39;0x00158d0008059a5b&#39;</span>
</span></span></code></pre></div><p>Cambiamos el valor del atributo <code>friendly_name</code> por otro que nos resulte más fácil de reconocer:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vim" data-lang="vim"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;0x00158d0008059a5b&#39;</span>:
</span></span><span class="line"><span class="cl">    <span class="nx">friendly_name</span>: <span class="s1">&#39;sfa_banyo&#39;</span>
</span></span></code></pre></div><p>y arrancamos el servicio de nuevo para que se aplique el nuevo nombre:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo systemctl start zigbee2mqtt
</span></span></code></pre></div><p>Ahora debe publicar los eventos en el <strong>topic</strong> <code>zigbee2mqtt/sfa_banyo</code>.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="lectura-de-valores">
        Lectura de valores
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/11/iot-aquara-sensor-fugas-agua/#lectura-de-valores" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Lectura de valores" href="#lectura-de-valores">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>El funcionamiento del dispositivo es muy sencillo. En la parte inferior tiene dos tornillos. Si ambos tornillos entran en contacto con agua, se transmite electricidad entre ambos y se considera que hay fuga de agua. Se trata de un dispositivo de bajo consumo, por lo que el sensor no está accesible la mayoría del tiempo, sólo cuando se activa porque detecta una fuga de agua. Podemos forzarla introduciendo el sensor en un vaso con el nivel de agua para que los dos tornillos de la parte inferior entren en contacto con ella.</p>
<p><img src="/img/iot-aqara-sensor-fugas-agua-back.png#center" alt=""  /></p>
<p>Las peticiones de lectura de valores o escritura para configurar parámetros no se procesan inmediatamente sino que son encoladas y se envían en la siguiente ocasión en la que está disponible.</p>
<p>Para leer valores, nos suscribimos con <code>mosquitto_sub</code> al <strong>topic</strong> <code>zigbee2mqtt/sfa_banyo</code> en nuestro <strong>broker MQTT</strong>. Si lo hacemos desde dentro de la propia Raspberry pi (o el servidor donde se esté ejecutando <strong>mosquitto</strong>) utilizaremos el parámetro <code>-h localhost</code>. Si lo hacemos desde otro equipo, especificaremos la dirección IP del la máquina donde se ejecute <strong>mosquitto</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">mosquitto_sub -h localhost -p <span class="m">1883</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-u <span class="nv">$MQTTUSER</span> -P <span class="nv">$MQTTPASS</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-t <span class="s2">&#34;zigbee2mqtt/sfa_banyo&#34;</span>
</span></span></code></pre></div><p>Cuando se detecta una fuga de agua, el mensaje enviado es el siguiente:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span><span class="nt">&#34;battery&#34;</span><span class="p">:</span><span class="mi">100</span><span class="p">,</span><span class="nt">&#34;battery_low&#34;</span><span class="p">:</span><span class="kc">false</span><span class="p">,</span><span class="nt">&#34;linkquality&#34;</span><span class="p">:</span><span class="mi">36</span><span class="p">,</span><span class="nt">&#34;tamper&#34;</span><span class="p">:</span><span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="nt">&#34;temperature&#34;</span><span class="p">:</span><span class="mi">25</span><span class="p">,</span><span class="nt">&#34;voltage&#34;</span><span class="p">:</span><span class="mi">3015</span><span class="p">,</span><span class="nt">&#34;water_leak&#34;</span><span class="p">:</span><span class="kc">true</span><span class="p">}</span>
</span></span></code></pre></div><p>Cuando deja de producirse la fuga, el mensaje enviado es el siguiente:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span><span class="nt">&#34;battery&#34;</span><span class="p">:</span><span class="mi">100</span><span class="p">,</span><span class="nt">&#34;battery_low&#34;</span><span class="p">:</span><span class="kc">false</span><span class="p">,</span><span class="nt">&#34;linkquality&#34;</span><span class="p">:</span><span class="mi">96</span><span class="p">,</span><span class="nt">&#34;tamper&#34;</span><span class="p">:</span><span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="nt">&#34;temperature&#34;</span><span class="p">:</span><span class="mi">25</span><span class="p">,</span><span class="nt">&#34;voltage&#34;</span><span class="p">:</span><span class="mi">3015</span><span class="p">,</span><span class="nt">&#34;water_leak&#34;</span><span class="p">:</span><span class="kc">false</span><span class="p">}</span>
</span></span></code></pre></div><p>Los parámetros que publica son los siguientes:</p>
<ul>
<li><strong>battery</strong>. Número que indica el tanto por ciento (%) de batería restante. En las pruebas realizadas, hasta que transcurrió una hora del emparejamiento del dispositivo, no empezaron a transmitirse valores en este parámetro y aparecía el valor <code>null</code>.</li>
<li><strong>battery_low</strong>. Envía <code>true</code> si la pila está baja y es necesario cambiarla o <code>false</code> si tiene un nivel correcto.</li>
<li><strong>linkquality</strong>. Número que indica la fuerza de la señal de enlace con el hub. El valor puede oscilar entre 0 (mínimo valor, cuando no hay señal) y 255, que sería el máximo valor. La unidad de este valor es <strong>lqi</strong> (<em>Link Quality Indicator</em>).</li>
<li><strong>tamper</strong>. Indica si el dispositivo es manipulado. Algunos detectores incluyen un par de terminales llamados <strong>tamper</strong>, como sistema anti-sabotaje. Cuando alguien trata de abrir el dispositivo, la apertura se detecta y se envía una señal para avisar de ello. En este caso, se envía el valor <code>true</code> en este parámetro si el dispositivo es manipulado o <code>false</code> en caso contrario.</li>
<li><strong>temperature</strong>. Temperatura del dispositivo en ºC. En las pruebas realizadas, hasta que transcurrió una hora del emparejamiento del dispositivo, no empezaron a transmitirse valores en este parámetro y aparecía el valor <code>null</code>.</li>
<li><strong>voltage</strong>. Voltaje de la pila en milivoltios. Al igual que ocurre con <code>battery</code> y <code>temperature</code>, en las pruebas realizadas, hasta que transcurrió una hora del emparejamiento del dispositivo, no empezaron a transmitirse valores en este parámetro y aparecía el valor <code>null</code>.</li>
<li><strong>water_leak</strong>. Si se detecta una fuga de agua, contendrá el valor <code>true</code>. Cuando no haya ninguna fuga detectada, se enviará valor <code>false</code>.</li>
</ul>
<p>Admite la calibración de la temperatura del dispositivo, que podemos realizar añadiendo en la correspondiente entrada del archivo <code>configuration.yaml</code> de zigbee2mqtt lo siguiente:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><span class="nt">&#39;0x00158d0008059a5b&#39;</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">friendly_name</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;sfa_banyo&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">device_temperature_calibration</span><span class="p">:</span><span class="w"> </span><span class="m">3</span><span class="w">
</span></span></span></code></pre></div><p>o bien publicar en el servidor mqtt un payload con ese valor en el topic asociado al dispositivo:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">mosquitto_pub -d -h localhost -p <span class="m">1883</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-u <span class="nv">$MQTTUSER</span> -P <span class="nv">$MQTTPASS</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-t <span class="s2">&#34;zigbee2mqtt/sfa_banyo&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-m <span class="s1">&#39;{&#34;device_temperature_calibration&#34;: &#34;3&#34;}&#39;</span>
</span></span></code></pre></div><p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="resumen">
        Resumen
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/11/iot-aquara-sensor-fugas-agua/#resumen" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Resumen" href="#resumen">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Podemos tener un sistema de detección de fugas de agua para aquellos lugares donde exista riesgo de ello de una forma muy sencilla con este dispositivo. Para ello debemos disponer de un <strong>gateway Zigbee</strong> y un <strong>broker MQTT</strong> al que emparejaremos el sensor de fugas de agua con conectividad <strong>Zigbee</strong> Aqara SJCGQ11LM. Se ha visto como configurarlo y consultar su estado sin necesidad de hacer uso de aplicaciones del fabricante, a través de la publicación y suscripción a <strong>topics</strong> <strong>MQTT</strong>, lo que permitiría detectar mediante scripts en bash o cualquier lenguaje de programación como python las fugas y disparar una alarma o enviar un aviso. En combinación con <a class="gblog-markdown__link" href="https://www.home-assistant.io/">Home Assistant</a> permitiría crear automatizaciones de forma sencilla para estos casos.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="referencias">
        Referencias
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/11/iot-aquara-sensor-fugas-agua/#referencias" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Referencias" href="#referencias">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><ul>
<li><a class="gblog-markdown__link" href="https://www.zigbee2mqtt.io/devices/SJCGQ11LM.html">https://www.zigbee2mqtt.io/devices/SJCGQ11LM.html</a></li>
</ul>]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://dvdcr.gitlab.io/tags/IoT" term="IoT" label="IoT" />
                            
                        
                    
                
            
        </entry>
    
        
        <entry>
            <title type="html"><![CDATA[Configurar sensor de movimiento Zigbee SNZB-03 con Zigbee2MQTT]]></title>
            <link href="https://dvdcr.gitlab.io/posts/2022/04/iot-sonoff-sensor-movimiento-SNZB-03/?utm_source=atom_feed" rel="alternate" type="text/html"  hreflang="en" />
            <id>https://dvdcr.gitlab.io/posts/2022/04/iot-sonoff-sensor-movimiento-SNZB-03/</id>
            
            <published>2022-04-03T00:00:00+01:00</published>
            <updated>2022-04-03T00:00:00+01:00</updated>
            
            
            <content type="html"><![CDATA[<p><img src="/img/sonoff-SNZB-03.jpg#center" alt=""  /></p>
<p>El <strong>Sonoff SNZB-03</strong> es un sensor de movimiento que se conecta a través de protocolo <strong>Zigbee</strong>. Se alimenta con una pila de botón de 3V CR2450. Presenta unas dimensiones muy reducidas (40x35x30mm)</p>
<p>Está diseñado para funcionar con la aplicación <a class="gblog-markdown__link" href="https://www.ewelink.cc">eWeLink</a>, pero veremos cómo configurarlo para su uso con <strong>zigbee2mqtt</strong>, lo que abre el paso para su control mediante scripts o programas propios y a la integración plataformas como  <strong>Home Assistant</strong>, pero sin requerir de servicios externos.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="requisitos-previos">
        Requisitos previos
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/04/iot-sonoff-sensor-movimiento-SNZB-03/#requisitos-previos" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Requisitos previos" href="#requisitos-previos">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Debemos disponer de los siguientes componentes ya instalados y configurados:</p>
<ul>
<li><strong>Broker MQTT</strong>. En <a class="gblog-markdown__link" href="https://dvdcr.gitlab.io/posts/2022/01/iot-rpi-mqtt/">este artículo</a> se explica cómo instalar <strong>mosquitto</strong> en una Raspberry Pi.</li>
<li><strong>Zigbee2mqtt</strong>. La instalación y configuración básica se describen en <a class="gblog-markdown__link" href="https://dvdcr.gitlab.io/posts/2022/02/iot-rpi-zigbee2mqtt/">este artículo</a></li>
</ul>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="emparejamiento">
        Emparejamiento
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/04/iot-sonoff-sensor-movimiento-SNZB-03/#emparejamiento" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Emparejamiento" href="#emparejamiento">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Pulsamos durante cinco segundos el botón de reset, hasta que comience a parpadear un led rojo. El botón de reset está dentro de un pequeño orificio para evitar que se active por accidente. Hay que pulsarlo con un pin similar a los que se utilizan para abrir la bandeja de la tarjeta SIM de los teléfonos, incluído en la caja del dispositivo. Una vez activada la sincronización en el log de <strong>zigbee2mqtt</strong> aparecerá:</p>
<pre tabindex="0"><code>info  2022-02-04 22:46:12: Device &#39;0x00124b00250beb63&#39; joined
info  2022-02-04 22:46:12: MQTT publish: topic &#39;zigbee2mqtt/bridge/event&#39;, payload &#39;{&#34;data&#34;:{&#34;friendly_name&#34;:&#34;0x00124b00250beb63&#34;,&#34;ieee_address&#34;:&#34;0x00124b00250beb63&#34;},&#34;type&#34;:&#34;device_joined&#34;}&#39;
info  2022-02-04 22:46:12: Starting interview of &#39;0x00124b00250beb63&#39;
info  2022-02-04 22:46:12: MQTT publish: topic &#39;zigbee2mqtt/bridge/event&#39;, payload &#39;{&#34;data&#34;:{&#34;friendly_name&#34;:&#34;0x00124b00250beb63&#34;,&#34;ieee_address&#34;:&#34;0x00124b00250beb63&#34;,&#34;status&#34;:&#34;started&#34;},&#34;type&#34;:&#34;device_interview&#34;}&#39;
info  2022-02-04 22:46:13: MQTT publish: topic &#39;zigbee2mqtt/bridge/event&#39;, payload &#39;{&#34;data&#34;:{&#34;friendly_name&#34;:&#34;0x00124b00250beb63&#34;,&#34;ieee_address&#34;:&#34;0x00124b00250beb63&#34;},&#34;type&#34;:&#34;device_announce&#34;}&#39;
</code></pre><p>Si en el log aparece el mensaje <code>device_joined</code> será indicativo de que se ha emparejado correctamente.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="asignación-de-un-nombre-amigable">
        Asignación de un nombre amigable
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/04/iot-sonoff-sensor-movimiento-SNZB-03/#asignación-de-un-nombre-amigable" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Asignación de un nombre amigable" href="#asignaci%c3%b3n-de-un-nombre-amigable">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Para dar un nombre más amigable al dispositivo, paramos el servicio <strong>zigbee2mqtt</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo systemctl stop zigbee2mqtt
</span></span></code></pre></div><p>Editamos el archivo de configuración de <strong>zigbee2mqtt</strong>, que en este caso está en <code>/opt/zigbee2mqtt/data/configuration.yaml</code>.</p>
<p>Al final del archivo, en la sección <code>devices</code> nos aparecerá una entrada del tipo:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vim" data-lang="vim"><span class="line"><span class="cl">  <span class="s1">&#39;0x00124b00250beb63&#39;</span>:
</span></span><span class="line"><span class="cl">    <span class="nx">friendly_name</span>: <span class="s1">&#39;0x00124b00250beb63&#39;</span>
</span></span></code></pre></div><p>Cambiamos el valor del atributo <code>friendly_name</code> por otro que nos resulte más fácil de reconocer:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vim" data-lang="vim"><span class="line"><span class="cl">  <span class="s1">&#39;0x00124b00250beb63&#39;</span>:
</span></span><span class="line"><span class="cl">    <span class="nx">firendly_name</span>: <span class="s1">&#39;sensor_movimiento&#39;</span>
</span></span></code></pre></div><p>y arrancamos el servicio de nuevo para que se aplique el nuevo nombre:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo systemctl start zigbee2mqtt
</span></span></code></pre></div><p>Ahora debe publicar los eventos en el <strong>topic</strong> <code>zigbee2mqtt/sensor_movimiento</code>.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="lectura-de-valores">
        Lectura de valores
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/04/iot-sonoff-sensor-movimiento-SNZB-03/#lectura-de-valores" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Lectura de valores" href="#lectura-de-valores">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Se trata de un dispositivo de bajo consumo, por lo que el sensor no está accesible la mayoría del tiempo, sólo cuando se activa porque se detecta un movimiento.</p>
<p>Las peticiones de lectura de valores no se procesan inmediatamente sino que son encoladas y se envían en la siguiente ocasión en la que está disponible.</p>
<p>Para leer valores, nos suscribimos con <code>mosquitto_sub</code> al <strong>topic</strong> <code>zigbee2mqtt/sensor_movimiento</code> en nuestro <strong>broker MQTT</strong>. Si lo hacemos desde dentro de la propia Raspberry pi (o el servidor donde se esté ejecutando <strong>mosquitto</strong>) utilizaremos el parámetro <code>-h localhost</code>. Si lo hacemos desde otro equipo, especificaremos la dirección IP del la máquina donde se ejecute <strong>mosquitto</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">mosquitto_sub -h localhost -p <span class="m">1883</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-u <span class="nv">$MQTTUSER</span> -P <span class="nv">$MQTTPASS</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-t <span class="s2">&#34;zigbee2mqtt/sensor_movimiento&#34;</span>
</span></span></code></pre></div><p>Cuando detecta un movimiento, recibimos el valor <code>true</code> en el parámetro <code>occupancy</code>.</p>
<pre tabindex="0"><code>{&#34;battery&#34;:57.5,&#34;battery_low&#34;:false,&#34;linkquality&#34;:111,&#34;occupancy&#34;:true,&#34;tamper&#34;:false,&#34;voltage&#34;:2900}
</code></pre><p>Cuando se deja de detectar, se envía el valor <code>false</code> en ese mismo parámetro:</p>
<pre tabindex="0"><code>{&#34;battery&#34;:57.5,&#34;battery_low&#34;:false,&#34;linkquality&#34;:114,&#34;occupancy&#34;:false,&#34;tamper&#34;:false,&#34;voltage&#34;:2900}
</code></pre><p>Además del parámetro <code>occupancy</code> que es el que nos indica si realmente se detecta movimiento, el mensaje <em>JSON</em> muestra otros parámetros:</p>
<ul>
<li><strong>battery_low</strong>. Indica si la batería está a punto de acabarse, en cuyo caso contiene el valor <code>true</code>. Si la batería tiene un nivel aceptable contiene el valor <code>false</code>.</li>
<li><strong>battery</strong>. Número que indica la batería restante en porcentaje.</li>
<li><strong>tamper</strong>. Indica si el dispositivo es manipulado. Algunos detectores incluyen un par de terminales llamados <strong>tamper</strong>, como sistema anti-sabotaje. Cuando alguien trata de abrir el dispositivo, la apertura se detecta y se envía una señal para avisar de ello. En este caso, se envía el valor <code>true</code> en este parámetro si el dispositivo es manipulado o <code>false</code> en caso contrario.</li>
<li><strong>linkquality</strong>. Fuerza de la señal. El valor mínimo es 0 y el máximo 255. La unidad de este valor es <strong>lqi</strong> (<em>Link Quality Indicator</em>).</li>
<li><strong>voltage</strong>. Voltaje de la pila en milivoltios.</li>
</ul>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="parámetros-de-configuración">
        Parámetros de configuración
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/04/iot-sonoff-sensor-movimiento-SNZB-03/#parámetros-de-configuración" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Parámetros de configuración" href="#par%c3%a1metros-de-configuraci%c3%b3n">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Este dispositivo no tiene parámetros de configuración conocidos a la hora de utilizarlo con <strong>Zigbee2mqtt</strong> como sí tienen otros dispositivos similares como el <a class="gblog-markdown__link" href="https://dvdcr.gitlab.io/posts/2022/03/iot-tuya-sensor-movimiento/">Tuya ZM-35ZH-Q</a>.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="resumen">
        Resumen
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/04/iot-sonoff-sensor-movimiento-SNZB-03/#resumen" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Resumen" href="#resumen">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Disponiendo de un <strong>gateway Zigbee</strong> y un <strong>broker MQTT</strong> se puede emparejar con el gateway un sensor de movimiento con conectividad <strong>Zigbee</strong>, en este caso el <strong>Sonoff SNZB-03</strong>. Se ha visto como consultar su estado a través de suscripción a <strong>topics</strong> <strong>MQTT</strong>. Esto permite detectar la entrada de una persona en un área y realizar acciones en consecuencia, como disparar una alarma, un aviso o encender una luz. En combinación con <a class="gblog-markdown__link" href="https://www.home-assistant.io/">Home Assistant</a> permitiría definir automatizaciones de forma sencilla.</p>
<p>Aún no he hecho un uso suficiente para sacar conclusiones definitivas. La parte negativa en comparación con el <a class="gblog-markdown__link" href="https://dvdcr.gitlab.io/posts/2022/03/iot-tuya-sensor-movimiento/">Tuya ZM-35ZH-Q</a> es que no admite ningún parámetro de configuración como la sensibilidad o el tiempo en el que permanece activa la detención. Como ventaja sobre ese otro dispositivo, el Sonoff nos informa del estado exacto de la batería.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="referencias">
        Referencias
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/04/iot-sonoff-sensor-movimiento-SNZB-03/#referencias" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Referencias" href="#referencias">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><ul>
<li><a class="gblog-markdown__link" href="https://www.zigbee2mqtt.io/devices/SNZB-03.html">https://www.zigbee2mqtt.io/devices/SNZB-03.html</a></li>
<li><a class="gblog-markdown__link" href="https://sonoff.tech/product-document/smart-home-security-doc/snzb-03-doc/">https://sonoff.tech/product-document/smart-home-security-doc/snzb-03-doc/</a></li>
<li><a class="gblog-markdown__link" href="https://sonoff.tech/wp-content/uploads/2021/03/%E5%BF%AB%E9%80%9F%E6%8C%87%E5%8D%97-SNZB-03-V1.0-20200506.pdf">https://sonoff.tech/wp-content/uploads/2021/03/%E5%BF%AB%E9%80%9F%E6%8C%87%E5%8D%97-SNZB-03-V1.0-20200506.pdf</a></li>
<li><a class="gblog-markdown__link" href="https://sonoff.tech/wp-content/uploads/2021/03/%E8%AF%B4%E6%98%8E%E4%B9%A6-SNZB-03-V1.0-20210305.pdf">https://sonoff.tech/wp-content/uploads/2021/03/%E8%AF%B4%E6%98%8E%E4%B9%A6-SNZB-03-V1.0-20210305.pdf</a></li>
</ul>]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://dvdcr.gitlab.io/tags/IoT" term="IoT" label="IoT" />
                            
                        
                    
                
            
        </entry>
    
        
        <entry>
            <title type="html"><![CDATA[Configurar sensor de apertura de puertas y ventanas Sonoff SNZB-04 con Zigbee2MQTT]]></title>
            <link href="https://dvdcr.gitlab.io/posts/2022/03/iot-sonoff-sensor-contacto/?utm_source=atom_feed" rel="alternate" type="text/html"  hreflang="en" />
            <id>https://dvdcr.gitlab.io/posts/2022/03/iot-sonoff-sensor-contacto/</id>
            
            <published>2022-03-27T00:00:00+01:00</published>
            <updated>2022-03-27T00:00:00+01:00</updated>
            
            
            <content type="html"><![CDATA[<p><img src="/img/iot-sonoff-snzb-04-global.png#center" alt=""  /></p>
<p>El <strong>Sonof SNZB-04</strong> es un sensor de contacto que se conecta a través de protocolo <strong>Zigbee</strong> y que se alimenta con una pila de botón de 3V CR2032. Nos permite detectar la apertura de una puerta o ventana.</p>
<p>Está compuesto por dos pequeños módulos, uno que contiene la electrónica y otro que contiene un imán. Estos módulos los podemos pegar en el borde de una puerta, poniendo uno de ellos en el marco o parte fija y el otro en la parte móvil, de forma que cuando esté cerrada, los dos módulos estén cerca el uno del otro y alineados según una marca de referencia.</p>
<p>Está diseñado para funcionar con la aplicación <em>eWeLink</em>, pero veremos cómo configurarlo para su uso con <strong>zigbee2mqtt</strong>, lo que abre el paso para su control mediante scripts o programas propios y a la integración plataformas como  <strong>Home Assistant</strong>, pero sin requerir de servicios externos.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="requisitos-previos">
        Requisitos previos
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/iot-sonoff-sensor-contacto/#requisitos-previos" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Requisitos previos" href="#requisitos-previos">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Debemos disponer de los siguientes componentes ya instalados y configurados:</p>
<ul>
<li><strong>Broker MQTT</strong>. En <a class="gblog-markdown__link" href="https://dvdcr.gitlab.io/posts/2022/01/iot-rpi-mqtt/">este artículo</a> se explica cómo instalar <strong>mosquitto</strong> en una Raspberry Pi.</li>
<li><strong>Zigbee2mqtt</strong>. La instalación y configuración básica se describen en <a class="gblog-markdown__link" href="https://dvdcr.gitlab.io/posts/2022/02/iot-rpi-zigbee2mqtt/">este artículo</a></li>
</ul>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="encendido">
        Encendido
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/iot-sonoff-sensor-contacto/#encendido" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Encendido" href="#encendido">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Para activar el sensor por primera vez es necesario retirar una protección que impide que la pila toque los contactos de alimentación del dispositivo. En primer lugar hay que abrir la tapa trasera del módulo principal, el de mayor tamaño. Para ello introducimos un destornillador en la muesca que tiene justo debajo de la tapa:</p>
<p><img src="/img/iot-sonoff-snzb-04-abrir-tapa.jpg#center" alt=""  /></p>
<p>Giramos el destornillador para forzar la apertura y una vez retirada la tapa, veremos debajo de la pila una pieza de plástico con la misma forma que la pila y una pequeña lengüeta de la que deberemos tirar para quitarla.</p>
<p><img src="/img/iot-sonoff-snzb-04-battery.jpg#center" alt=""  /></p>
<p>Una vez quitada la pieza de plástico, la pila estará tocando los contactos de ambos polos. Volvemos a poner la tapa y el dispositivo estará ya operativo para hacer el emparejamiento.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="emparejamiento">
        Emparejamiento
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/iot-sonoff-sensor-contacto/#emparejamiento" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Emparejamiento" href="#emparejamiento">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Pulsamos durante cinco segundos el botón de reset, hasta que veamos parpadear tres veces un led rojo en la tapa superior del módulo principal. El botón de reset está en el módulo principal, dentro de un pequeño orificio para evitar que se active por accidente. Hay que pulsarlo con un pin similar a los que se utilizan para abrir la bandeja de la tarjeta SIM de los teléfonos, incluído en la caja del dispositivo. Una vez activada la sincronización en el log de <strong>zigbee2mqtt</strong> aparecerá:</p>
<pre tabindex="0"><code>Device &#39;0x00124b0025003318&#39; joined
MQTT publish: topic &#39;zigbee2mqtt/bridge/event&#39;, payload &#39;{&#34;data&#34;:{&#34;friendly_name&#34;:&#34;0x00124b0025003318&#34;,&#34;ieee_address&#34;:&#34;0x00124b0025003318&#34;},&#34;type&#34;:&#34;device_joined&#34;}&#39;
Starting interview of &#39;0x00124b0025003318&#39;
MQTT publish: topic &#39;zigbee2mqtt/bridge/event&#39;, payload &#39;{&#34;data&#34;:{&#34;friendly_name&#34;:&#34;0x00124b0025003318&#34;,&#34;ieee_address&#34;:&#34;0x00124b0025003318&#34;,&#34;status&#34;:&#34;started&#34;},&#34;type&#34;:&#34;device_interview&#34;}&#39;
MQTT publish: topic &#39;zigbee2mqtt/bridge/event&#39;, payload &#39;{&#34;data&#34;:{&#34;friendly_name&#34;:&#34;0x00124b0025003318&#34;,&#34;ieee_address&#34;:&#34;0x00124b0025003318&#34;},&#34;type&#34;:&#34;device_announce&#34;}&#39;
Successfully interviewed &#39;0x00124b0025003318&#39;, device has successfully been paired
Device &#39;0x00124b0025003318&#39; is supported, identified as: SONOFF Contact sensor (SNZB-04)
MQTT publish: topic &#39;zigbee2mqtt/bridge/event&#39;, payload &#39;{&#34;data&#34;:{&#34;definition&#34;:{&#34;description&#34;:&#34;Contact sensor&#34;,&#34;exposes&#34;:[{&#34;access&#34;:1,&#34;description&#34;:&#34;Indicates if the contact is closed (= true) or open (= false)&#34;,&#34;name&#34;:&#34;contact&#34;,&#34;property&#34;:&#34;contact&#34;,&#34;type&#34;:&#34;binary&#34;,&#34;value_off&#34;:true,&#34;value_on&#34;:false},{&#34;access&#34;:1,&#34;description&#34;:&#34;Indicates if the battery of this device is almost empty&#34;,&#34;name&#34;:&#34;battery_low&#34;,&#34;property&#34;:&#34;battery_low&#34;,&#34;type&#34;:&#34;binary&#34;,&#34;value_off&#34;:false,&#34;value_on&#34;:true},{&#34;access&#34;:1,&#34;description&#34;:&#34;Remaining battery in %&#34;,&#34;name&#34;:&#34;battery&#34;,&#34;property&#34;:&#34;battery&#34;,&#34;type&#34;:&#34;numeric&#34;,&#34;unit&#34;:&#34;%&#34;,&#34;value_max&#34;:100,&#34;value_min&#34;:0},{&#34;access&#34;:1,&#34;description&#34;:&#34;Voltage of the battery in millivolts&#34;,&#34;name&#34;:&#34;voltage&#34;,&#34;property&#34;:&#34;voltage&#34;,&#34;type&#34;:&#34;numeric&#34;,&#34;unit&#34;:&#34;mV&#34;},{&#34;access&#34;:1,&#34;description&#34;:&#34;Link quality (signal strength)&#34;,&#34;name&#34;:&#34;linkquality&#34;,&#34;property&#34;:&#34;linkquality&#34;,&#34;type&#34;:&#34;numeric&#34;,&#34;unit&#34;:&#34;lqi&#34;,&#34;value_max&#34;:255,&#34;value_min&#34;:0}],&#34;model&#34;:&#34;SNZB-04&#34;,&#34;options&#34;:[],&#34;supports_ota&#34;:false,&#34;vendor&#34;:&#34;SONOFF&#34;},&#34;friendly_name&#34;:&#34;0x00124b0025003318&#34;,&#34;ieee_address&#34;:&#34;0x00124b0025003318&#34;,&#34;status&#34;:&#34;successful&#34;,&#34;supported&#34;:true},&#34;type&#34;:&#34;device_interview&#34;}&#39;
</code></pre><p>Si en el log aparece el mensaje <code>device_joined</code> será indicativo de que se ha emparejado correctamente.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="asignación-de-un-nombre-amigable">
        Asignación de un nombre amigable
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/iot-sonoff-sensor-contacto/#asignación-de-un-nombre-amigable" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Asignación de un nombre amigable" href="#asignaci%c3%b3n-de-un-nombre-amigable">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Para dar un nombre más amigable al dispositivo, paramos el servicio <strong>zigbee2mqtt</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo systemctl stop zigbee2mqtt
</span></span></code></pre></div><p>Editamos el archivo de configuración de <strong>zigbee2mqtt</strong>, que en este caso está en <code>/opt/zigbee2mqtt/data/configuration.yaml</code>.</p>
<p>Al final del archivo, en la sección <code>devices</code> nos aparecerá una entrada del tipo:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vim" data-lang="vim"><span class="line"><span class="cl">  <span class="s1">&#39;0x00124b0025003318&#39;</span>:
</span></span><span class="line"><span class="cl">    <span class="nx">friendly_name</span>: <span class="s1">&#39;0x00124b0025003318&#39;</span>
</span></span></code></pre></div><p>Cambiamos el valor del atributo <code>friendly_name</code> por otro que nos resulte más fácil de reconocer:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vim" data-lang="vim"><span class="line"><span class="cl">  <span class="s1">&#39;0x00124b0025003318&#39;</span>:
</span></span><span class="line"><span class="cl">    <span class="nx">friendly_name</span>: <span class="s1">&#39;sensor_apertura&#39;</span>
</span></span></code></pre></div><p>y arrancamos el servicio de nuevo para que se aplique el nuevo nombre:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo systemctl start zigbee2mqtt
</span></span></code></pre></div><p>Ahora debe publicar los eventos en el <strong>topic</strong> <code>zigbee2mqtt/sensor_apertura</code>.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="lectura-de-valores">
        Lectura de valores
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/iot-sonoff-sensor-contacto/#lectura-de-valores" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Lectura de valores" href="#lectura-de-valores">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Se trata de un dispositivo de bajo consumo, por lo que el sensor no está accesible la mayoría del tiempo, sólo cuando se activa porque detecta un cambio de estado de la apertura. Esto hace que las peticiones de lectura de valores no se procesen inmediatamente sino que sean encoladas y que el valor se reciba en la siguiente ocasión en la que el dispositivo está disponible, es decir, cuando hay un cambio de estado.</p>
<p>Para leer valores, nos suscribimos con <code>mosquitto_sub</code> al <strong>topic</strong> <code>zigbee2mqtt/sensor_apertura</code> en nuestro <strong>broker MQTT</strong>. Si lo hacemos desde dentro de la propia Raspberry pi (o el servidor donde se esté ejecutando <strong>mosquitto</strong>) utilizaremos el parámetro <code>-h localhost</code>. Si lo hacemos desde otro equipo, especificaremos la dirección IP del la máquina donde se ejecute <strong>mosquitto</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">mosquitto_sub -h localhost -p <span class="m">1883</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-u <span class="nv">$MQTTUSER</span> -P <span class="nv">$MQTTPASS</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-t <span class="s2">&#34;zigbee2mqtt/sensor_apertura&#34;</span>
</span></span></code></pre></div><p>Cuando la puerta o ventana está cerrada los dos módulos están alineados y recibimos el valor <code>true</code> en el parámetro <code>contact</code>.</p>
<pre tabindex="0"><code>{&#34;battery&#34;:50,&#34;battery_low&#34;:false,&#34;contact&#34;:true,&#34;linkquality&#34;:84,&#34;tamper&#34;:false,&#34;voltage&#34;:2800}
</code></pre><p>Cuando se abre los dos módulos dejan de estar alineados y se envía el valor <code>false</code> en ese mismo parámetro:</p>
<pre tabindex="0"><code>{&#34;battery&#34;:50,&#34;battery_low&#34;:false,&#34;contact&#34;:false,&#34;linkquality&#34;:96,&#34;tamper&#34;:false,&#34;voltage&#34;:2800}
</code></pre><p>Además del parámetro <code>contact</code> que es el que nos indica si la puerta o ventana está abierta o cerrada, el mensaje <em>JSON</em> muestra otros parámetros:</p>
<ul>
<li><strong>battery</strong>. Número que indica la batería restante en porcentaje.</li>
<li><strong>battery_low</strong>. Indica si la batería está a punto de acabarse, en cuyo caso contiene el valor <code>true</code>. Si la batería tiene un nivel aceptable contiene el valor <code>false</code>.</li>
<li><strong>linkquality</strong>. Fuerza de la señal. El valor mínimo es 0 y el máximo 255. La unidad de este valor es <strong>lqi</strong> (<em>Link Quality Indicator</em>).</li>
<li><strong>tamper</strong>. Indica si el dispositivo es manipulado. Algunos detectores incluyen un par de terminales llamados <strong>tamper</strong>, como sistema anti-sabotaje. Cuando alguien trata de abrir el dispositivo, la apertura se detecta y se envía una señal para avisar de ello. En este caso, se envía el valor <code>true</code> en este parámetro si el dispositivo es manipulado o <code>false</code> en caso contrario.</li>
<li><strong>voltage</strong>. Voltaje de la pila en milivoltios.</li>
</ul>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="resumen">
        Resumen
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/iot-sonoff-sensor-contacto/#resumen" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Resumen" href="#resumen">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Disponiendo de un <strong>gateway Zigbee</strong> y un <strong>broker MQTT</strong> se ha visto como emparejar un sensor de apertura de puertas y ventanas con conectividad <strong>Zigbee</strong>, en este caso el <a class="gblog-markdown__link" href="https://sonoff.tech/product-document/smart-home-security-doc/snzb-04-doc/">Sonoff SNZB-04</a>, y la forma de consultar su estado a través de la publicación y suscripción a <strong>topics</strong> <strong>MQTT</strong>. Esto permite detectar la apertura de puertas y ventanas y realizar acciones en consecuencia, como disparar una alarma, un aviso, encender o apagar una luz. En combinación con <a class="gblog-markdown__link" href="https://www.home-assistant.io/">Home Assistant</a> permitiría definir automatizaciones de forma sencilla.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="referencias">
        Referencias
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/iot-sonoff-sensor-contacto/#referencias" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Referencias" href="#referencias">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><ul>
<li><a class="gblog-markdown__link" href="https://www.zigbee2mqtt.io/devices/SNZB-04.html">https://www.zigbee2mqtt.io/devices/SNZB-04.html</a></li>
<li><a class="gblog-markdown__link" href="https://sonoff.tech/product-document/smart-home-security-doc/snzb-04-doc/">https://sonoff.tech/product-document/smart-home-security-doc/snzb-04-doc/</a></li>
</ul>]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://dvdcr.gitlab.io/tags/IoT" term="IoT" label="IoT" />
                            
                        
                    
                
            
        </entry>
    
        
        <entry>
            <title type="html"><![CDATA[Gestión de contraseñas desde la terminal]]></title>
            <link href="https://dvdcr.gitlab.io/posts/2022/03/linux-pass/?utm_source=atom_feed" rel="alternate" type="text/html"  hreflang="en" />
            <id>https://dvdcr.gitlab.io/posts/2022/03/linux-pass/</id>
            
            <published>2022-03-20T00:00:00+01:00</published>
            <updated>2022-03-20T00:00:00+01:00</updated>
            
            
            <content type="html"><![CDATA[<p><img src="/img/pass.svg#center" alt=""  /></p>
<p>La variedad de servicios a los que debemos acceder hoy en día con una contraseña obliga a utilizar contraseñas complejas y distintas para los diferentes servicios. Para gestionarlas de la mejor manera posible existen gran variedad de gestores de contraseñas, desde aquellos que funcionan como aplicaciones de escritorio como <a class="gblog-markdown__link" href="https://keepass.info">Keepass</a> y sus variantes, soluciones completas propietarias ofrecidas por empresas especializadas que nos proporcionan el almacenamiento remoto y acceso desde nuestro equipo a través de un complemento para el navegador o una aplicación móvil como ocurre con <a class="gblog-markdown__link" href="https://www.lastpass.com">Lastpass</a> y soluciones completas de código abierto que podemos utilizar de forma autohospedada o gestionada por sus creadores, como ocurre con <a class="gblog-markdown__link" href="https://bitwarden.com/">Bitwarden</a>.</p>
<p>Todas estas soluciones son muy completas y válidas para la mayor parte de los casos de uso. Pero también tienen su sitio herramientas con una base mucho más sencilla, accesibles desde todo tipo de clientes, incluso desde la terminal. Es el caso de <a class="gblog-markdown__link" href="https://www.passwordstore.org">pass</a>, una herramienta desarrollada con la sencillez como principio y cuyo uso a través de la terminal nos permite utilizarla por ejemplo en scripts en los que sea necesaria autenticación con contraseña pero no queramos exponerlas por descuido si nuestros scripts deben estar en un repositorio público de código, entre otros muchos usos.</p>
<p><strong>Pass</strong> es una aplicación desarrollada por Jason A. Donenfeld de <a class="gblog-markdown__link" href="https://zx2c4.com">zx2c4.com</a> con licencia GPLv2+. También es el creador de <a class="gblog-markdown__link" href="https://www.wireguard.com">wireguard</a>, entre otras herramientas.</p>
<p> </p>
<p><strong>Contenido</strong></p>



  <div class="gblog-toc gblog-toc__level--6"><nav id="TableOfContents">
  <ul>
    <li>
      <ul>
        <li><a href="#instalación">Instalación</a></li>
        <li><a href="#modo-de-almacenamiento">Modo de almacenamiento</a></li>
        <li><a href="#comando-pass">Comando pass</a></li>
        <li><a href="#iniciar-almacén">Iniciar almacén</a></li>
        <li><a href="#operaciones-frecuentes">Operaciones frecuentes</a>
          <ul>
            <li><a href="#guardar-una-contraseña">Guardar una contraseña</a></li>
            <li><a href="#mostrar-contenido-de-una-contraseña">Mostrar contenido de una contraseña</a></li>
            <li><a href="#copiar-contenido-de-una-contraseña-al-portapapeles">Copiar contenido de una contraseña al portapapeles</a></li>
            <li><a href="#generar-código-qr-con-el-contenido-de-una-contraseña">Generar código QR con el contenido de una contraseña</a></li>
            <li><a href="#generar-una-contraseña-aleatoria">Generar una contraseña aleatoria</a></li>
            <li><a href="#editar-una-contraseña">Editar una contraseña</a></li>
            <li><a href="#eliminar-una-contraseña">Eliminar una contraseña</a></li>
            <li><a href="#copiar-una-contraseña-a-otra-ruta-del-almacén">Copiar una contraseña a otra ruta del almacén</a></li>
            <li><a href="#mover-una-contraseña-a-otra-ruta-del-almacén">Mover una contraseña a otra ruta del almacén</a></li>
            <li><a href="#buscar-contraseñas-en-el-almacén">Buscar contraseñas en el almacén</a></li>
          </ul>
        </li>
        <li><a href="#utilización-de-un-almacén-por-múltiples-usuarios">Utilización de un almacén por múltiples usuarios</a></li>
        <li><a href="#utilización-de-pass-en-comandos-y-scripts">Utilización de pass en comandos y scripts</a></li>
        <li><a href="#uso-de-git">Uso de git</a></li>
        <li><a href="#exportar-la-base-de-datos-de-pass-a-otro-equipo">Exportar la base de datos de pass a otro equipo</a></li>
        <li><a href="#extensiones">Extensiones</a></li>
        <li><a href="#aplicaciones-cliente-compatibles">Aplicaciones cliente compatibles</a></li>
        <li><a href="#importar-claves-desde-otras-herramientas">Importar claves desde otras herramientas</a></li>
        <li><a href="#variables-de-entorno">Variables de entorno</a></li>
        <li><a href="#referencias">Referencias</a></li>
      </ul>
    </li>
  </ul>
</nav><hr></div>


<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="instalación">
        Instalación
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/linux-pass/#instalación" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Instalación" href="#instalaci%c3%b3n">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Se encuentra en los repositorios de las principales distribuciones de GNU/Linux, con lo que se puede instalar desde ellos o descargar el comprimido desde su <a class="gblog-markdown__link" href="https://git.zx2c4.com/password-store/">repositorio de git</a>. Se puede instalar en MacOS a través de <code>brew</code>.</p>
<p>Para <em>Debian</em>/<em>Ubuntu</em>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo apt-get install -y pass
</span></span></code></pre></div><p>Para <em>Fedora</em>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo dnf install -y pass
</span></span></code></pre></div><p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="modo-de-almacenamiento">
        Modo de almacenamiento
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/linux-pass/#modo-de-almacenamiento" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Modo de almacenamiento" href="#modo-de-almacenamiento">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>El esquema de almacenamiento es sumamente simple:</p>
<ul>
<li>Todas las contraseñas se almacenan dentro del directorio <code>${HOME}/.password-store</code>.</li>
<li>Cada contraseña se almacena dentro de un archivo encriptado con <a class="gblog-markdown__link" href="https://gnupg.org">gpg</a> cuyo nombre es el nombre del sitio web o recurso cuya contraseña se almacena.</li>
<li>Estos archivos encriptados se organizan en una jerarquía de directorios para clasificarlos.</li>
</ul>
<p>Este esquema permite que la exportación de los datos sea tan sencilla como la copia del contenido de este directorio. Simplemente tendremos que contar con la clave <strong>gpg</strong> en el lugar donde lo queramos utilizar.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="comando-pass">
        Comando pass
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/linux-pass/#comando-pass" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Comando pass" href="#comando-pass">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Toda la gestión se realiza con el comando <code>pass</code>, un script de shell con funciones de <strong>autocompletado</strong> para <em>bash</em>, <em>zsh</em> y <em>fish</em>. Escribiendo el comando y pulsando el tabulador dos veces nos irá sugiriendo los subcomandos y parámetros que admite.</p>
<p>Podemos comprobar la versión actualmente instalada con el subcomando <code>version</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass version
</span></span></code></pre></div><pre tabindex="0"><code>============================================
= pass: the standard unix password manager =
=                                          =
=                  v1.7.4                  =
=                                          =
=             Jason A. Donenfeld           =
=               Jason@zx2c4.com            =
=                                          =
=      http://www.passwordstore.org/       =
============================================
</code></pre><p>También podemos obtener ayuda con <code>pass help</code>.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="iniciar-almacén">
        Iniciar almacén
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/linux-pass/#iniciar-almacén" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Iniciar almacén" href="#iniciar-almac%c3%a9n">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Antes de poder utilizar <code>pass</code> debemos iniciar el almacén de las contraseñas. Para ello existe el subcomando <code>init</code> al que debemos indicar el identificador de una clave GPG. Se pueden identificar múltiples claves GPG si hay un equipo de personas que debe tener acceso al almacén.</p>
<p>Si no disponemos de una clave <em>gpg</em> en nuestro equipo debemos generarla con el comando <code>gpg --gen-key</code>, para crear una clave con las opciones por defecto ya que sólo nos preguntará un nombre y una dirección de correo electrónico y elegirá el algoritmo y caducidad de un año, o con el comando <code>gpg --full-generate-key</code>, que nos permitirá especificar todas las características (algoritmo de clave, longitud de clave, caducidad, identificador, etc).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">gpg --full-generate-key
</span></span></code></pre></div><pre tabindex="0"><code>gpg (GnuPG) 2.2.27; Copyright (C) 2021 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Por favor seleccione tipo de clave deseado:
   (1) RSA y RSA (por defecto)
   (2) DSA y ElGamal
   (3) DSA (sólo firmar)
   (4) RSA (sólo firmar)
  (14) Existing key from card
Su elección: 1
las claves RSA pueden tener entre 1024 y 4096 bits de longitud.
¿De qué tamaño quiere la clave? (3072) 4096
El tamaño requerido es de 4096 bits
Por favor, especifique el período de validez de la clave.
         0 = la clave nunca caduca
      &lt;n&gt;  = la clave caduca en n días
      &lt;n&gt;w = la clave caduca en n semanas
      &lt;n&gt;m = la clave caduca en n meses
      &lt;n&gt;y = la clave caduca en n años
¿Validez de la clave (0)? 1y
La clave caduca dom 05 mar 2023 07:56:37 CET
¿Es correcto? (s/n) s

GnuPG debe construir un ID de usuario para identificar su clave.

Nombre y apellidos: Tutorial Pass
Dirección de correo electrónico: tutorial.pass@dvdcr.com
Comentario: Usuario principal de Pass
Ha seleccionado este ID de usuario:
    &#34;Tutorial Pass (Usuario principal de Pass) &lt;tutorial.pass@dvdcr.com&gt;&#34;

¿Cambia (N)ombre, (C)omentario, (D)irección o (V)ale/(S)alir? V
</code></pre><p>En este punto un diálogo emergente nos pedirá una contraseña que tendremos que especificar dos veces.</p>
<p><img src="/img/linux-pass-gpg-password.jpg#center" alt=""  /></p>
<p>Y luego se recomienda trabajar con el ratón o en otras ventanas para generar mayor entropía y que haya suficientes valores aleatorios para la generación de la clave.</p>
<pre tabindex="0"><code>Es necesario generar muchos bytes aleatorios. Es una buena idea realizar
alguna otra tarea (trabajar en otra ventana/consola, mover el ratón, usar
la red y los discos) durante la generación de números primos. Esto da al
generador de números aleatorios mayor oportunidad de recoger suficiente
entropía.
gpg: clave 7BA4043CB83457F9026E38CA9348B9233410DF876 marcada como de confianza absoluta
gpg: certificado de revocación guardado como &#39;/home/usuario/.gnupg/openpgp-revocs.d/8DC10C2043C49D2A184B6083B441F579E8FD4B35.rev&#39;
claves pública y secreta creadas y firmadas.

pub   rsa4096 2022-03-05 [SC] [caduca: 2023-03-05]
      7BA4043CB83457F9026E38CA9348B9233410DF876
uid                      Tutorial Pass (Usuario principal de Pass) &lt;tutorial.pass@dvdcr.com&gt;
sub   rsa4096 2022-03-05 [E] [caduca: 2023-03-05]
</code></pre><p>Podemos obtener la lista de claves <em>gpg</em> disponibles en el equipo con <code>gpg --list-secret-keys</code>.</p>
<p>Una vez que tenemos al menos una clave <em>gpg</em> podemos inicializar el almacén con el comando <code>pass init </code> indicando el correo electrónico asociado a la clave gpg que queremos utilizar, el comentario o el identificador hexadecimal de la clave.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass init <span class="s2">&#34;Usuario principal de Pass&#34;</span>
</span></span></code></pre></div><pre tabindex="0"><code>mkdir: se ha creado el directorio &#39;/home/usuario/.password-store/&#39;
Password store initialized for Usuario principal de Pass
</code></pre><p>Ahora dispondremos del almacén de contraseñas en el directorio <code>~/.password-store</code>, con lo que se puede empezar a trabajar con <code>pass</code>.</p>
<p>Opcionalmente podemos inicializar el almacén de contraseñas como un repositorio de <strong>git</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass git init
</span></span></code></pre></div><pre tabindex="0"><code>ayuda: Usando &#39;master&#39; como el nombre de la rama inicial. Este nombre de rama predeterminado
ayuda: está sujeto a cambios. Para configurar el nombre de la rama inicial para usar en todos
ayuda: de sus nuevos repositorios, que suprimirán esta advertencia, llame a:
ayuda: 
ayuda: 	git config --global init.defaultBranch &lt;nombre&gt;
ayuda: 
ayuda: Los nombres comúnmente elegidos en lugar de &#39;maestro&#39; son &#39;principal&#39;, &#39;troncal&#39; y
ayuda: &#39;desarrollo&#39;. Se puede cambiar el nombre de la rama recién creada mediante este comando:
ayuda: 
ayuda: 	git branch -m &lt;nombre&gt;
Inicializado repositorio Git vacío en /home/usuario/.password-store/.git/
[master (commit-raíz) 5537e70] Add current contents of password store.
 1 file changed, 1 insertion(+)
 create mode 100644 .gpg-id
[master 4c94ae7] Configure git repository for gpg file diff.
 1 file changed, 1 insertion(+)
 create mode 100644 .gitattributes
</code></pre><p>Esto hará que cada vez que una contraseña sea manipulada se haga un <code>git commit</code>, lo que permitiría tener trazabilidad de los cambios y deshacerlos si es necesario.</p>
<p>Además, opcionalmente, podemos agregar un repositorio remoto para subir los cambios en él:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass git remote add origin https://migit.es:pass-store
</span></span></code></pre></div><p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="operaciones-frecuentes">
        Operaciones frecuentes
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/linux-pass/#operaciones-frecuentes" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Operaciones frecuentes" href="#operaciones-frecuentes">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><div class="gblog-post__anchorwrap">
    <h3 id="guardar-una-contraseña">
        Guardar una contraseña
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/linux-pass/#guardar-una-contraseña" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Guardar una contraseña" href="#guardar-una-contrase%c3%b1a">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h3>
</div><p>Aunque haremos referencia a &ldquo;contraseñas&rdquo;, realmente en una entrada de <code>pass</code> podemos almacenar cualquier tipo de información como identificadores de usuario, direcciones IP o direcciones de correo electrónico. Incluso es posible guardar en una misma entrada múltiples líneas y utilizar alguna sintaxis clave-valor con algún separador como el signo igual o dos puntos.</p>
<p>Para <strong>insertar una contraseña</strong> especificaremos la jerarquía donde queremos que se guarde y <code>pass</code> nos preguntará el valor dos veces sin mostrar lo que tecleamos por pantalla:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass insert bbdd/mysql/wordpress/admin
</span></span></code></pre></div><pre tabindex="0"><code>mkdir: se ha creado el directorio &#39;/home/user/.password-store/bbdd&#39;
mkdir: se ha creado el directorio &#39;/home/user/.password-store/bbdd/mysql&#39;
mkdir: se ha creado el directorio &#39;/home/user/.password-store/bbdd/mysql/wordpress&#39;
Enter password for bbdd/mysql/wordpress/admin: 
Retype password for bbdd/mysql/wordpress/admin: 
</code></pre><p>Para insertar una entrada que contenga múltiples líneas tendremos que utilizar el parámetro <code>-m</code>, teclear toda la información y pulsar <em>CTRL+D</em> cuando hayamos terminado:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass insert -m wordpress/blog/config
</span></span></code></pre></div><pre tabindex="0"><code>mkdir: se ha creado el directorio &#39;/home/user/.password-store/wordpress&#39;
mkdir: se ha creado el directorio &#39;/home/user/.password-store/wordpress/blog&#39;
Enter contents of wordpress/blog/config and press Ctrl+D when finished:

define(&#39;DB_NAME&#39;, &#39;nombredetubasededatos&#39;);
define(&#39;DB_USER&#39;, &#39;nombredeusuario&#39;);
define(&#39;DB_PASSWORD&#39;, &#39;contraseña&#39;);
define(&#39;DB_HOST&#39;, &#39;localhost&#39;);
define(&#39;DB_CHARSET&#39;, &#39;utf8&#39;);
define(&#39;DB_COLLATE&#39;, &#39;&#39;);
</code></pre><p> </p>
<div class="gblog-post__anchorwrap">
    <h3 id="mostrar-contenido-de-una-contraseña">
        Mostrar contenido de una contraseña
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/linux-pass/#mostrar-contenido-de-una-contraseña" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Mostrar contenido de una contraseña" href="#mostrar-contenido-de-una-contrase%c3%b1a">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h3>
</div><p>Para mostrar el contenido podemos utilizar indistintamente el comando <code>pass</code> sin ningún subcomando o con los subcomandos <code>ls</code> o <code>show</code>. Si no especificamos ninguna ruta, se mostrará el árbol completo de contraseñas del almacén:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass
</span></span></code></pre></div><pre tabindex="0"><code>Password Store
├── bbdd
│   └── mysql
│       └── wordpress
│           └── admin
└── wordpress
    └── blog
        └── config
</code></pre><p>Habríamos obtenido el mismo resultado exactamente con:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass ls
</span></span></code></pre></div><p>o</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass show
</span></span></code></pre></div><p>Si especificamos una ruta parcial que no incluya el elemento final de la ruta, <code>pass</code> mostrará sólo la porción del árbol que haya bajo la ruta que hemos escrito. No nos pedirá la contraseña de cifrado de la clave <em>gpg</em>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass bbdd
</span></span></code></pre></div><pre tabindex="0"><code>bbdd
└── mysql
    └── wordpress
        └── admin
</code></pre><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass bbdd/mysql
</span></span></code></pre></div><pre tabindex="0"><code>bbdd/mysql
└── wordpress
    └── admin
</code></pre><p>Si especificamos la ruta completa hasta una contraseña, nos mostrará la contraseña en claro:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass bbdd/mysql/wordpress/admin
</span></span></code></pre></div><p>Si es la primera vez en la sesión que solicitamos una contraseña o bien la caché de contraseñas de <em>gpg</em> ha caducado, nos aparecerá un diálogo solicitando la contraseña de la clave <em>gpg</em>:</p>
<p><img src="/img/linux-pass-ask-password.jpg#center" alt=""  /></p>
<p>Tras introducir la contraseña de la clave <em>gpg</em>, nos mostrará el valor de la contraseña cuya ruta hemos especificado. Durante el tiempo de caché que tenga definido <em>gpg</em> no nos volverá a solicitar la contraseña si realizamos más operaciones a continuación.</p>
<p>Debemos recordar también que <strong>disponemos de función de autocompletado</strong>, que nos ayuda a obtener la ruta sin teclearla completamente, ayudándonos de la tecla <em>Tab</em>. Si escribimos <code>pass bbdd</code> y pulsamos el tabulador nos autocompletará la ruta hasta el máximo posible en el que no haya ambigüedad. En este caso concreto escribirá toda la ruta <code>bbdd/mysql/wordpress/admin</code> hasta la clave final, puesto que sólo hay una contraseña y no hay ambigüedad.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h3 id="copiar-contenido-de-una-contraseña-al-portapapeles">
        Copiar contenido de una contraseña al portapapeles
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/linux-pass/#copiar-contenido-de-una-contraseña-al-portapapeles" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Copiar contenido de una contraseña al portapapeles" href="#copiar-contenido-de-una-contrase%c3%b1a-al-portapapeles">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h3>
</div><p>Para copiar el contenido de una contraseña al portapapeles, evitando que aparezca en la terminal, utilizaremos el parámetro <code>-c</code> o <code>--clip</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass -c bbdd/mysql/wordpress/admin
</span></span></code></pre></div><pre tabindex="0"><code>Copied bbdd/mysql/wordpress/admin to clipboard. Will clear in 45 seconds.
</code></pre><p>Por defecto permanecerá en el portapapeles durante 45 segundos, durante los cuales podremos pegarla en cualquier aplicación como un navegador web o una aplicación de terminal que nos esté solicitando una contraseña. El tiempo en el que permanecerá en el portapapeles se puede modificar si definimos un valor en la variable de entorno <code>PASSWORD_STORE_CLIP_TIME</code>. Vamos a ver cómo subirlo a 90 segundos temporalmente:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">PASSWORD_STORE_CLIP_TIME</span><span class="o">=</span><span class="m">90</span>
</span></span><span class="line"><span class="cl">pass -c wordpress/blog/config
</span></span></code></pre></div><pre tabindex="0"><code>Copied wordpress/blog/config to clipboard. Will clear in 90 seconds.
</code></pre><p>En el caso de contraseñas en las que hayamos almacenado varias filas, sólo se copiará una de ellas, por defecto la primera.</p>
<p>Vamos a ver un ejemplo de ello. Mostramos el contenido de una de las claves que hemos introducido previamente:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass wordpress/blog/config 
</span></span></code></pre></div><pre tabindex="0"><code>define(&#39;DB_NAME&#39;, &#39;nombredetubasededatos&#39;);
define(&#39;DB_USER&#39;, &#39;nombredeusuario&#39;);
define(&#39;DB_PASSWORD&#39;, &#39;contraseña&#39;);
define(&#39;DB_HOST&#39;, &#39;localhost&#39;);
define(&#39;DB_CHARSET&#39;, &#39;utf8&#39;);
define(&#39;DB_COLLATE&#39;, &#39;&#39;);
</code></pre><p>Copiamos el contenido:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass -c wordpress/blog/config
</span></span></code></pre></div><pre tabindex="0"><code>Copied wordpress/blog/config to clipboard. Will clear in 45 seconds.
</code></pre><p>Pegamos el contenido:</p>
<pre tabindex="0"><code>define(&#39;DB_NAME&#39;, &#39;nombredetubasededatos&#39;);
</code></pre><p>Siempre se copia una línea pero podemos precisar el número de línea en concreto especificándolo a continuación del parámetro <code>-c</code> o <code>--clip</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass -c2 wordpress/blog/config
</span></span><span class="line"><span class="cl">Copied wordpress/blog/config to clipboard. Will clear in <span class="m">45</span> seconds.
</span></span></code></pre></div><p>Pegamos el contenido y veremos que se ha seleccionado la línea 2:</p>
<pre tabindex="0"><code>define(&#39;DB_USER&#39;, &#39;nombredeusuario&#39;);
</code></pre><p> </p>
<div class="gblog-post__anchorwrap">
    <h3 id="generar-código-qr-con-el-contenido-de-una-contraseña">
        Generar código QR con el contenido de una contraseña
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/linux-pass/#generar-código-qr-con-el-contenido-de-una-contraseña" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Generar código QR con el contenido de una contraseña" href="#generar-c%c3%b3digo-qr-con-el-contenido-de-una-contrase%c3%b1a">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h3>
</div><p>De forma muy similar a la copia al portapapeles, pero utilizando el parámetro <code>-q</code> o <code>--qrcode</code>, <code>pass</code> nos generará un código <strong>QR</strong> con el contenido de una contraseña:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass -q bbdd/mysql/wordpress/admin
</span></span></code></pre></div><p>nos mostrará una ventana flotante con el código <strong>QR</strong>:</p>
<p><img src="/img/linux-pass-qrcode.png#center" alt=""  /></p>
<p><code>pass</code> hace uso de la herramienta <strong>qrencode</strong> para ello.</p>
<p>Tal y como ocurría con el copiado al portapapeles, <code>pass</code> sólo copia una línea en el caso de entradas multilínea, por lo que si queremos una linea en concreto deberemos indicarlo poniendo el número de línea a continuación del parámetro <code>-q</code> o <code>--qrcode</code>. Si queremos que se genere el código <strong>QR</strong> con el contenido de la tercera fila:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass -q3 wordpress/blog/config
</span></span></code></pre></div><p> </p>
<div class="gblog-post__anchorwrap">
    <h3 id="generar-una-contraseña-aleatoria">
        Generar una contraseña aleatoria
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/linux-pass/#generar-una-contraseña-aleatoria" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Generar una contraseña aleatoria" href="#generar-una-contrase%c3%b1a-aleatoria">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h3>
</div><p><code>pass</code> puede generar nuevas contraseñas de forma aleatoria, para lo que utiliza internamente <code>/dev/urandom</code>.</p>
<p>La forma más sencilla de aprovechar esta funcionalidad es utilizar el subcomando <code>generate</code> sin parámetros, lo que generará un clave que incluirá todo tipo de símbolos y con una longitud de 25 caracteres:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass generate api/geo/admin
</span></span></code></pre></div><pre tabindex="0"><code>mkdir: se ha creado el directorio &#39;/home/user/.password-store/api/geo&#39;
[master 8c03f82] Add generated password for api/geo/admin.
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 api/geo/admin.gpg
The generated password for api/geo/admin is:
\8Pq*F9,+.Z8VZF0MLe9%`IKt
</code></pre><p>Podemos indicar que la clave generada sólo contenga caracteres alfanuméricos si incluimos el parámetro <code>-n</code> o <code>--no-symbols</code> y limitar la longitud de la contraseña si especificamos al final del comando un número. En el siguiente ejemplo generaremos una contraseña con caracteres únicamente alfanuméricos que tendrá 12 caracteres:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass generate -n api/geo/developer <span class="m">12</span>
</span></span></code></pre></div><pre tabindex="0"><code>[master aa44eb2] Add generated password for api/geo/developer.
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 api/geo/developer.gpg
The generated password for api/geo/developer is:
FViQ3KvuIpY2
</code></pre><p>Si no queremos que la contraseña generada aparezca en pantalla al generarse, sino que además de almacenarse en su archivo correspondiente se copie en el portapapeles, podemos añadir el parámetro <code>--clip</code> o <code>-c</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass generate -n -c api/geo/developer2 <span class="m">15</span>
</span></span></code></pre></div><pre tabindex="0"><code>[master 3cec11d] Add generated password for api/geo/developer2.
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 api/geo/developer2.gpg
Copied api/geo/developer2 to clipboard. Will clear in 45 seconds.
</code></pre><p>Como variante de lo anterior, podemos indicar el parámetro <code>-q</code> o <code>-qrcode</code> para que además de almacenarse la clave en su archivo correspondiente se genere un código <strong>QR</strong> y la contraseña no salga por consola.</p>
<p>Si especificamos la ruta a una contraseña ya existente, nos preguntará si queremos sobreescribirla:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ pass generate -n -c api/geo/developer2 <span class="m">15</span>
</span></span></code></pre></div><pre tabindex="0"><code>An entry already exists for api/geo/developer2. Overwrite it? [y/N] y
[master f6f9663] Add generated password for api/geo/developer2.
 1 file changed, 0 insertions(+), 0 deletions(-)
 rewrite api/geo/developer2.gpg (100%)
Copied api/geo/developer2 to clipboard. Will clear in 45 seconds.
</code></pre><p>Si a la hora de actualizar una contraseña no deseamos que nos pida confirmación, podemos utilizar el parámetro <code>-f</code> o <code>--force</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass generate -n -c -f api/geo/developer2 <span class="m">15</span>
</span></span></code></pre></div><pre tabindex="0"><code>[master cfb4798] Add generated password for api/geo/developer2.
 1 file changed, 0 insertions(+), 0 deletions(-)
 rewrite api/geo/developer2.gpg (89%)
Copied api/geo/developer2 to clipboard. Will clear in 45 seconds.
</code></pre><p>El parámetro <code>-i</code> o <code>--in-place</code> permite especificar que en caso de archivos multilínea sólo se reemplace el contenido de la primera línea con la contraseña generada aleatoriamente.</p>
<p>Vamos a generar una clave multilínea:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass insert -m api/node/users
</span></span></code></pre></div><pre tabindex="0"><code>mkdir: se ha creado el directorio &#39;/home/user/.password-store/api/node&#39;
Enter contents of api/node/users and press Ctrl+D when finished:

user1:blablabla
user2:blebleble
user3:bliblibli                            
[master 392d80e] Add given password for api/node/users to store.
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 api/node/users.gpg
</code></pre><p>Generamos una contraseña con el parámetro <code>-i</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass generate -n -i api/node/users
</span></span></code></pre></div><pre tabindex="0"><code>[master 2837f6a] Replace generated password for api/node/users.
 1 file changed, 0 insertions(+), 0 deletions(-)
 rewrite api/node/users.gpg (100%)
The generated password for api/node/users is:
NijYp8yE19IT06A0uaG1FuG22
</code></pre><p>Comprobamos el resultado. Veremos que la primera línea ha sido reemplazada por una nueva contraseña generada al azar:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass api/node/users
</span></span></code></pre></div><pre tabindex="0"><code>NijYp8yE19IT06A0uaG1FuG22
user2:blebleble
user3:bliblibli
</code></pre><p>Si utilizamos el subcomando <code>generate</code> sin <code>-i</code>  sobre una clave multilínea existente sustituye completamente el contenido, quedando una sola línea:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass generate -n api/node/users
</span></span></code></pre></div><pre tabindex="0"><code>An entry already exists for api/node/users. Overwrite it? [y/N] y
[master 262061f] Add generated password for api/node/users.
 1 file changed, 0 insertions(+), 0 deletions(-)
 rewrite api/node/users.gpg (100%)
The generated password for api/node/users is:
ibIF3YBTAHrcjOlr61bO2TaV3
</code></pre><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass api/node/users
</span></span></code></pre></div><pre tabindex="0"><code>ibIF3YBTAHrcjOlr61bO2TaV3
</code></pre><p>Podemos cambiar la longitud predeterminada de las contraseñas generadas aleatoriamente creando la variable de entorno <code>PASSWORD_STORE_GENERATED_LENGTH</code> con el valor de la nueva longitud que queramos establecer:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">PASSWORD_STORE_GENERATED_LENGTH</span><span class="o">=</span><span class="m">8</span>
</span></span><span class="line"><span class="cl">pass generate api/meteo/token
</span></span></code></pre></div><pre tabindex="0"><code>mkdir: se ha creado el directorio &#39;/home/user/.password-store/api/meteo&#39;
[master e460e6e] Add generated password for api/meteo/token.
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 api/meteo/token.gpg
The generated password for api/meteo/token is:
zU-D+WAI
</code></pre><p>Podemos personalizar el conjunto de caracteres que utilizado para generar contraseñas aleatorias. Si lo que queremos personalizar es el conjunto utilizado cuando se especifica la opción <code>-n</code> o <code>--no-symbols</code> tendremos que crear la variable de entorno <code>PASSWORD_STORE_CHARACTER_SET_NO_SYMBOLS</code> con una cadena que contenga los caracteres permitidos:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">PASSWORD_STORE_CHARACTER_SET_NO_SYMBOLS</span><span class="o">=</span><span class="s2">&#34;ZzYyXxWxTtSsRr012&#34;</span>
</span></span><span class="line"><span class="cl">pass generate -f -n api/meteo/token
</span></span><span class="line"><span class="cl"><span class="o">[</span>master 208ecd5<span class="o">]</span> Add generated password <span class="k">for</span> api/meteo/token.
</span></span><span class="line"><span class="cl"> <span class="m">1</span> file changed, <span class="m">0</span> insertions<span class="o">(</span>+<span class="o">)</span>, <span class="m">0</span> deletions<span class="o">(</span>-<span class="o">)</span>
</span></span><span class="line"><span class="cl"> rewrite api/meteo/token.gpg <span class="o">(</span>100%<span class="o">)</span>
</span></span><span class="line"><span class="cl">The generated password <span class="k">for</span> api/meteo/token is:
</span></span><span class="line"><span class="cl">txT0Yt0x
</span></span></code></pre></div><p>Si lo que queremos personalizar es el conjunto utilizado cuando NO se especifica la opción <code>-n</code> o la opción <code>--no-symbols</code> tendremos que crear la variable de entorno <code>PASSWORD_STORE_CHARACTER_SET</code> con una cadena que contenga los caracteres permitidos:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">PASSWORD_STORE_CHARACTER_SET</span><span class="o">=</span><span class="s2">&#34;abcdefgABCDEFG56789_</span>$<span class="s2">=&amp;</span>$<span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">pass generate -f api/meteo/token
</span></span><span class="line"><span class="cl"><span class="o">[</span>master bf92ac1<span class="o">]</span> Add generated password <span class="k">for</span> api/meteo/token.
</span></span><span class="line"><span class="cl"> <span class="m">1</span> file changed, <span class="m">0</span> insertions<span class="o">(</span>+<span class="o">)</span>, <span class="m">0</span> deletions<span class="o">(</span>-<span class="o">)</span>
</span></span><span class="line"><span class="cl"> rewrite api/meteo/token.gpg <span class="o">(</span>100%<span class="o">)</span>
</span></span><span class="line"><span class="cl">The generated password <span class="k">for</span> api/meteo/token is:
</span></span><span class="line"><span class="cl">G<span class="nv">$e</span><span class="p">&amp;</span>cdf9
</span></span></code></pre></div><p> </p>
<div class="gblog-post__anchorwrap">
    <h3 id="editar-una-contraseña">
        Editar una contraseña
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/linux-pass/#editar-una-contraseña" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Editar una contraseña" href="#editar-una-contrase%c3%b1a">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h3>
</div><p>La <strong>modificación de una contraseña</strong> se puede realizar con los subcomandos <code>insert</code> o <code>generate</code> simplemente con el hecho de indicar la ruta de una contraseña existente. Si no queremos que se pida confirmación utilizaremos el parámetro <code>-f</code>  o <code>--force</code>.</p>
<p>Para <strong>modificar una contraseña de forma interactiva</strong> a través de un editor existe el subcomando <code>edit</code>. Cuando llamamos a <code>pass edit /ruta/a/clave</code> se nos abre un editor de texto. Esto es especialmente util para modificar claves multilínea.
El editor que se abre es el especificado en la variable de entorno <code>EDITOR</code>, que la mayoría de distribuciones tienen inicializada. Si no tiene valor, se utilizará <code>vi</code> por defecto.
La edición se realiza sobre un archivo creado en un directorio temporal, habitualmente <code>/dev/shm</code>. Este directorio en realidad no se encuentra en disco sino que es un montaje realizado en memoria virtual y que por lo tanto reduce el riesgo de recuperación de contraseñas almacenadas en disco físico. Si este sistema de archivos no estuviera disponible, se utilizaría el directorio temporal estándar y se emitiría un aviso.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h3 id="eliminar-una-contraseña">
        Eliminar una contraseña
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/linux-pass/#eliminar-una-contraseña" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Eliminar una contraseña" href="#eliminar-una-contrase%c3%b1a">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h3>
</div><p>La eliminación de una contraseña puede hacerse con los subcomandos <code>rm</code>, <code>remove</code> o <code>delete</code>. Se puede especificar el parámetro <code>-r</code> o <code>--recursive</code>  para eliminar toda una rama del árbol de contraseñas. Si queremos evitar que nos pida confirmación podemos utilizar los parámetros <code>-f</code>  o <code>--force</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"> pass api/meteo
</span></span></code></pre></div><pre tabindex="0"><code>api/meteo
└── token
</code></pre><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass rm -r -f api/meteo
</span></span></code></pre></div><pre tabindex="0"><code>&#39;/home/user/.password-store/api/meteo/token.gpg&#39; borrado
removed directory &#39;/home/user/.password-store/api/meteo/&#39;
[master 4b0b3fa] Remove api/meteo from store.
 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 api/meteo/token.gpg
</code></pre><p> </p>
<div class="gblog-post__anchorwrap">
    <h3 id="copiar-una-contraseña-a-otra-ruta-del-almacén">
        Copiar una contraseña a otra ruta del almacén
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/linux-pass/#copiar-una-contraseña-a-otra-ruta-del-almacén" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Copiar una contraseña a otra ruta del almacén" href="#copiar-una-contrase%c3%b1a-a-otra-ruta-del-almac%c3%a9n">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h3>
</div><p>Podemos copiar una contraseña o directorio completo a una nueva ruta con los subcomandos <code>copy</code> o <code>cp</code>. Pedirá confirmación para sobreescribir el destino si ya existe. Se puede omitir la confirmación si se especifica el parámetro <code>-f</code>  o <code>--force</code>.</p>
<p>Si la ruta destino termina en <code>/</code> se considerará un directorio.</p>
<p>Si la ruta destino está cifrada con una clave <em>pgp</em> diferente de la del origen, las contraseñas serán reencriptadas con la clave que le corresponde según su nueva ruta.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass wordpress
</span></span></code></pre></div><pre tabindex="0"><code>wordpress
└── blog
    └── config
</code></pre><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass cp wordpress/blog wordpress/ecommerce
</span></span></code></pre></div><pre tabindex="0"><code>/home/user/.password-store/wordpress/blog
&#39;/home/user/.password-store/wordpress/blog&#39; -&gt; &#39;/home/user/.password-store/wordpress/ecommerce&#39;
&#39;/home/user/.password-store/wordpress/blog/config.gpg&#39; -&gt; &#39;/home/user/.password-store/wordpress/ecommerce/config.gpg&#39;
[master 4999bd9] Copy wordpress/blog to wordpress/ecommerce.
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 wordpress/ecommerce/config.gpg
</code></pre><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass wordpress
</span></span></code></pre></div><pre tabindex="0"><code>wordpress
├── blog
│   └── config
└── ecommerce
    └── config
</code></pre><p> </p>
<div class="gblog-post__anchorwrap">
    <h3 id="mover-una-contraseña-a-otra-ruta-del-almacén">
        Mover una contraseña a otra ruta del almacén
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/linux-pass/#mover-una-contraseña-a-otra-ruta-del-almacén" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Mover una contraseña a otra ruta del almacén" href="#mover-una-contrase%c3%b1a-a-otra-ruta-del-almac%c3%a9n">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h3>
</div><p>Con los subcomandos <code>mv</code> o <code>rename</code> podemos cambiar el nombre o directorio donde se encuentra una contraseña. Podemos especificar los parámetros <code>-f</code> o <code>--force</code>  para evitar que nos pida confirmación antes de realizar la operación. Si la nueva ruta termina en <code>/</code> se considerará un directorio. Si se mueve una contraseña a un directorio que está encriptado con una clave diferente de la del directorio origen, las contraseñas serán reencriptadas para que se correspondan con la clave de su nuevo destino.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass wordpress
</span></span></code></pre></div><pre tabindex="0"><code>wordpress
├── blog
│   └── config
└── ecommerce
    └── config
</code></pre><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass mv -f wordpress/ecommerce wordpress/tienda
</span></span></code></pre></div><pre tabindex="0"><code>/home/user/.password-store/wordpress/ecommerce
renamed &#39;/home/user/.password-store/wordpress/ecommerce&#39; -&gt; &#39;/home/user/.password-store/wordpress/tienda&#39;
[master aebbaca] Rename wordpress/ecommerce to wordpress/tienda.
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename wordpress/{ecommerce =&gt; tienda}/config.gpg (100%)
</code></pre><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass wordpress
</span></span></code></pre></div><pre tabindex="0"><code>wordpress
├── blog
│   └── config
└── tienda
    └── config
</code></pre><p> </p>
<div class="gblog-post__anchorwrap">
    <h3 id="buscar-contraseñas-en-el-almacén">
        Buscar contraseñas en el almacén
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/linux-pass/#buscar-contraseñas-en-el-almacén" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Buscar contraseñas en el almacén" href="#buscar-contrase%c3%b1as-en-el-almac%c3%a9n">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h3>
</div><p>Los subcomandos <code>find</code> y <code>search</code> buscan coincidencias parciales en el <strong>nombre</strong> de una contraseña o en su <strong>ruta</strong>. Mostrará el árbol completo en el que se encuentren elementos que coincidan con la cadena de búsqueda.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass find wordpress
</span></span></code></pre></div><pre tabindex="0"><code>Search Terms: wordpress
├── bbdd
│   └── mysql
│       └── wordpress
│           └── admin
└── wordpress
    ├── blog
    │   └── config
    └── tienda
        └── config
</code></pre><p>El subcomando <code>grep</code> realiza la búsqueda por coincidencia parcial en el <strong>contenido</strong> de una contraseña. Mostrará la ruta hasta la contraseña o contraseñas encontradas y su contenido completo. La sintaxis es:</p>
<pre tabindex="0"><code>pass grep &lt;opciones&gt; &lt;cadena-de-búsqueda&gt;
</code></pre><p><code>&lt;opciones&gt;</code> serán parámetros admitidos por el comando <code>grep</code> y se pasarán tal cual a dicho comando para hacer la búsqueda. El siguiente comando realiza una búsqueda en el contenido ignorando mayúsculas y minúsculas:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass grep -i db_password
</span></span></code></pre></div><pre tabindex="0"><code>wordpress/blog/config:
define(&#39;DB_PASSWORD&#39;, &#39;contraseña&#39;);
wordpress/tienda/config:
define(&#39;DB_PASSWORD&#39;, &#39;contraseña&#39;);
</code></pre><p>En el siguiente comando no incluimos el parámetro <code>-i</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass grep db_password
</span></span></code></pre></div><p>No devuelve nada porque no encuentra ningún resultado ya que el contenido está en mayúsculas
y no hemos incluído el parámetro <code>-i</code>.</p>
<p>Si buscamos sin <code>-i</code>pero especificando la cadena de búsqueda en mayúsculas sí que encuentra resultados:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass grep DB_PASSWORD
</span></span></code></pre></div><pre tabindex="0"><code>wordpress/blog/config:
define(&#39;DB_PASSWORD&#39;, &#39;contraseña&#39;);
wordpress/tienda/config:
define(&#39;DB_PASSWORD&#39;, &#39;contraseña&#39;);
</code></pre><p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="utilización-de-un-almacén-por-múltiples-usuarios">
        Utilización de un almacén por múltiples usuarios
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/linux-pass/#utilización-de-un-almacén-por-múltiples-usuarios" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Utilización de un almacén por múltiples usuarios" href="#utilizaci%c3%b3n-de-un-almac%c3%a9n-por-m%c3%baltiples-usuarios">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Es posible la utilización de un almacén por múltiples usuarios, cada uno con su propia clave <em>gpg</em>. Podemos tener directorios de claves sobre los que tengan permisos varios usuarios o solamente alguno de ellos.</p>
<p>Vamos a partir de un equipo en el que tenemos tres claves:</p>
<pre tabindex="0"><code>gpg --list-keys
gpg: comprobando base de datos de confianza
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: nivel: 0  validez:   3  firmada:   0  confianza: 0-, 0q, 0n, 0m, 0f, 3u
gpg: siguiente comprobación de base de datos de confianza el: 2023-03-05
/home/user/.gnupg/pubring.kbx
-----------------------------
pub   rsa4096 2022-03-05 [SC] [caduca: 2023-03-05]
      8DC10C2043C49D2A184B6083B441F579E8FD4B35
uid        [  absoluta ] Tutorial Pass (Usuario principal de Pass) &lt;tutorial.pass@dvdcr.com&gt;
sub   rsa4096 2022-03-05 [E] [caduca: 2023-03-05]

pub   rsa3072 2022-03-12 [SC]
      481F470ACD42A060A43823EADF8598AD17670555
uid        [  absoluta ] Departamento contabilidad (contabilidad) &lt;contabilidad@dvdcr.com&gt;
sub   rsa3072 2022-03-12 [E]

pub   rsa3072 2022-03-12 [SC]
      8B84A215584C363F0DF905622E3766B165193EFD
uid        [  absoluta ] Departamento Auditoría (auditoria) &lt;auditoria@dvdcr.com&gt;
sub   rsa3072 2022-03-12 [E]
</code></pre><p>Vamos a inicializar un directorio dentro de nuestro almacén al que sólo tendrá acceso el departamento de contabilidad con su clave:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass init -p contabilidad/privado contabilidad
</span></span></code></pre></div><pre tabindex="0"><code>mkdir: se ha creado el directorio &#39;/home/user/.password-store/contabilidad&#39;
mkdir: se ha creado el directorio &#39;/home/user/.password-store/contabilidad/privado&#39;
Password store initialized for contabilidad (contabilidad/privado)
[master 49e7c31] Set GPG id to contabilidad (contabilidad/privado).
 1 file changed, 1 insertion(+)
 create mode 100644 contabilidad/privado/.gpg-id
</code></pre><p>Generamos una clave para el correo electrónico de contabilidad:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass generate -n contabilidad/privado/email
</span></span></code></pre></div><pre tabindex="0"><code>pass init -p contabilidad/privado contabilidad
mkdir: se ha creado el directorio &#39;/home/user/.password-store/contabilidad&#39;
mkdir: se ha creado el directorio &#39;/home/user/.password-store/contabilidad/privado&#39;
Password store initialized for contabilidad (contabilidad/privado)
[master 49e7c31] Set GPG id to contabilidad (contabilidad/privado).
 1 file changed, 1 insertion(+)
 create mode 100644 contabilidad/privado/.gpg-id
[dvdc@baracus ~]$ pass generate -n contabilidad/privado/email
[master 9162f15] Add generated password for contabilidad/privado/email.
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 contabilidad/privado/email.gpg
The generated password for contabilidad/privado/email is:
JJGtJAWZ2yeD5cbSS30WO2l9s
</code></pre><p>Intentamos copiar al portapapeles esa nueva clave:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass -c contabilidad/privado/email
</span></span></code></pre></div><p>Nos pedirá la contraseña de la clave con la que hemos cifrado la rama a la que pertenece esa entrada:</p>
<p><img src="/img/linux-pass-clave-contabilidad.png#center" alt=""  /></p>
<pre tabindex="0"><code>Copied contabilidad/privado/email to clipboard. Will clear in 45 seconds.
</code></pre><p>Vamos a inicializar un directorio dentro de nuestro almacén al que tendrán acceso tanto el departamento de contabilidad como el de auditoría con sus respectivas claves:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass init -p contabilidad/publico contabilidad auditoria
</span></span></code></pre></div><pre tabindex="0"><code>mkdir: se ha creado el directorio &#39;/home/user/.password-store/contabilidad/publico&#39;
Password store initialized for contabilidad, auditoria (contabilidad/publico)
[master 48f6c9e] Set GPG id to contabilidad, auditoria (contabilidad/publico).
 1 file changed, 2 insertions(+)
 create mode 100644 contabilidad/publico/.gpg-id
</code></pre><p>Generamos una nueva clave para la aplicación de facturación dentro de ese directorio:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass generate -n contabilidad/publico/facturacion
</span></span></code></pre></div><pre tabindex="0"><code>[master 518614c] Add generated password for contabilidad/publico/facturacion.
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 contabilidad/publico/facturacion.gpg
The generated password for contabilidad/publico/facturacion is:
jvAY6kjsvpSuzfgJiXHQvj12F
</code></pre><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass contabilidad
</span></span></code></pre></div><pre tabindex="0"><code>contabilidad
├── privado
│   └── email
└── publico
    └── facturacion
</code></pre><p>Intentamos copiarla al portapapeles.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass -c contabilidad/publico/facturacion
</span></span></code></pre></div><p>En nuestro caso, como en el mismo equipo constan tanto las claves de contabilidad como las de auditoría, nos pedirá en primer lugar la contraseña de una de ellas:</p>
<p><img src="/img/linux-pass-clave-contabilidad.png#center" alt=""  /></p>
<p>y si cancelamos nos preguntará la clave del departamento de auditoría:</p>
<p><img src="/img/linux-pass-clave-auditoria.png#center" alt=""  /></p>
<pre tabindex="0"><code>Copied contabilidad/publico/facturacion to clipboard. Will clear in 45 seconds.
</code></pre><p>Cualquiera de las dos claves privadas habría servido para consultar la contraseña de facturación.</p>
<p>Ahora veamos qué ocurre cuando copiamos una clave de un directorio asociado a una clave privada, como ocurre con <code>wordpress/tienda/config</code>, que estaba asociada al usuario principal y por lo tanto no era accesible por las claves de auditoría y contabilidad, a otro con una clave distinta, como es <code>contabilidad/publico</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass find contabilidad wordpress/tienda
</span></span></code></pre></div><pre tabindex="0"><code>Search Terms: contabilidad,wordpress/tienda
├── contabilidad
│   ├── privado
│   │   └── email
|   └── publico
│       └── facturacion
└── wordpress
    └── tienda
        └── config
</code></pre><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass mv wordpress/tienda contabilidad/publico
</span></span></code></pre></div><pre tabindex="0"><code>pass mv -f wordpress/tienda contabilidad/publico/
/home/user/.password-store/wordpress/tienda
renamed &#39;/home/user/.password-store/wordpress/tienda&#39; -&gt; &#39;/home/user/.password-store/contabilidad/publico/tienda&#39;
contabilidad/publico/tienda/config: reencrypting to 71AC1CC8EF0578B4 B9E9C8615602D819
[master 206ff99] Rename wordpress/tienda to contabilidad/publico/.
 2 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 contabilidad/publico/tienda/config.gpg
 delete mode 100644 wordpress/tienda/config.gpg
</code></pre><p>Vemos en el mensaje que la clave ha sido reencriptada (<code>reencrypting to 71AC1CC8EF0578B4 B9E9C8615602D819</code>).</p>
<p>La estructura de claves ha quedado así:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass find contabilidad wordpress/
</span></span></code></pre></div><pre tabindex="0"><code>Search Terms: contabilidad,wordpress/
├── contabilidad
│   ├── privado
│   │   └── email
│   └── publico
│       ├── facturacion
│       └── tienda
│           └── config
└── wordpress
    └── blog
        └── config
</code></pre><p>Tratamos de acceder a la clave de la tienda:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass -c contabilidad/publico/tienda/config
</span></span></code></pre></div><pre tabindex="0"><code>Copied contabilidad/publico/tienda/config to clipboard. Will clear in 45 seconds.
</code></pre><p>Vemos que hay acceso a ella simplemente porque tenemos cacheada una de las dos claves. Si hubiera pasado un tiempo y ya no estuviera en caché, nos habría pedido la clave de auditoría o contabilidad.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="utilización-de-pass-en-comandos-y-scripts">
        Utilización de pass en comandos y scripts
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/linux-pass/#utilización-de-pass-en-comandos-y-scripts" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Utilización de pass en comandos y scripts" href="#utilizaci%c3%b3n-de-pass-en-comandos-y-scripts">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Puede que queramos que ciertas contraseñas no queden en el historial de comandos ejecutados. También puede que esos comandos formen parte de un script que va a estar en un repositorio Git público y no queremos que esté expuesto el usuario ni la contraseña.</p>
<p>Un ejemplo de ello puede ser una llamada a una API que requiere usuario y contraseña:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">curl -u admin:clave-secret -X GET http://localhost:9200/_cluster/health
</span></span></code></pre></div><p>Una posible solución a este caso es utilizar variables de entorno o bien utilizar directamente una llamada al comando <code>pass</code>.</p>
<p>Primero almacenamos el usuario y la clave:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass insert api/elastic/user
</span></span></code></pre></div><pre tabindex="0"><code>mkdir: se ha creado el directorio &#39;/home/user/.password-store/api&#39;
mkdir: se ha creado el directorio &#39;/home/user/.password-store/api/elastic&#39;
Enter password for api/elastic/user: 
Retype password for api/elastic/user: 
[master 413ac44] Add given password for api/elastic/user to store.
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 api/elastic/user.gpg
</code></pre><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass insert api/elastic/password
</span></span></code></pre></div><pre tabindex="0"><code>Enter password for api/elastic/password: 
Retype password for api/elastic/password: 
[master becd064] Add given password for api/elastic/password to store.
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 api/elastic/password.gpg
</code></pre><p>Ahora podemos utilizar el comando pass correspondiente encerrado entre <code>$()</code> para sustituir el usuario y contraseña:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">curl -u <span class="k">$(</span>pass api/elastic/user<span class="k">)</span>:<span class="k">$(</span>pass api/elastic/password<span class="k">)</span> -X GET http://localhost:9200/_cluster/health
</span></span></code></pre></div><p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="uso-de-git">
        Uso de git
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/linux-pass/#uso-de-git" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Uso de git" href="#uso-de-git">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Como se ha especificado en el apartado de inicialización de una almacén de claves, es posible inicializar un almacén de claves como repositorio git:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass git init
</span></span></code></pre></div><p>Esto hace que cualquier operación sobre el almacén quede registrada en un commit:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass generate -f -n api/geo/developer2
</span></span></code></pre></div><pre tabindex="0"><code>pass git log
</code></pre><pre tabindex="0"><code>commit 56a1ff865436d81bfb5e9b9cb8c280c8339b0132 (HEAD -&gt; master)
Author: dvdcr &lt;dvdcr@dvdcr.com&gt;
Date:   Sat Mar 12 06:41:19 2022 +0100

    Add generated password for api/geo/developer2.

commit 682642ef6a5e932e1018a1290df4dbdd31b2115b
Author: dvdcr &lt;dvdcr@dvdcr.com&gt;
Date:   Sat Mar 12 06:40:10 2022 +0100

    Add current contents of password store.
</code></pre><p>Podemos vincular un repositorio remoto:</p>
<pre tabindex="0"><code>pass git remote add origin https://gitlab.com/dvdcr/pass-store
</code></pre><p>Realizamos una operación adicional:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass generate -n -f api/geo/developer3
</span></span></code></pre></div><pre tabindex="0"><code>1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 api/geo/developer3.gpg
The generated password for api/geo/developer3 is:
5jzrsabiM7YanVZp4iIu9pLJO
</code></pre><p>Vemos que aparece la última operación en el historial local:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass git log
</span></span></code></pre></div><pre tabindex="0"><code>commit 3b2c2dbc090787b4b01379ad3fec680617cb59b7 (HEAD -&gt; master)
Author: dvdcr &lt;dvdcr@dvdcr.com&gt;
Date:   Sat Mar 12 06:45:58 2022 +0100

    Add generated password for api/geo/developer3.

commit 56a1ff865436d81bfb5e9b9cb8c280c8339b0132
Author: dvdcr &lt;dvdcr@dvdcr.com&gt;
Date:   Sat Mar 12 06:41:19 2022 +0100

    Add generated password for api/geo/developer2.

commit 682642ef6a5e932e1018a1290df4dbdd31b2115b
Author: dvdcr &lt;dvdcr@dvdcr.com&gt;
Date:   Sat Mar 12 06:40:10 2022 +0100

    Add current contents of password store.
</code></pre><p>Pero además podemos subir los cambios al repositorio remoto y tener un backup de una forma rápida y sencilla (siempre que confiemos en el repositorio remoto).
Podremos utilizar cualquier comando de <code>git</code>, en este caso un <code>push</code>. Como acabamos de vincular el repositorio y no existe la rama en el repositorio remoto,
tendremos que utilizar el parámetro <code>-u</code>, pero las siguientes veces no será necesario:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass git push -u --all
</span></span></code></pre></div><pre tabindex="0"><code>Enumerando objetos: 40, listo.
Contando objetos: 100% (40/40), listo.
Compresión delta usando hasta 4 hilos
Comprimiendo objetos: 100% (29/29), listo.
Escribiendo objetos: 100% (40/40), 10.54 KiB | 1.32 MiB/s, listo.
Total 40 (delta 4), reusados 0 (delta 0), pack-reusados 0
To https://gitlab.com/dvdcr/pass-store
 * [new branch]      master -&gt; master
Rama &#39;master&#39; configurada para hacer seguimiento a la rama remota &#39;master&#39; de &#39;origin&#39;.
</code></pre><p>Podemos comprobar a través de la web del repositorio que se ha subido el contenido del directorio que contiene las claves:</p>
<p><img src="/img/linux-pass-gitlab.png#center" alt=""  /></p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="exportar-la-base-de-datos-de-pass-a-otro-equipo">
        Exportar la base de datos de pass a otro equipo
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/linux-pass/#exportar-la-base-de-datos-de-pass-a-otro-equipo" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Exportar la base de datos de pass a otro equipo" href="#exportar-la-base-de-datos-de-pass-a-otro-equipo">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>El proceso de exportación del almacén de claves para su utilización en otro equipo es sencillo.</p>
<p>Por un lado está el contenido del almacén. Basta con copiarnos todo el contenido de <code>~/.password-store</code> (o la ruta personalizada si no utilizamos la ruta por defecto) al otro equipo en esa misma ruta. La forma de copiarla es indiferente. Podemos copiar directamente con <a class="gblog-markdown__link" href="https://man7.org/linux/man-pages/man1/scp.1.html">scp</a> si tenemos acceso por <em>ssh</em> a la máquina destino o bien utilizar un pendrive o recurso compartido intermedio.</p>
<p>Ejemplo de copia del almacén con <code>scp</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">scp -r ~/.password-store/ usuario@192.168.1.10:/home/user/
</span></span></code></pre></div><p>Si en ordenador destino intentamos obtener una de las contraseñas nos informará de que no tenemos una clave de descifrado apta para este almacén:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass -c api/elastic/password
</span></span></code></pre></div><pre tabindex="0"><code>gpg: descifrado fallido: No tenemos la clave secreta 
</code></pre><p>Para poder acceder al almacén debemos disponer de la clave o claves <em>gpg</em> en el equipo destino. Para ello comprobamos en el equipo origen la lista de claves:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">gpg --list-keys
</span></span></code></pre></div><pre tabindex="0"><code>home/dvdc/.gnupg/pubring.kbx
-----------------------------
pub   rsa4096 2022-03-05 [SC] [caduca: 2023-03-05]
      8DC10C2043C49D2A184B6083B441F579E8FD4B35
uid        [  absoluta ] Tutorial Pass (Usuario principal de Pass) &lt;tutorial.pass@dvdcr.com&gt;
sub   rsa4096 2022-03-05 [E] [caduca: 2023-03-05]

pub   rsa3072 2022-03-12 [SC]
      481F470ACD42A060A43823EADF8598AD17670555
uid        [  absoluta ] Departamento contabilidad (contabilidad) &lt;contabilidad@dvdcr.com&gt;
sub   rsa3072 2022-03-12 [E]

pub   rsa3072 2022-03-12 [SC]
      8B84A215584C363F0DF905622E3766B165193EFD
uid        [  absoluta ] Departamento Auditoría (auditoria) &lt;auditoria@dvdcr.com&gt;
sub   rsa3072 2022-03-12 [E]
</code></pre><p>Hacemos la exportación de la clave privada y pública de cada usuario, utilizando como identificador el correo o hash. Nos pedirá la contraseña de cada una de las claves privadas:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nv">ID_KEY</span><span class="o">=</span>tutorial.pass@dvdcr.com
</span></span><span class="line"><span class="cl">gpg --output tutorial_public.gpg --armor --export <span class="nv">$ID_KEY</span>
</span></span><span class="line"><span class="cl">gpg --output tutorial_private.gpg --armor --export-secret-key <span class="nv">$ID_KEY</span>
</span></span><span class="line"><span class="cl"><span class="nv">ID_KEY</span><span class="o">=</span>contabilidad@dvdcr.com
</span></span><span class="line"><span class="cl">gpg --output contabilidad_public.gpg --armor --export <span class="nv">$ID_KEY</span>
</span></span><span class="line"><span class="cl">gpg --output contabilidad_private.gpg --armor --export-secret-key <span class="nv">$ID_KEY</span>
</span></span><span class="line"><span class="cl"><span class="nv">ID_KEY</span><span class="o">=</span>auditoria@dvdcr.com
</span></span><span class="line"><span class="cl">gpg --output auditoria_public.gpg --armor --export <span class="nv">$ID_KEY</span>
</span></span><span class="line"><span class="cl">gpg --output auditoria_private.gpg --armor --export-secret-key <span class="nv">$ID_KEY</span>
</span></span></code></pre></div><p>El resultado es el siguiente:</p>
<pre tabindex="0"><code>ls -l
total 36
-rw-------. 1 dvdc 5229 mar 12 07:08 auditoria_private.gpg
-rw-rw-r--. 1 dvdc 2472 mar 12 07:08 auditoria_public.gpg
-rw-------. 1 dvdc 5237 mar 12 07:08 contabilidad_private.gpg
-rw-rw-r--. 1 dvdc 2480 mar 12 07:08 contabilidad_public.gpg
-rw-------. 1 dvdc 6817 mar 12 07:08 tutorial_private.gpg
-rw-rw-r--. 1 dvdc 3195 mar 12 07:08 tutorial_public.gpg
</code></pre><p>Copiamos al ordenador destino las claves por cualquiera de los métodos conocidos. Ejemplo de copia con <code>scp</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">ssh usuario@192.168.1.10 mkdir /home/user/gpg-keys
</span></span><span class="line"><span class="cl">scp *.gpg usuario@192.168.1.10:/home/user/gpg-keys/
</span></span></code></pre></div><p>y las importamos en el ordenador destino con el comando <code>gpg --import</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">gpg --import *_public.gpg
</span></span></code></pre></div><pre tabindex="0"><code>gpg: /home/alumno/.gnupg/trustdb.gpg: se ha creado base de datos de confianza
gpg: clave 2E3766B165193EFD: clave pública &#34;Departamento Auditoría (auditoria) &lt;auditoria@dvdcr.com&gt;&#34; importada
gpg: clave DF8598AD17670555: clave pública &#34;Departamento contabilidad (contabilidad) &lt;contabilidad@dvdcr.com&gt;&#34; importada
gpg: clave B441F579E8FD4B35: clave pública &#34;Tutorial Pass (Usuario principal de Pass) &lt;tutorial.pass@dvdcr.com&gt;&#34; importada
gpg: Cantidad total procesada: 3
gpg:               importadas: 3
</code></pre><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">gpg --allow-secret-key-import --import *_private.gpg
</span></span></code></pre></div><p>Nos pedirá la contraseña para cada una de las claves privadas en un diálogo que será en modo gráfico o texto dependiendo del equipo destino. Aquí un ejemplo de un diálogo en modo texto:</p>
<p><img src="/img/linux-pass-ask-passwd-terminal.png#center" alt=""  /></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">gpg --allow-secret-key-import --import *_private.gpg
</span></span></code></pre></div><pre tabindex="0"><code>gpg: clave 2E3766B165193EFD: &#34;Departamento Auditoría (auditoria) &lt;auditoria@dvdcr.com&gt;&#34; sin cambios
gpg: clave 2E3766B165193EFD: clave secreta importada
gpg: clave DF8598AD17670555: &#34;Departamento contabilidad (contabilidad) &lt;contabilidad@dvdcr.com&gt;&#34; sin cambios
gpg: clave DF8598AD17670555: clave secreta importada
gpg: clave B441F579E8FD4B35: &#34;Tutorial Pass (Usuario principal de Pass) &lt;tutorial.pass@dvdcr.com&gt;&#34; sin cambios
gpg: clave B441F579E8FD4B35: clave secreta importada
gpg: Cantidad total procesada: 3
gpg:              sin cambios: 3
gpg:       claves secretas leídas: 3
gpg:   claves secretas importadas: 3
gpg:  claves secretas sin cambios: 1
</code></pre><p>Después de la importación de las claves será posible acceder a las contraseñas.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="extensiones">
        Extensiones
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/linux-pass/#extensiones" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Extensiones" href="#extensiones">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p><code>pass</code> admite extensiones para soportar una mayor variedad de casos de uso. Las extensiones que se instalan en la ruta <code>/usr/lib/password-store/extensions</code> están siempre habilitadas. Las extensiones instaladas en <code>~/.password-store/.extensions/</code> estáh habilitadas si la variable de entorno <code>PASSWORD_STORE_ENABLE_EXTENSIONS</code> tiene valor <code>true</code>.</p>
<p>Las extensiones son realizadas por la comunidad. Puede consultarse una lista de ellas en <a class="gblog-markdown__link" href="https://www.passwordstore.org/#extensions">https://www.passwordstore.org/#extensions</a>.</p>
<p>Vamos a probar una extensión para importar claves desde múltiples gestores de contraseñas, concretamente la extensión <code>pass-import</code>, que podemos encontrar en <a class="gblog-markdown__link" href="https://github.com/roddhjav/pass-import/">https://github.com/roddhjav/pass-import/</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">git clone https://github.com/roddhjav/pass-import/
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> pass-import
</span></span><span class="line"><span class="cl">sudo python3 setup.py install
</span></span><span class="line"><span class="cl">sudo pip3 install pykeepass
</span></span></code></pre></div><p>Una vez instalada la exensión podemos instalar por ejemplo las contraseñas almacenadas en un almacén de <em>Keepass</em>. Dependiendo del tipo de almacén origen puede que requiera instalar algún componente más, pero si es necesario nos informará de ello al intentar utilizarla.</p>
<p>El uso general de la herramienta utiliza la sintaxis:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass import ruta/al/almacen
</span></span></code></pre></div><p>Si utilizamos esa sintaxis, la extensión intentará detectar el formato del almacén del que queremos importar las contraseñas. Si no lo detecta o hay ambigüedad y queremos especificarlo, podemos utilizar la sintaxis:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass import gestor_contraseñas ruta/al/almacen
</span></span></code></pre></div><p>Ejemplo de importación de un almacén de <em>Keepassxc</em>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pass import keepassxc almacen.kdbx
</span></span></code></pre></div><pre tabindex="0"><code>Password for almacen.kdbx: 
 (*) Importing passwords from keepassxc to pass
  .  Passwords imported from: almacen.kdbx
  .  Passwords exported to: /home/user/.password-store
  .  Number of password imported: 4
  .  Passwords imported:
	compras/aliexpress
	compras/amazon
	correo/gmail
	correo/outlook
</code></pre><p>Al instalar la expensión se incluye también el comando <code>pimport</code> que permite exportar un almacén de un tipo determinado a otro que no sea necesariamente <code>pass</code>. La sintaxis en este caso es:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pimport gestor_contraseñas_destino gestor_contraseñas_origen ruta/a/contraseñas/origen <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--out ruta/a/contraseñas/destino
</span></span></code></pre></div><p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="aplicaciones-cliente-compatibles">
        Aplicaciones cliente compatibles
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/linux-pass/#aplicaciones-cliente-compatibles" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Aplicaciones cliente compatibles" href="#aplicaciones-cliente-compatibles">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Además del comando <code>pass</code> existen varias aplicaciones cliente compatibles con los almacenes de claves de <code>pass</code> que han sido creadas por otros programadores para diferentes sistemas y/o herramientas. Existen plugins de navegador para <em>Chrome</em> y <em>Firefox</em>, aplicaciones móviles para <em>Android</em> e <em>iOS</em>, paquetes para emacs, aplicaciones de escritorio para <em>Windows</em>, <em>Linux</em> y <em>Mac</em>, etc.</p>
<p>Podemos encontrar una lista de aplicaciones en <a class="gblog-markdown__link" href="https://www.passwordstore.org/#other">https://www.passwordstore.org/#other</a></p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="importar-claves-desde-otras-herramientas">
        Importar claves desde otras herramientas
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/linux-pass/#importar-claves-desde-otras-herramientas" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Importar claves desde otras herramientas" href="#importar-claves-desde-otras-herramientas">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Existen herramientas creadas por usuarios de <code>pass</code> que permiten importar datos desde otros gestores de contraseñas como <em>One Password</em>, <em>Keepass</em>, <em>Lastpass</em>, <em>Kwallet</em> o <em>Firefox</em>, entre otros.</p>
<p>La lista completa de herramientas y el enlace a su descarga está en está en <a class="gblog-markdown__link" href="https://www.passwordstore.org#migration">https://www.passwordstore.org#migration</a>.</p>
<p>Como ejemplo de importación de datos procedentes de <code>Keepass</code> a partir de un archivo de exportación <em>CSV</em> podemos descargar el script de importación correspondiente:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">curl https://git.zx2c4.com/password-store/plain/contrib/importers/keepass2csv2pass.py <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-o ~/bin/keepass2csv2pass.py
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">chmod +x ~/bin/keepass2csv2pass.py
</span></span></code></pre></div><p>Exportamos un almacén de <code>Keepass</code> a formato CSV con el menú <code>Base de datos</code> &gt; <code>Exportar</code> &gt; <code>Archivo CSV...</code></p>
<p>y lo importamos con el script:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">./keepass2csv2pass.py archivo.csv
</span></span></code></pre></div><p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="variables-de-entorno">
        Variables de entorno
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/linux-pass/#variables-de-entorno" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Variables de entorno" href="#variables-de-entorno">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Mediante la definición de variables de entorno, se pueden configurar ciertos aspectos del comportamiento de <code>pass</code>.</p>
<table>
  <thead>
      <tr>
          <th>Variable</th>
          <th>Utilidad</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>PASSWORD_STORE_DIR</td>
          <td>Permite especificar un directorio de almacenamiento de claves diferente del predeterminado (<code>~/.password-store</code>)</td>
      </tr>
      <tr>
          <td>PASSWORD_STORE_KEY</td>
          <td>Sobreescribe la clave de <em>gpg</em> establecida por defecto para la identificación al ejecutar el comando <code>pass init</code>. Los nombres de las claves no deben contener espacios y por ello se recomienda utilizar la firma hexadecimal. Se pueden especificar múltiples claves separadas por espacios.</td>
      </tr>
      <tr>
          <td>PASSWORD_STORE_GPG_OPTS</td>
          <td>Opciones adicionales que se pasarán a todas las llamadas que se hacen a <em>gpg</em>.</td>
      </tr>
      <tr>
          <td>PASSWORD_STORE_X_SELECTION</td>
          <td>Sobreescribe la selección que se pasa al comando <code>xclip</code>, por defecto el portapapeles.</td>
      </tr>
      <tr>
          <td>PASSWORD_STORE_CLIP_TIME</td>
          <td>Especifica el número de segundos que permanece una clave copiada en el portapepeles antes de restaurar su contenido previo. Por defecto 45 segundos.</td>
      </tr>
      <tr>
          <td>PASSWORD_STORE_UMASK</td>
          <td>Establece el enmascaramiento (<code>umask</code>)  de todos los archivos que sean modificados por <code>pass</code>, por defecto 077.</td>
      </tr>
      <tr>
          <td>PASSWORD_STORE_GENERATED_LENGTH</td>
          <td>Longitud predeterminada de las contraseñas creadas con el comando <code>pass generate</code> si no se especifica una longitud..</td>
      </tr>
      <tr>
          <td>PASSWORD_STORE_CHARACTER_SET</td>
          <td>Conjunto de caracteres utilzados por el comando <code>generate</code> para crear una nueva contraseña cuando no se utiliza el parámetro <code>-n</code> o <code>--no-symbols</code>. El valor será interpretado por <code>tr</code>.</td>
      </tr>
      <tr>
          <td>PASSWORD_STORE_CHARACTER_SET_NO_SYMBOLS</td>
          <td>Conjunto de caracteres utilizados por el comando <code>generate</code> para crear una nueva contraseña cuando se utiliza el parámetro <code>-n</code> o <code>--no-symbols</code>. El valor será interpretado por <code>tr</code>.</td>
      </tr>
      <tr>
          <td>PASSWORD_STORE_ENABLE_EXTENSIONS</td>
          <td>Esta variable de entorno debe tener el valor <code>true</code> para que se habiliten las extensiones.</td>
      </tr>
      <tr>
          <td>PASSWORD_STORE_EXTENSIONS_DIR</td>
          <td>Ubicación donde se buscarán los archivos ejecutables de las extensiones, por defecto <code>${PASSWORD_STORE_DIR}/.extensions</code>.</td>
      </tr>
      <tr>
          <td>PASSWORD_STORE_SIGNING_KEY</td>
          <td>Si se establece esta variable de entorno, todos los archivos <code>.gpg-id</code>y todos los archivos  de extensiones deben estar firmados utilizando una firma separada que utilice la clave GPG especificada por la huella completa de 40 caracteres en mayúsculas que contenga esta variable. Si se especifican varias huellas, cada una de ellas separada por espacios en blanco, las firmas deben coincidir con al menos una de ellas. El comando <code>ìnit</code> mantendrá las firmas de los archivos <code>.gpg-id</code> actualizadas.</td>
      </tr>
      <tr>
          <td>EDITOR</td>
          <td>Ubicación del ejecutable del editor de texto que aparece cuando ejecutamos <code>pass edit</code>.</td>
      </tr>
  </tbody>
</table>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="referencias">
        Referencias
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/linux-pass/#referencias" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Referencias" href="#referencias">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><ul>
<li><a class="gblog-markdown__link" href="https://www.passwordstore.org/">https://www.passwordstore.org/</a></li>
<li><a class="gblog-markdown__link" href="https://git.zx2c4.com/password-store/about/">https://git.zx2c4.com/password-store/about/</a></li>
<li><a class="gblog-markdown__link" href="https://github.com/roddhjav/pass-import#readme">https://github.com/roddhjav/pass-import#readme</a></li>
</ul>]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://dvdcr.gitlab.io/tags/linux" term="linux" label="linux" />
                            
                        
                    
                
            
        </entry>
    
        
        <entry>
            <title type="html"><![CDATA[Añadir tipos de letra]]></title>
            <link href="https://dvdcr.gitlab.io/posts/2022/03/add-ttf/?utm_source=atom_feed" rel="alternate" type="text/html"  hreflang="en" />
            <id>https://dvdcr.gitlab.io/posts/2022/03/add-ttf/</id>
            
            <published>2022-03-13T00:00:00+01:00</published>
            <updated>2022-03-21T00:00:00+01:00</updated>
            
            
            <content type="html"><![CDATA[<p><img src="/img/fuentes.svg#center" alt=""  /></p>
<p>Los tipos de letra que vienen de serie en un entorno de escritorio GNU/Linux son bastante limitados, pero podemos agregar nuevos tipos de letra a nuestro sistema para que sean utilizadas por las aplicaciones ofimáticas o de diseño.</p>
<p>Pueden agregarse para un único usuario en concreto o para que estén disponibles para todos los usuarios del sistema.</p>
<div class="gblog-post__anchorwrap">
    <h2 id="añadir-tipos-de-letra-para-todos-los-usuarios-del-sistema">
        Añadir tipos de letra para todos los usuarios del sistema
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/add-ttf/#añadir-tipos-de-letra-para-todos-los-usuarios-del-sistema" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Añadir tipos de letra para todos los usuarios del sistema" href="#a%c3%b1adir-tipos-de-letra-para-todos-los-usuarios-del-sistema">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Un <strong>primer método</strong> es instalar paquetes de fuentes disponibles en los repositorios de nuestra distribución. Debemos conocer de antemano el nombre del paquete que proporciona las fuentes o hacer alguna búsqueda en nuestros repositorios y luego simplemente instalarlo con el gestor de paquetes de nuestra distribución:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Bash" data-lang="Bash"><span class="line"><span class="cl">sudo dnf install -y libreoffice-opensymbol-fonts
</span></span></code></pre></div><p>o</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Bash" data-lang="Bash"><span class="line"><span class="cl">sudo apt install -y libreoffice-opensymbol-fonts
</span></span></code></pre></div><p>Un <strong>segundo método</strong> es agregar archivos <em>TTF</em> u <em>OTF</em> que hayamos descargado de <a class="gblog-markdown__link" href="https://www.dafont.com">Dafont</a> o cualquier sitio web similar.</p>
<p>Para ello debemos crear un nuevo directorio dentro de <code>/usr/share/fonts/</code> con el nombre de la familia de la fuente, por ejemplo:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Bash" data-lang="Bash"><span class="line"><span class="cl">sudo mkdir /usr/share/fonts/bauhaus
</span></span></code></pre></div><p>Después hay que copiar los archivos de las fuentes (<em>TTF</em> u <em>OTF</em>) al nuevo directorio:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Bash" data-lang="Bash"><span class="line"><span class="cl">sudo cp Bauhaus*.ttf /usr/share/fonts/bauhaus/
</span></span></code></pre></div><p>Por último hay que actualizar la caché de fuentes para que estén disponibles sin necesidad de reiniciar el equipo:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Bash" data-lang="Bash"><span class="line"><span class="cl">sudo fc-cache -v
</span></span></code></pre></div><p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="paquetes-de-fuentes">
        Paquetes de fuentes
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/add-ttf/#paquetes-de-fuentes" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Paquetes de fuentes" href="#paquetes-de-fuentes">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><ul>
<li>ttf-aenigma</li>
<li>ttf-anonymous-pro</li>
<li>ttf-bitstream-vera</li>
<li>ttf-sjfonts</li>
</ul>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="añadir-tipos-de-letra-sólo-para-el-usuario-actual">
        Añadir tipos de letra sólo para el usuario actual
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/add-ttf/#añadir-tipos-de-letra-sólo-para-el-usuario-actual" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Añadir tipos de letra sólo para el usuario actual" href="#a%c3%b1adir-tipos-de-letra-s%c3%b3lo-para-el-usuario-actual">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>No siempre es buena idea agregar fuentes para todo el sistema, sobre todo si hay muchos usuarios y cada uno agrega un importante número de fuentes que realmente el resto de usuarios no utiliza. También puede ocurrir que no se tengan los priviliegios de administración sobre el sistema. Sea cual sea el motivo es posible agregar las fuentes sólo para un usuario. Para ello, el primer paso es crear un nuevo directorio dentro de  <code>~/.local/share/fonts/</code> o <code>~/.fonts</code> (puede que el propio directorio <em>fonts</em> no exista y haya que crearlo también) con el nombre de la familia, por ejemplo:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Bash" data-lang="Bash"><span class="line"><span class="cl">mkdir -p ~/.local/share/fonts/<span class="s2">&#34;retro computer&#34;</span>
</span></span></code></pre></div><p>A continuación copiamos los archivos de las fuentes (<em>TTF</em> u <em>OTF</em>) al nuevo directorio:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Bash" data-lang="Bash"><span class="line"><span class="cl">cp Retro*.otf ~/.local/share/fonts/<span class="s2">&#34;retro computer&#34;</span>/
</span></span></code></pre></div><p>y por último hay que actualizar la caché de fuentes para que estén disponibles para las aplicaciones sin necesidad de reiniciar el equipo:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Bash" data-lang="Bash"><span class="line"><span class="cl">fc-cache -v
</span></span></code></pre></div><p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="referencias">
        Referencias
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/add-ttf/#referencias" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Referencias" href="#referencias">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p><a class="gblog-markdown__link" href="https://docs.fedoraproject.org/en-US/quick-docs/fonts/#unpackaged">https://docs.fedoraproject.org/en-US/quick-docs/fonts/#unpackaged</a></p>]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://dvdcr.gitlab.io/tags/linux" term="linux" label="linux" />
                            
                        
                    
                
            
        </entry>
    
        
        <entry>
            <title type="html"><![CDATA[Configurar sensor de movimiento Tuya ZM-35ZH-Q con Zigbee2MQTT]]></title>
            <link href="https://dvdcr.gitlab.io/posts/2022/03/iot-tuya-sensor-movimiento/?utm_source=atom_feed" rel="alternate" type="text/html"  hreflang="en" />
            <id>https://dvdcr.gitlab.io/posts/2022/03/iot-tuya-sensor-movimiento/</id>
            
            <published>2022-03-06T00:00:00+01:00</published>
            <updated>2022-03-06T00:00:00+01:00</updated>
            
            
            <content type="html"><![CDATA[<p><img src="/img/sensor-movimiento-tuya-ZM-35ZH-Q.png#center" alt=""  /></p>
<p>El <strong>Tuya ZM-35ZH-Q</strong> es un sensor de movimiento que se conecta a través de protocolo <strong>Zigbee</strong>. Se alimenta con una pila de botón de 3V CR2450 que promete una duración de más de un año. Por sus reducidas dimensiones (33mm de diámetro por 30mm de altura) se puede colocar en cualquier sitio sin que resulte muy llamativo.</p>
<p>Está diseñado para funcionar con la aplicación <em>Tuya Smart Life</em>, pero veremos cómo configurarlo para su uso con <strong>zigbee2mqtt</strong>, lo que abre el paso para su control mediante scripts o programas propios y a la integración plataformas como  <strong>Home Assistant</strong>, pero sin requerir de servicios externos.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="requisitos-previos">
        Requisitos previos
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/iot-tuya-sensor-movimiento/#requisitos-previos" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Requisitos previos" href="#requisitos-previos">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Debemos disponer de los siguientes componentes ya instalados y configurados:</p>
<ul>
<li><strong>Broker MQTT</strong>. En <a class="gblog-markdown__link" href="https://dvdcr.gitlab.io/posts/2022/01/iot-rpi-mqtt/">este artículo</a> se explica cómo instalar <strong>mosquitto</strong> en una Raspberry Pi.</li>
<li><strong>Zigbee2mqtt</strong>. La instalación y configuración básica se describen en <a class="gblog-markdown__link" href="https://dvdcr.gitlab.io/posts/2022/02/iot-rpi-zigbee2mqtt/">este artículo</a></li>
</ul>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="emparejamiento">
        Emparejamiento
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/iot-tuya-sensor-movimiento/#emparejamiento" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Emparejamiento" href="#emparejamiento">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Pulsamos durante cinco segundos el botón de reset, hasta que comience a parpadear un led rojo. El botón de reset está dentro de un pequeño orificio para evitar que se active por accidente. Hay que pulsarlo con un pin similar a los que se utilizan para abrir la bandeja de la tarjeta SIM de los teléfonos, incluído en la caja del dispositivo. Una vez activada la sincronización en el log de <strong>zigbee2mqtt</strong> aparecerá:</p>
<pre tabindex="0"><code>info  2022-02-20 21:57:56: Device &#39;0xa4c138b9576d55bf&#39; joined
info  2022-02-20 21:57:56: MQTT publish: topic &#39;zigbee2mqtt/bridge/event&#39;, 
      payload &#39;{&#34;data&#34;:{&#34;friendly_name&#34;:&#34;0xa4c138b9576d55bf&#34;,&#34;ieee_address&#34;:&#34;0xa4c138b9576d55bf&#34;},
      &#34;type&#34;:&#34;device_joined&#34;}&#39;
info  2022-02-20 21:57:56: Starting interview of &#39;0xa4c138b9576d55bf&#39;
info  2022-02-20 21:57:56: MQTT publish: topic &#39;zigbee2mqtt/bridge/event&#39;, 
      payload &#39;{&#34;data&#34;:{&#34;friendly_name&#34;:&#34;0xa4c138b9576d55bf&#34;,&#34;ieee_address&#34;:&#34;0xa4c138b9576d55bf&#34;,
      &#34;status&#34;:&#34;started&#34;},&#34;type&#34;:&#34;device_interview&#34;}&#39;
info  2022-02-20 21:57:56: MQTT publish: topic &#39;zigbee2mqtt/bridge/event&#39;, 
      payload &#39;{&#34;data&#34;:{&#34;friendly_name&#34;:&#34;0xa4c138b9576d55bf&#34;,&#34;ieee_address&#34;:&#34;0xa4c138b9576d55bf&#34;},
      &#34;type&#34;:&#34;device_announce&#34;}&#39;
</code></pre><p>Si en el log aparece el mensaje <code>device_joined</code> será indicativo de que se ha emparejado correctamente.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="asignación-de-un-nombre-amigable">
        Asignación de un nombre amigable
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/iot-tuya-sensor-movimiento/#asignación-de-un-nombre-amigable" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Asignación de un nombre amigable" href="#asignaci%c3%b3n-de-un-nombre-amigable">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Para dar un nombre más amigable al dispositivo, paramos el servicio <strong>zigbee2mqtt</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo systemctl stop zigbee2mqtt
</span></span></code></pre></div><p>Editamos el archivo de configuración de <strong>zigbee2mqtt</strong>, que en este caso está en <code>/opt/zigbee2mqtt/data/configuration.yaml</code>.</p>
<p>Al final del archivo, en la sección <code>devices</code> nos aparecerá una entrada del tipo:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vim" data-lang="vim"><span class="line"><span class="cl">  <span class="s1">&#39;0xa4c138b9576d55bf&#39;</span>:
</span></span><span class="line"><span class="cl">    <span class="nx">friendly_name</span>: <span class="s1">&#39;0xa4c138b9576d55bf&#39;</span>
</span></span></code></pre></div><p>Cambiamos el valor del atributo <code>friendly_name</code> por otro que nos resulte más fácil de reconocer:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vim" data-lang="vim"><span class="line"><span class="cl">  <span class="s1">&#39;0xa4c138b9576d55bf&#39;</span>:
</span></span><span class="line"><span class="cl">   <span class="nx">friendly_name</span>: <span class="s1">&#39;movimiento&#39;</span>
</span></span></code></pre></div><p>y arrancamos el servicio de nuevo para que se aplique el nuevo nombre:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo systemctl start zigbee2mqtt
</span></span></code></pre></div><p>Ahora debe publicar los eventos en el <strong>topic</strong> <code>zigbee2mqtt/movimiento</code>.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="lectura-de-valores">
        Lectura de valores
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/iot-tuya-sensor-movimiento/#lectura-de-valores" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Lectura de valores" href="#lectura-de-valores">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Se trata de un dispositivo de bajo consumo, por lo que el sensor no está accesible la mayoría del tiempo, sólo cuando se activa porque se detecta un movimiento.</p>
<p>Las peticiones de lectura de valores o escritura para configurar parámetros no se procesan inmediatamente sino que son encoladas y se envían en la siguiente ocasión en la que está disponible.</p>
<p>Para leer valores, nos suscribimos con <code>mosquitto_sub</code> al <strong>topic</strong> <code>zigbee2mqtt/movimiento</code> en nuestro <strong>broker MQTT</strong>. Si lo hacemos desde dentro de la propia Raspberry pi (o el servidor donde se esté ejecutando <strong>mosquitto</strong>) utilizaremos el parámetro <code>-h localhost</code>. Si lo hacemos desde otro equipo, especificaremos la dirección IP del la máquina donde se ejecute <strong>mosquitto</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">mosquitto_sub -h localhost -p <span class="m">1883</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-u <span class="nv">$MQTTUSER</span> -P <span class="nv">$MQTTPASS</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-t <span class="s2">&#34;zigbee2mqtt/movimiento&#34;</span>
</span></span></code></pre></div><p>Cuando detecta un movimiento, recibimos el valor <code>true</code> en el parámetro <code>occupancy</code>.</p>
<pre tabindex="0"><code>{&#34;battery&#34;:null,&#34;battery_low&#34;:false,&#34;linkquality&#34;:87,&#34;occupancy&#34;:true,&#34;tamper&#34;:false}
</code></pre><p>Cuando se deja de detectar, se envía el valor <code>false</code> en ese mismo parámetro:</p>
<pre tabindex="0"><code>{&#34;battery&#34;:null,&#34;battery_low&#34;:false,&#34;linkquality&#34;:84,&#34;occupancy&#34;:false,&#34;tamper&#34;:false}
</code></pre><p>Además del parámetro <code>occupancy</code> que es el que nos indica si realmente se detecta movimiento, el mensaje <em>JSON</em> muestra otros parámetros:</p>
<ul>
<li><strong>battery_low</strong>. Indica si la batería está a punto de acabarse, en cuyo caso contiene el valor <code>true</code>. Si la batería tiene un nivel aceptable contiene el valor <code>false</code>.</li>
<li><strong>battery</strong>. Número que indica la batería restante en porcentaje. Según la documentación, debe mostrar un valor numérico entre 0 y 100, pero en las pruebas que se han realizado, siempre ha mostrado el valor <code>null</code>.</li>
<li><strong>tamper</strong>. Indica si el dispositivo es manipulado. Algunos detectores incluyen un par de terminales llamados <strong>tamper</strong>, como sistema anti-sabotaje. Cuando alguien trata de abrir el dispositivo, la apertura se detecta y se envía una señal para avisar de ello. En este caso, se envía el valor <code>true</code> en este parámetro si el dispositivo es manipulado o <code>false</code> en caso contrario.</li>
<li><strong>linkquality</strong>. Fuerza de la señal. El valor mínimo es 0 y el máximo 255. La unidad de este valor es <strong>lqi</strong> (<em>Link Quality Indicator</em>).</li>
</ul>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="parámetros-de-configuración">
        Parámetros de configuración
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/iot-tuya-sensor-movimiento/#parámetros-de-configuración" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Parámetros de configuración" href="#par%c3%a1metros-de-configuraci%c3%b3n">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Para configurar su comportamiento, este dispositivo admite algunos parámetros que se describen a continuación. Como se ha indicado anteriormente, al tratarse de un dispositivo de bajo consumo, no está permanentemente disponible. Sólo lo está cuando se detecta movimiento (<code>{&quot;occupancy&quot;: true}</code>).</p>
<div class="gblog-post__anchorwrap">
    <h3 id="sensitivity">
        sensitivity
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/iot-tuya-sensor-movimiento/#sensitivity" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor sensitivity" href="#sensitivity">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h3>
</div><p>Ajuste de sensibilidad al movimiento. Admite tres valores: <code>low</code>, <code>medium</code> y <code>high</code>. Para modificar la configuración  hay que publicar un mensaje en el <strong>topic</strong>  <code>zigbee2mqtt/FRIENDLY_NAME/set</code>con el <strong>payload</strong> <code>{&quot;sensitivity&quot;: &lt;valor&gt;}</code> con uno de esos tres valores. Para que se aplique la configuración lo antes posible, lo mejor es generar movimiento delante del sensor para que se active. Ejemplo para poner la sensibilidad al mínimo:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">mosquitto_pub -h localhost -p <span class="m">1883</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-u <span class="nv">$MQTTUSER</span> -P <span class="nv">$MQTTPASS</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-t <span class="s2">&#34;zigbee2mqtt/movimiento/set&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-m <span class="s1">&#39;{&#34;sensitivity&#34;: &#34;low&#34;}&#39;</span>
</span></span></code></pre></div><div class="gblog-post__anchorwrap">
    <h3 id="keep_time">
        keep_time
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/iot-tuya-sensor-movimiento/#keep_time" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor keep_time" href="#keep_time">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h3>
</div><p>Tiempo de espera tras una activación para volver al modo <code>occupancy=false</code>. Representa una cifra en segundos y admite sólo los valores <code>30</code>, <code>60</code> y <code>120</code>. En las pruebas realizadas se ha visto que es un valor aproximado, no exacto.</p>
<p>Para modificar este valor hay que publicar un mensaje en el topic <code>zigbee2mqtt/FRIENDLY_NAME/set</code> con el <strong>payload</strong> <code>{&quot;keep_time&quot;: &lt;valor&gt;}</code>. Si se indica un valor numérico que no sea uno de los 3 especificados, no realiza ningún cambio.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">mosquitto_pub -h localhost -p <span class="m">1883</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-u <span class="nv">$MQTTUSER</span> -P <span class="nv">$MQTTPASS</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-t <span class="s2">&#34;zigbee2mqtt/movimiento/set&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-m <span class="s1">&#39;{&#34;keep_time&#34;: &#34;60&#34;}&#39;</span>
</span></span></code></pre></div><p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="resumen">
        Resumen
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/iot-tuya-sensor-movimiento/#resumen" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Resumen" href="#resumen">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Disponiendo de un <strong>gateway Zigbee</strong> y un <strong>broker MQTT</strong> se puede emparejar con el gateway un sensor de movimiento con conectividad <strong>Zigbee</strong>, en este caso el Tuya ZM-35ZH-Q. Se ha visto como configurarlo y consultar su estado a través de la publicación y suscripción a <strong>topics</strong> <strong>MQTT</strong>. Esto permite detectar la entrada de una persona en un área y realizar acciones en consecuencia, como disparar una alarma, un aviso o encender una luz. En combinación con <a class="gblog-markdown__link" href="https://www.home-assistant.io/">Home Assistant</a> permitiría definir automatizaciones de forma sencilla.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="referencias">
        Referencias
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/03/iot-tuya-sensor-movimiento/#referencias" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Referencias" href="#referencias">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><ul>
<li><a class="gblog-markdown__link" href="https://www.zigbee2mqtt.io/devices/ZM-35H-Q.html">https://www.zigbee2mqtt.io/devices/ZM-35H-Q.html</a></li>
</ul>]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://dvdcr.gitlab.io/tags/IoT" term="IoT" label="IoT" />
                            
                        
                    
                
            
        </entry>
    
        
        <entry>
            <title type="html"><![CDATA[Introducción a Kustomize. Personalización de recursos de Kubernetes.]]></title>
            <link href="https://dvdcr.gitlab.io/posts/2022/02/k8s-kustomize-intro/?utm_source=atom_feed" rel="alternate" type="text/html"  hreflang="en" />
            <id>https://dvdcr.gitlab.io/posts/2022/02/k8s-kustomize-intro/</id>
            
            <published>2022-02-27T00:00:00+01:00</published>
            <updated>2022-02-27T00:00:00+01:00</updated>
            
            
            <content type="html"><![CDATA[<p><img src="/img/kustomize-logo-text.svg#center" alt=""  /></p>
<p>La gestión de recursos en <strong>kubernetes</strong> se realiza principalmente a través de definiciones en lenguaje <strong>YAML</strong>. Para mejorar la gestión y reutilizar definiciones, algunas de las implementaciones específicas de <strong>kubernetes</strong> como <strong>OpenShift</strong> permiten el uso de <strong>plantillas</strong> o <strong>templates</strong>, mientras que en otros casos se utilizan herramientas de terceros que permiten definir plantillas, procesarlas y generar recursos en base a parámetros.</p>
<p><a class="gblog-markdown__link" href="https://kustomize.io">kustomize</a> es una herramienta para conseguir el mismo objetivo con un enfoque ligeramente diferente. Permite personalizar una definición inicial que sirve como base, especificando parches concretos por entorno, proyecto o grupo de recursos. Las personalizaciones o parches incluyen sólo aquellas partes que difieren de la definición base. A diferencia de una plantilla, el documento de partida es una definición real y perfectamente utilizable de manera individual, no un documento con una sintaxis de marcado que requiere un procesado previo que reemplace las variables por valores reales para su utilización.</p>
<p> </p>
<p><strong>Contenido</strong></p>



  <div class="gblog-toc gblog-toc__level--6"><nav id="TableOfContents">
  <ul>
    <li>
      <ul>
        <li><a href="#instalación">Instalación</a></li>
        <li><a href="#definición-de-los-recursos-de-una-aplicación-sin-kustomize">Definición de los recursos de una aplicación sin kustomize</a></li>
        <li><a href="#definición-de-los-recursos-de-una-aplicación-con-customize-para-un-entorno-de-desarrollo">Definición de los recursos de una aplicación con customize para un entorno de desarrollo</a></li>
        <li><a href="#definición-de-los-recursos-de-una-aplicación-con-customize-para-un-entorno-de-preproducción">Definición de los recursos de una aplicación con customize para un entorno de preproducción</a></li>
        <li><a href="#resumen">Resumen</a></li>
        <li><a href="#referencias">Referencias</a></li>
      </ul>
    </li>
  </ul>
</nav><hr></div>


<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="instalación">
        Instalación
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/02/k8s-kustomize-intro/#instalación" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Instalación" href="#instalaci%c3%b3n">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p><strong>Kustomize</strong> viene incluido en el cliente de <strong>kubernetes</strong> <code>kubectl</code> desde la versión 1.14. Utilizamos <strong>kustomize</strong> con este cliente con el comando <code>kubectl apply -k &lt;archivo.yaml&gt;</code> en lugar de utilizar el habitual parámetro <code>-f</code> que se utiliza al aplicar las definiciones de un archivo. También existe la posibilidad de utilizarlo como una herramienta <em>standalone</em>. Es una herramienta creada en <a class="gblog-markdown__link" href="">Go</a> por lo que consta de un sólo ejecutable que depende del sistema operativo. Basta con descargar el archivo comprimido de la <a class="gblog-markdown__link" href="https://github.com/kubernetes-sigs/kustomize/releases">página de versiones</a>, descomprimirlo y copiarlo a algún directorio que se encuentre dentro del <em>path</em> en nuestro sistema operativo.</p>
<p>También se puede instalar con la instrucción:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">curl -s <span class="s2">&#34;https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="p">|</span> bash
</span></span></code></pre></div><p>que detectará el sistema operativo y se descargará el binario que corresponda. <strong>Este sistema no funciona para arquitectura ARM</strong>.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="definición-de-los-recursos-de-una-aplicación-sin-kustomize">
        Definición de los recursos de una aplicación sin kustomize
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/02/k8s-kustomize-intro/#definición-de-los-recursos-de-una-aplicación-sin-kustomize" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Definición de los recursos de una aplicación sin kustomize" href="#definici%c3%b3n-de-los-recursos-de-una-aplicaci%c3%b3n-sin-kustomize">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Para entender el funcionamiento de <strong>kustomize</strong> vamos a crear una aplicación de ejemplo. Nos basaremos en el cluster de <strong>microk8s</strong>, llamado <em>ocm-hub</em> que configuramos en el artículo <a class="gblog-markdown__link" href="https://dvdcr.gitlab.io/posts/2022/01/mk8s-ocm/">Instalar Open Cluster Management en un equipo personal</a></p>
<p>Arrancamos la máquina virtual y el cluster:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">multipass start ocm-hub
</span></span><span class="line"><span class="cl">multipass <span class="nb">exec</span> ocm-hub microk8s start
</span></span></code></pre></div><p>Partimos de un namespace llamado <code>portales</code> donde vamos a crear nuestro proyecto:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">kubectl --kubeconfig<span class="o">=</span><span class="nv">$HOME</span>/.kube/ocm-hub.config create namespace portales
</span></span></code></pre></div><p>La definición inicial de la aplicación será para un entorno de desarrollo y tendrá los siguientes elementos:</p>
<ul>
<li>Un <strong>config map</strong> llamado <code>contenido</code> con el código de la página <em>index.html</em>.</li>
<li>Un <strong>deployment</strong> llamado <code>portal</code> que crea un pod de <em>nginx</em> y monta el <strong>config map</strong> como archivo <code>index.html</code> en el directorio de publicación de <em>ngnix</em>.</li>
<li>Un <strong>service</strong> <code>portal</code> para exponer los pods de forma que sean accesibles desde fuera del cluster.</li>
</ul>
<p>Vamos a crear un directorio llamado <code>base</code> donde van a existir un archivo por cada uno de estos elementos:</p>
<pre tabindex="0"><code>webapp
└── base
    ├── configmap.yaml
    ├── deployment.yaml
    └── service.yaml
</code></pre><p>Podemos crear todos los objetos necesarios para esta aplicación situándonos en el directorio donde están los archivos <code>YAML</code> de definición y ejecutando:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">kubectl --kubeconfig<span class="o">=</span><span class="nv">$HOME</span>/.kube/ocm-hub.config apply -f .
</span></span></code></pre></div><pre tabindex="0"><code>configmap/contenido created
deployment.apps/portal created
service/portal created
</code></pre><p>Comprobamos que se han creado el pod y el servicio:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">kubectl --kubeconfig<span class="o">=</span><span class="nv">$HOME</span>/.kube/ocm-hub.config get pods -n portales
</span></span></code></pre></div><pre tabindex="0"><code>NAME                     READY   STATUS    RESTARTS   AGE
portal-87bdbf6c8-8m7cf   1/1     Running   0          5m5s
</code></pre><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">kubectl --kubeconfig<span class="o">=</span><span class="nv">$HOME</span>/.kube/ocm-hub.config get service -n portales
</span></span></code></pre></div><pre tabindex="0"><code>NAME     TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
portal   NodePort   10.152.183.147   &lt;none&gt;        8080:30001/TCP   5m17s
</code></pre><p>Podemos comprobar que funciona haciendo una petición al puerto 30001 de nuestro cluster. Para ello obtenemos la dirección IP de nuestra máquina virtual:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">multipass info ocm-hub <span class="p">|</span> grep IPv4
</span></span></code></pre></div><pre tabindex="0"><code>IPv4:           10.89.130.31
</code></pre><p>e introducimos la dirección en el navegador o probamos con <code>curl</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">curl 10.89.130.31:30001
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">html</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">style</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">body</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="k">background-color</span><span class="p">:</span> <span class="mh">#fff</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">style</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">body</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">     <span class="p">&lt;</span><span class="nt">h1</span><span class="p">&gt;</span>Portal corporativo<span class="p">&lt;/</span><span class="nt">h1</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">     <span class="p">&lt;</span><span class="nt">h2</span><span class="p">&gt;</span>Entorno: Desarrollo<span class="p">&lt;/</span><span class="nt">h2</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">body</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">html</span><span class="p">&gt;</span>
</span></span></code></pre></div><p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="definición-de-los-recursos-de-una-aplicación-con-customize-para-un-entorno-de-desarrollo">
        Definición de los recursos de una aplicación con customize para un entorno de desarrollo
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/02/k8s-kustomize-intro/#definición-de-los-recursos-de-una-aplicación-con-customize-para-un-entorno-de-desarrollo" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Definición de los recursos de una aplicación con customize para un entorno de desarrollo" href="#definici%c3%b3n-de-los-recursos-de-una-aplicaci%c3%b3n-con-customize-para-un-entorno-de-desarrollo">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Ahora supongamos que esta aplicación corresponde a un entorno de desarrollo, pero queremos ahora desplegarla además en un entorno de preproducción. La configuración en lo esencial será la misma, pero habrá algunos parámetros diferentes, como las etiquetas, el número de pods y los límites de recursos por pod. Podríamos simplemente repetir todos los archivos <code>YAML</code> y modificarlos en aquellos puntos que difieren, pero aquí es donde interviene <code>kustomize</code> para hacer esto un poco más sencillo.</p>
<p><strong>Kustomize</strong> permite partir de una configuración válida, como en este caso la de desarrollo, y definir varias configuraciones alternativas con cambios a partir de configuración base.
Esto permitiría por ejemplo partir de un repositorio de git sobre el que no tengamos control, personalizando las partes que difieren y adaptar más fácilmente los cambios que surjan en el repositorio original.</p>
<p>Vamos a seguir una de las estrategias posibles que es especificar sólo los atributos del archivo <code>YAML</code> que difieren y aquellos que conforman la jerarquía completa de la que dependen hasta la raíz para que el resultado sea un archivo correcto.</p>
<p>Primero vamos a eliminar todos los objetos que hemos creado:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">kubectl --kubeconfig<span class="o">=</span><span class="nv">$HOME</span>/.kube/ocm-hub.config delete -f .
</span></span></code></pre></div><p>Vamos a revisar la <strong>configuración base</strong>, que estará compuesta por los archivos que ya teníamos con alguna leve modificación.</p>
<p><code>base/configmap.yaml</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">ConfigMap</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">contenido</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">portales</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">entorno</span><span class="p">:</span><span class="w"> </span><span class="l">Base</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">index</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">    &lt;html&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">      &lt;style&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">        body {
</span></span></span><span class="line"><span class="cl"><span class="sd">          background-color: #fff;
</span></span></span><span class="line"><span class="cl"><span class="sd">        }
</span></span></span><span class="line"><span class="cl"><span class="sd">      &lt;/style&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">      &lt;body&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">         &lt;h1&gt;Portal corporativo&lt;/h1&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">         &lt;h2&gt;Entorno base&lt;/h2&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">      &lt;/body&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">    &lt;/html&gt;</span><span class="w">    
</span></span></span></code></pre></div><p> </p>
<p><code>base/deployment.yaml</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span>-<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">apps/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Deployment</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">portales</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">portal</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">portal</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">replicas</span><span class="p">:</span><span class="w"> </span><span class="m">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">selector</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">matchLabels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">portal</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">portal</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">containers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">nginx</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">nginx:1.21.5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">containerPort</span><span class="p">:</span><span class="w"> </span><span class="m">8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">protocol</span><span class="p">:</span><span class="w"> </span><span class="l">TCP</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">volumeMounts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">paginas</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">mountPath</span><span class="p">:</span><span class="w"> </span><span class="l">/usr/share/nginx/html</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">resources</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">requests</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">cpu</span><span class="p">:</span><span class="w"> </span><span class="l">100m</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">memory</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;128M&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">limits</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">cpu</span><span class="p">:</span><span class="w"> </span><span class="l">100m</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">memory</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;256M&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">env</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">LOG_LEVEL</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;DEBUG&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">ENTORNO</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">valueFrom</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">configMapKeyRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">contenido</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">entorno</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">paginas</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">configMap</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">contenido</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">items</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">index</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">index.html</span><span class="w">
</span></span></span></code></pre></div><p> </p>
<p><code>base/service.yaml</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">portales</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">portal</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">portal</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">NodePort</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">acceso-externo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">nodePort</span><span class="p">:</span><span class="w"> </span><span class="m">30000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">protocol</span><span class="p">:</span><span class="w"> </span><span class="l">TCP</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">targetPort</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">selector</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">portal</span><span class="w">
</span></span></span></code></pre></div><p> </p>
<p>Aparte de los archivos de definición propiamente dichos tendremos que añadir un archivo de configuración para <strong>kustomize</strong>. Tiene que estar en el directorio donde tenemos nuestros archivos <code>YAML</code> y su nombre debe ser <code>kustomization.yaml</code>:</p>
<p><code>base/kustomization.yaml</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">resources</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="l">configmap.yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="l">deployment.yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="l">service.yaml</span><span class="w">
</span></span></span></code></pre></div><p>Este archivo contiene la lista de los archivos que conforman la configuración base, lo que se indica bajo la etiqueta <code>resources</code>.</p>
<p>Ahora crearemos el directorio <code>overlays</code> donde añadiremos las personalizaciones sobre la base para cada uno de los entornos. Comenzamos creando una personalización para el <strong>entorno de desarrollo</strong>, en un directorio que llamaremos <code>dev</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">mkdir -p overlays/dev
</span></span></code></pre></div><p>Respecto al <strong>configmap</strong>, tendremos un archivo completo. Difiere en la variable <code>entorno</code> y que en el código html mostrará en un encabezado el texto <code>Entorno desarrollo</code>.
Con respecto a este elemento no hay ningún ahorro en código y reutilización que nos aporte <strong>kustomization</strong>. El contenido principal es el código HTML, que es contenido estático. Si se tratara de otro tipo de archivo que no fuera estático podríamos incluir contenido en base a variables de entorno y no sería necesario repetirlo al completo:</p>
<p><code>overlays/dev/configmap.yaml</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">ConfigMap</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">contenido</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">portales</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">entorno</span><span class="p">:</span><span class="w"> </span><span class="l">Desarrollo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">index</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">    &lt;html&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">      &lt;style&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">        body {
</span></span></span><span class="line"><span class="cl"><span class="sd">          background-color: #fff;
</span></span></span><span class="line"><span class="cl"><span class="sd">        }
</span></span></span><span class="line"><span class="cl"><span class="sd">      &lt;/style&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">      &lt;body&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">         &lt;h1&gt;Portal corporativo&lt;/h1&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">         &lt;h2&gt;Entorno desarrollo&lt;/h2&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">      &lt;/body&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">    &lt;/html&gt;</span><span class="w">    
</span></span></span></code></pre></div><p>Respecto al <strong>deployment</strong>, vamos a especificar el <strong>número de pods</strong>, que será 1 (en la configuración base era 0), para lo que crearemos el archivo <code>overlays/dev/deployment-replicas.yaml</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">apps/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Deployment</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">portales</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">portal</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">replicas</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span></code></pre></div><p>En este caso sólo incluimos los datos necesarios para la identificación del objeto (<code>metadata.namespace</code> y <code>metadata.name</code>) y el dato concreto que queremos modificar (<code>spec.replicas:1</code>)</p>
<p>También sobre el <strong>deployment</strong> vamos a modificar las <strong>etiquetas</strong> asignadas al deployment y a los pods que contiene, para que sean diferentes de las de otros entornos, en este caso las etiquetas van a ser <code>app: portal-dev</code>. El archivo lo llamaremos <code>overlays/dev/deployment-labels.yaml</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">apps/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Deployment</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">portales</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">portal</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">portal-dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">selector</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">matchLabels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">portal-dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">portal-dev</span><span class="w">
</span></span></span></code></pre></div><p>En el <strong>service</strong>, será necesario modificar las etiquetas de selección de pods para que se adecúen al entorno de desarrollo, etiquetado con <code>app: portal-dev</code>. También habrá que variar el puerto. Estos cambios estarán en el archivo <code>overlays/dev/service.yaml</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">portales</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">portal</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">acceso-externo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">nodePort</span><span class="p">:</span><span class="w"> </span><span class="m">30001</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">protocol</span><span class="p">:</span><span class="w"> </span><span class="l">TCP</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">targetPort</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">selector</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">portal-dev</span><span class="w">
</span></span></span></code></pre></div><p>A todo lo enterior añadiremos un archivo de configuración para <strong>kustomize</strong>. Tiene que estar en el directorio donde tenemos nuestros archivos <code>YAML</code> y se llamará <code>kustomization.yaml</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">nameSuffix</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>-<span class="l">dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">bases</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="l">../../base</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">patchesStrategicMerge</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="l">configmap.yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="l">service.yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="l">deployment-labels.yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="l">deployment-replicas.yaml</span><span class="w">
</span></span></span></code></pre></div><p>Este archivo refleja cuáles son los archivos de definición a procesar, indicando cuál es el directorio o directorios que contienen la configuración base (parámetro <code>bases</code>), cuales son los archivos que modificarán esa configuración base (parámetro <code>patchesStrategicMerge</code>) y además que sobre los nombres de los objetos que se creen se aplicará el sufijo <code>-dev</code> (parámetro <code>nameSuffix</code>).</p>
<p><strong>Kustomize</strong> soporta diferentes mecanismos de <em>parcheo</em> o <em>personalización</em> de la configuración base, como <a class="gblog-markdown__link" href="https://github.com/kubernetes/community/blob/master/contributors/devel/sig-api-machinery/strategic-merge-patch.md">patchesStrategicMerge</a> y <code>patchesJson6902</code>. El primero de ellos, que es el que estamos utilizando en este ejemplo, es una lista de archivos, cada uno de los cuales debe incluir el nombre de un recurso (Deployment, Pod, Service, etc.) procedente de la base. Lo más recomendable es hacer pequeños <em>parches</em> que hacen un cambio en concreto.</p>
<p>La estructura de archivos y directorios que queda tras la configuración es la siguiente:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vim" data-lang="vim"><span class="line"><span class="cl"><span class="nx">webapp</span>
</span></span><span class="line"><span class="cl">├── <span class="nx">base</span>
</span></span><span class="line"><span class="cl">│   ├── <span class="nx">configmap</span>.<span class="nx">yaml</span>
</span></span><span class="line"><span class="cl">│   ├── <span class="nx">deployment</span>.<span class="nx">yaml</span>
</span></span><span class="line"><span class="cl">│   ├── <span class="nx">kustomization</span>.<span class="nx">yaml</span>
</span></span><span class="line"><span class="cl">│   └── <span class="nx">service</span>.<span class="nx">yaml</span>
</span></span><span class="line"><span class="cl">└── <span class="nx">overlays</span>
</span></span><span class="line"><span class="cl">    └── <span class="nx">dev</span>
</span></span><span class="line"><span class="cl">        ├── <span class="nx">configmap</span>.<span class="nx">yaml</span>
</span></span><span class="line"><span class="cl">        ├── <span class="nx">deployment</span><span class="p">-</span><span class="nx">labels</span>.<span class="nx">yaml</span>
</span></span><span class="line"><span class="cl">        ├── <span class="nx">deployment</span><span class="p">-</span><span class="nx">replicas</span>.<span class="nx">yaml</span>
</span></span><span class="line"><span class="cl">        ├── <span class="nx">kustomization</span>.<span class="nx">yaml</span>
</span></span><span class="line"><span class="cl">        └── <span class="nx">service</span>.<span class="nx">yaml</span>
</span></span></code></pre></div><p>Volvemos a desplegar nuestra aplicación, pero en este caso especificando el parámetro <code>-k</code> para indicar que se debe utilizar <em>kustomize</em> para procesar los archivos de definición y el directorio donde se encuentran las definiciones. Al utilizar este parámetro <em>kustomize</em> buscará un archivo <code>kustomization.yaml</code> y aplicará las instrucciones ahí definidas. Si nos situamos en el directorio <code>webapp</code> la forma de aplicar la configuración para el entorno de desarrollo sería:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">kubectl --kubeconfig<span class="o">=</span><span class="nv">$HOME</span>/.kube/ocm-hub.config apply -k overlays/dev
</span></span></code></pre></div><pre tabindex="0"><code>configmap/contenido-dev created
service/portal-dev created
deployment.apps/portal-dev created
</code></pre><p>Podemos ver que los elementos se han creado con el nombre previsto y el sufijo <code>-dev</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">kubectl --kubeconfig<span class="o">=</span><span class="nv">$HOME</span>/.kube/ocm-hub.config get pods,deployments,services -n portales
</span></span></code></pre></div><pre tabindex="0"><code>NAME                             READY   STATUS    RESTARTS   AGE
pod/portal-dev-d6f58dc49-zb47c   1/1     Running   0          27s

NAME                         READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/portal-dev   1/1     1            1           27s

NAME                 TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
service/portal-dev   NodePort   10.152.183.50   &lt;none&gt;        8080:30001/TCP   27s
</code></pre><p>y que la web muestra su contenido a través del puerto 30001:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">curl 10.89.130.31:30001
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">html</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">style</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">body</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="k">background-color</span><span class="p">:</span> <span class="mh">#fff</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">style</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">body</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">     <span class="p">&lt;</span><span class="nt">h1</span><span class="p">&gt;</span>Portal corporativo<span class="p">&lt;/</span><span class="nt">h1</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">     <span class="p">&lt;</span><span class="nt">h2</span><span class="p">&gt;</span>Entorno desarrollo<span class="p">&lt;/</span><span class="nt">h2</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">body</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">html</span><span class="p">&gt;</span>
</span></span></code></pre></div><p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="definición-de-los-recursos-de-una-aplicación-con-customize-para-un-entorno-de-preproducción">
        Definición de los recursos de una aplicación con customize para un entorno de preproducción
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/02/k8s-kustomize-intro/#definición-de-los-recursos-de-una-aplicación-con-customize-para-un-entorno-de-preproducción" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Definición de los recursos de una aplicación con customize para un entorno de preproducción" href="#definici%c3%b3n-de-los-recursos-de-una-aplicaci%c3%b3n-con-customize-para-un-entorno-de-preproducci%c3%b3n">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Ahora crearemos dentro del directorio <code>overlays</code> un directorio que llamaremos <code>pre</code> para almacenar la personalización para el <strong>entorno de preproducción</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">mkdir -p overlays/pre
</span></span></code></pre></div><p>El config map será idéntico salvo por la variable <code>entorno</code> y la descripción del encabezado <code>h2</code> del código html:</p>
<p><code>overlays/dev/configmap.yaml</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">ConfigMap</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">contenido</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">portales</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">entorno</span><span class="p">:</span><span class="w"> </span><span class="l">Preproducción</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">index</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">    &lt;html&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">      &lt;style&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">        body {
</span></span></span><span class="line"><span class="cl"><span class="sd">          background-color: #fff;
</span></span></span><span class="line"><span class="cl"><span class="sd">        }
</span></span></span><span class="line"><span class="cl"><span class="sd">      &lt;/style&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">      &lt;body&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">         &lt;h1&gt;Portal corporativo&lt;/h1&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">         &lt;h2&gt;Entorno preproducción&lt;/h2&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">      &lt;/body&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">    &lt;/html&gt;</span><span class="w">    
</span></span></span></code></pre></div><p>Respecto al <strong>deployment</strong>, vamos a especificar el <strong>número de pods</strong> para el, que será 2, para lo que crearemos el archivo <code>overlays/dev/deployment-replicas.yaml</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">apps/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Deployment</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">portales</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">portal</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">replicas</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span></code></pre></div><p>Sobre el <strong>deployment</strong> vamos a modificar las <strong>etiquetas</strong> asignadas al deployment y a los pods que contiene, en este caso <code>app: portal-pre</code>. El archivo lo llamaremos <code>overlays/dev/deployment-labels.yaml</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">apps/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Deployment</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">portales</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">portal</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">portal-pre</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">selector</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">matchLabels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">portal-pre</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">portal-pre</span><span class="w">
</span></span></span></code></pre></div><p>En el <strong>service</strong>, cambiarán las etiquetas de selección de pods, etiquetados con <code>app: portal-pre</code>. También habrá que variar el puerto. Estos cambios estarán en el archivo <code>overlays/dev/service.yaml</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">portales</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">portal</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">acceso-externo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">nodePort</span><span class="p">:</span><span class="w"> </span><span class="m">30002</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">protocol</span><span class="p">:</span><span class="w"> </span><span class="l">TCP</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">targetPort</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">selector</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">portal-pre</span><span class="w">
</span></span></span></code></pre></div><p>Por último añadimos el archivo <code>kustomization.yaml</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">nameSuffix</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>-<span class="l">pre</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">bases</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="l">../../base</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">patchesStrategicMerge</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="l">configmap.yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="l">service.yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="l">deployment-labels.yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="l">deployment-replicas.yaml</span><span class="w">
</span></span></span></code></pre></div><p>La estructura de archivos y directorios que tenemos es la siguiente:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vim" data-lang="vim"><span class="line"><span class="cl"><span class="nx">webapp</span>
</span></span><span class="line"><span class="cl">├── <span class="nx">base</span>
</span></span><span class="line"><span class="cl">│   ├── <span class="nx">configmap</span>.<span class="nx">yaml</span>
</span></span><span class="line"><span class="cl">│   ├── <span class="nx">deployment</span>.<span class="nx">yaml</span>
</span></span><span class="line"><span class="cl">│   ├── <span class="nx">kustomization</span>.<span class="nx">yaml</span>
</span></span><span class="line"><span class="cl">│   └── <span class="nx">service</span>.<span class="nx">yaml</span>
</span></span><span class="line"><span class="cl">└── <span class="nx">overlays</span>
</span></span><span class="line"><span class="cl">    ├── <span class="nx">dev</span>
</span></span><span class="line"><span class="cl">    │   ├── <span class="nx">configmap</span>.<span class="nx">yaml</span>
</span></span><span class="line"><span class="cl">    │   ├── <span class="nx">deployment</span><span class="p">-</span><span class="nx">labels</span>.<span class="nx">yaml</span>
</span></span><span class="line"><span class="cl">    │   ├── <span class="nx">deployment</span><span class="p">-</span><span class="nx">replicas</span>.<span class="nx">yaml</span>
</span></span><span class="line"><span class="cl">    │   ├── <span class="nx">kustomization</span>.<span class="nx">yaml</span>
</span></span><span class="line"><span class="cl">    │   └── <span class="nx">service</span>.<span class="nx">yaml</span>
</span></span><span class="line"><span class="cl">    └── <span class="nx">pre</span>
</span></span><span class="line"><span class="cl">        ├── <span class="nx">configmap</span>.<span class="nx">yaml</span>
</span></span><span class="line"><span class="cl">        ├── <span class="nx">deployment</span><span class="p">-</span><span class="nx">labels</span>.<span class="nx">yaml</span>
</span></span><span class="line"><span class="cl">        ├── <span class="nx">deployment</span><span class="p">-</span><span class="nx">replicas</span>.<span class="nx">yaml</span>
</span></span><span class="line"><span class="cl">        ├── <span class="nx">kustomization</span>.<span class="nx">yaml</span>
</span></span><span class="line"><span class="cl">        └── <span class="nx">service</span>.<span class="nx">yaml</span>
</span></span></code></pre></div><p>Volvemos a desplegar nuestra aplicación, pero en este lugar indicando tras el parámetro <code>-k</code> la ruta de los archivos del entorno de preproducción:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">kubectl --kubeconfig<span class="o">=</span><span class="nv">$HOME</span>/.kube/ocm-hub.config apply -k overlays/pre
</span></span></code></pre></div><pre tabindex="0"><code>configmap/contenido-pre created
service/portal-pre created
deployment.apps/portal-pre created
</code></pre><p>Podemos ver que los elementos se han creado con el nombre previsto y el sufijo <code>-pre</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">kubectl --kubeconfig<span class="o">=</span><span class="nv">$HOME</span>/.kube/ocm-hub.config get pods,deployments,services -n portales
</span></span></code></pre></div><pre tabindex="0"><code>NAME                             READY   STATUS    RESTARTS   AGE
pod/portal-dev-d6f58dc49-zb47c   1/1     Running   0          21m
pod/portal-pre-5756bdc5f-j8xsx   1/1     Running   0          26s
pod/portal-pre-5756bdc5f-d8cjv   1/1     Running   0          26s

NAME                         READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/portal-dev   1/1     1            1           21m
deployment.apps/portal-pre   2/2     2            2           26s

NAME                 TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
service/portal-dev   NodePort   10.152.183.50   &lt;none&gt;        8080:30001/TCP   21m
service/portal-pre   NodePort   10.152.183.47   &lt;none&gt;        8080:30002/TCP   26s
</code></pre><p>En este caso vemos que se han desplegado dos pods para el entorno de preproducción. También podemos ver que la web muestra su contenido, en este caso a través del puerto 30002:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">curl 10.89.130.31:30002
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">html</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">style</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">body</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="k">background-color</span><span class="p">:</span> <span class="mh">#fff</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">style</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">body</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">     <span class="p">&lt;</span><span class="nt">h1</span><span class="p">&gt;</span>Portal corporativo<span class="p">&lt;/</span><span class="nt">h1</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">     <span class="p">&lt;</span><span class="nt">h2</span><span class="p">&gt;</span>Entorno preproducción<span class="p">&lt;/</span><span class="nt">h2</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">body</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">html</span><span class="p">&gt;</span>
</span></span></code></pre></div><p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="resumen">
        Resumen
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/02/k8s-kustomize-intro/#resumen" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Resumen" href="#resumen">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Hemos visto la definición de recursos para desplegar en kubernetes una aplicación básica de la forma habitual.</p>
<p>A continuación hemos adaptado la estructura de este despliegue para crear una configuración base apta para <strong>kustomize</strong>.</p>
<p>Se ha definido un parche con <strong>kustomize</strong> sobre la configuración base para el despliegue de la aplicación en un supuesto <strong>entorno de desarrollo</strong>.</p>
<p>Se ha definido un segundo parche con <strong>kustomize</strong> para el despliegue en un supuesto <strong>entorno de producción</strong>.</p>
<p>Este esquema sería replicable para otros entornos y variantes de configuración, que podríamos desplegar de forma individual o en conjunto de forma simultánea.</p>
<p>Los archivos de recursos utilizados en este artículo se encuentran en <a class="gblog-markdown__link" href="https://gitlab.com/dvdcr/kustomize-basic-sample">https://gitlab.com/dvdcr/kustomize-basic-sample</a></p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="referencias">
        Referencias
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/02/k8s-kustomize-intro/#referencias" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Referencias" href="#referencias">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><ul>
<li><a class="gblog-markdown__link" href="https://kustomize.io/">https://kustomize.io/</a></li>
<li><a class="gblog-markdown__link" href="https://github.com/kubernetes-sigs/kustomize/">https://github.com/kubernetes-sigs/kustomize/</a></li>
<li><a class="gblog-markdown__link" href="https://learnk8s.io/templating-yaml-with-code">https://learnk8s.io/templating-yaml-with-code</a></li>
<li><a class="gblog-markdown__link" href="https://kubectl.docs.kubernetes.io/guides/introduction/kustomize/">https://kubectl.docs.kubernetes.io/guides/introduction/kustomize/</a></li>
<li><a class="gblog-markdown__link" href="https://github.com/kubernetes/community/blob/master/contributors/devel/sig-api-machinery/strategic-merge-patch.md">https://github.com/kubernetes/community/blob/master/contributors/devel/sig-api-machinery/strategic-merge-patch.md</a></li>
<li><a class="gblog-markdown__link" href="https://gitlab.com/dvdcr/kustomize-basic-sample">https://gitlab.com/dvdcr/kustomize-basic-sample</a></li>
</ul>]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://dvdcr.gitlab.io/tags/kubernetes" term="kubernetes" label="kubernetes" />
                            
                        
                    
                
            
        </entry>
    
        
        <entry>
            <title type="html"><![CDATA[Configurar enchufe Zigbee TS011F con zigbee2mqtt]]></title>
            <link href="https://dvdcr.gitlab.io/posts/2022/02/iot-zigbee-enchufe-TS011F-plug-3/?utm_source=atom_feed" rel="alternate" type="text/html"  hreflang="en" />
            <id>https://dvdcr.gitlab.io/posts/2022/02/iot-zigbee-enchufe-TS011F-plug-3/</id>
            
            <published>2022-02-20T00:00:00+01:00</published>
            <updated>2022-02-20T00:00:00+01:00</updated>
            
            
            <content type="html"><![CDATA[<p><img src="/img/iot-enchufe-tuya-TS011F-plug-3.png#center" alt=""  />
Este enchufe con conectividad <strong>Zigbee</strong> se puede encontrar bajo diversas marcas (Tuya, BlitzWolf o Vikefon). Es un modelo muy compacto, que teóricamente soporta una intensidad de 16A.
Está diseñado para funcionar con la aplicación <em>Tuya Smart Life</em>, pero veremos cómo configurarlo para su uso con <strong>zigbee2mqtt</strong>, lo que abre el paso para su control mediante scripts o programas propios y a la integración plataformas como  <strong>Home Assistant</strong>, pero sin requerir de servicios externos.</p>
<div class="gblog-post__anchorwrap">
    <h2 id="requisitos-previos">
        Requisitos previos
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/02/iot-zigbee-enchufe-TS011F-plug-3/#requisitos-previos" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Requisitos previos" href="#requisitos-previos">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Debemos disponer de los siguientes componentes ya instalados y configurados:</p>
<ul>
<li><strong>Broker MQTT</strong>. En <a class="gblog-markdown__link" href="https://dvdcr.gitlab.io/posts/2022/01/iot-rpi-mqtt/">este artículo</a> se explica cómo instalar <strong>mosquitto</strong> en una Raspberry pi.</li>
<li><strong>Zigbee2mqtt</strong>. La instalación y configuración básica se describen en <a class="gblog-markdown__link" href="https://dvdcr.gitlab.io/posts/2022/02/iot-rpi-zigbee2mqtt/">este artículo</a></li>
</ul>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="emparejamiento">
        Emparejamiento
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/02/iot-zigbee-enchufe-TS011F-plug-3/#emparejamiento" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Emparejamiento" href="#emparejamiento">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Para emparejar el dispositivo con nuestra pasarela, debemos pulsar el botón de encendido durante 5 segundos. El led del botón se pondrá a parpadear para indicar que está en el modo de emparejamiento. Cuando deja de parpadear puede indicarnos que ha finalizado el emparejamiento o que no ha sido capaz de realizarlo. Según la documentación, se indica que si al dejar de parpadear el led queda en color rojo, significa que el emparejamiento se ha realizado correctamente, y si queda en color azul, significa que el emparejamiento no se ha realizado correctamente. En mi caso el led no se encendía tras el emparejamiento correcto.
La forma más clara de verificar el emparejamiento es revisando los logs de <strong>zigbee2mqtt</strong>. Con la configuración del <a class="gblog-markdown__link" href="https://dvdcr.gitlab.io/posts/2022/02/iot-rpi-zigbee2mqtt/">artículo al que hemos hecho referencia</a>, el log principal es el archivo <code>/opt/zigbee2mqtt/data/logs/zigbee2mqtt.log</code>. Si entramos a nuestra Raspberry pi o servidor que haga de pasarela y consultamos los logs:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">tail -f /opt/zigbee2mqtt/data/logs/zigbee2mqtt.log
</span></span></code></pre></div><p>Veremos en este caso que el emparejamiento ha tenido éxito por el mensaje <em>device has successfully been paired</em>, además de otra información del dispositivo:</p>
<pre tabindex="0"><code>Zigbee2MQTT:info  2022-01-20 22:21:39: Device &#39;0xa4c138c495df5e80&#39; joined
Zigbee2MQTT:info  2022-01-20 22:21:39: MQTT publish: topic &#39;zigbee2mqtt/bridge/event&#39;, payload &#39;{&#34;data&#34;:{&#34;friendly_name&#34;:&#34;0xa4c138c495df5e80&#34;,&#34;ieee_address&#34;:&#34;0xa4c138c495df5e80&#34;},&#34;type&#34;:&#34;device_joined&#34;}&#39;
Zigbee2MQTT:info  2022-01-20 22:21:39: Starting interview of &#39;0xa4c138c495df5e80&#39;
Zigbee2MQTT:info  2022-01-20 22:21:39: MQTT publish: topic &#39;zigbee2mqtt/bridge/event&#39;, payload &#39;{&#34;data&#34;:{&#34;friendly_name&#34;:&#34;0xa4c138c495df5e80&#34;,&#34;ieee_address&#34;:&#34;0xa4c138c495df5e80&#34;,&#34;status&#34;:&#34;started&#34;},&#34;type&#34;:&#34;device_interview&#34;}&#39;
Zigbee2MQTT:info  2022-01-20 22:21:39: MQTT publish: topic &#39;zigbee2mqtt/bridge/event&#39;, payload &#39;{&#34;data&#34;:{&#34;friendly_name&#34;:&#34;0xa4c138c495df5e80&#34;,&#34;ieee_address&#34;:&#34;0xa4c138c495df5e80&#34;},&#34;type&#34;:&#34;device_announce&#34;}&#39;
Zigbee2MQTT:info  2022-01-20 22:21:40: Successfully interviewed &#39;0xa4c138c495df5e80&#39;, device has successfully been paired
Zigbee2MQTT:info  2022-01-20 22:21:40: Device &#39;0xa4c138c495df5e80&#39; is supported, identified as: TuYa Smart plug (with power monitoring by polling) (TS011F_plug_3)
Zigbee2MQTT:info  2022-01-20 22:21:40: MQTT publish: topic &#39;zigbee2mqtt/bridge/event&#39;, payload &#39;{&#34;data&#34;:{&#34;definition&#34;:{&#34;description&#34;:&#34;Smart plug (with power monitoring by polling)&#34;,&#34;exposes&#34;:[{&#34;features&#34;:[{&#34;access&#34;:7,&#34;description&#34;:&#34;On/off state of the switch&#34;,&#34;name&#34;:&#34;state&#34;,&#34;property&#34;:&#34;state&#34;,&#34;type&#34;:&#34;binary&#34;,&#34;value_off&#34;:&#34;OFF&#34;,&#34;value_on&#34;:&#34;ON&#34;,&#34;value_toggle&#34;:&#34;TOGGLE&#34;}],&#34;type&#34;:&#34;switch&#34;},{&#34;access&#34;:1,&#34;description&#34;:&#34;Instantaneous measured power&#34;,&#34;name&#34;:&#34;power&#34;,&#34;property&#34;:&#34;power&#34;,&#34;type&#34;:&#34;numeric&#34;,&#34;unit&#34;:&#34;W&#34;},{&#34;access&#34;:1,&#34;description&#34;:&#34;Instantaneous measured electrical current&#34;,&#34;name&#34;:&#34;current&#34;,&#34;property&#34;:&#34;current&#34;,&#34;type&#34;:&#34;numeric&#34;,&#34;unit&#34;:&#34;A&#34;},{&#34;access&#34;:1,&#34;description&#34;:&#34;Measured electrical potential value&#34;,&#34;name&#34;:&#34;voltage&#34;,&#34;property&#34;:&#34;voltage&#34;,&#34;type&#34;:&#34;numeric&#34;,&#34;unit&#34;:&#34;V&#34;},{&#34;access&#34;:1,&#34;description&#34;:&#34;Sum of consumed energy&#34;,&#34;name&#34;:&#34;energy&#34;,&#34;property&#34;:&#34;energy&#34;,&#34;type&#34;:&#34;numeric&#34;,&#34;unit&#34;:&#34;kWh&#34;},{&#34;access&#34;:3,&#34;description&#34;:&#34;Recover state after power outage&#34;,&#34;name&#34;:&#34;power_outage_memory&#34;,&#34;property&#34;:&#34;power_outage_memory&#34;,&#34;type&#34;:&#34;enum&#34;,&#34;values&#34;:[&#34;on&#34;,&#34;off&#34;,&#34;restore&#34;]},{&#34;access&#34;:1,&#34;description&#34;:&#34;Link quality (signal strength)&#34;,&#34;name&#34;:&#34;linkquality&#34;,&#34;property&#34;:&#34;linkquality&#34;,&#34;type&#34;:&#34;numeric&#34;,&#34;unit&#34;:&#34;lqi&#34;,&#34;value_max&#34;:255,&#34;value_min&#34;:0}],&#34;model&#34;:&#34;TS011F_plug_3&#34;,&#34;options&#34;:[{&#34;access&#34;:2,&#34;description&#34;:&#34;This device does not support reporting electric measurements so it is polled instead. The default poll interval is 60 seconds, set to -1 to disable.&#34;,&#34;name&#34;:&#34;measurement_poll_interval&#34;,&#34;property&#34;:&#34;measurement_poll_interval&#34;,&#34;type&#34;:&#34;numeric&#34;,&#34;value_min&#34;:-1}],&#34;supports_ota&#34;:false,&#34;vendor&#34;:&#34;TuYa&#34;},&#34;friendly_name&#34;:&#34;0xa4c138c495df5e80&#34;,&#34;ieee_address&#34;:&#34;0xa4c138c495df5e80&#34;,&#34;status&#34;:&#34;successful&#34;,&#34;supported&#34;:true},&#34;type&#34;:&#34;device_interview&#34;}&#39;
Zigbee2MQTT:info  2022-01-20 22:21:40: Configuring &#39;0xa4c138c495df5e80&#39;
Zigbee2MQTT:info  2022-01-20 22:21:40: Successfully configured &#39;0xa4c138c495df5e80&#39;
Zigbee2MQTT:info  2022-01-20 22:21:44: MQTT publish: topic &#39;zigbee2mqtt/0xa4c138c495df5e80&#39;, payload &#39;{&#34;energy&#34;:0,&#34;linkquality&#34;:92}&#39;
Zigbee2MQTT:info  2022-01-20 22:21:45: MQTT publish: topic &#39;zigbee2mqtt/0xa4c138c495df5e80&#39;, payload &#39;{&#34;energy&#34;:0,&#34;linkquality&#34;:89,&#34;state&#34;:&#34;OFF&#34;}&#39;
</code></pre><p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="renombrar-dispositivo">
        Renombrar dispositivo
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/02/iot-zigbee-enchufe-TS011F-plug-3/#renombrar-dispositivo" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Renombrar dispositivo" href="#renombrar-dispositivo">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Todos los dispositivos <strong>Zigbee</strong> tienen un identificador único hexadecimal, que es el nombre que se les asigna por defecto en <strong>zigbee2mqtt</strong>, pero podemos asignar un <code>friendly_name</code> fácil de recordar, por ejemplo <code>enchufe</code>. Para ello debemos en primer lugar parar el servicio <strong>zigbee2mqtt</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo systemctl stop zigbee2mqtt
</span></span></code></pre></div><p>Luego modificamos el archivo <code>configuration.yaml</code> de <strong>zigbee2mqtt</strong> para especificar el nuevo nombre:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">devices</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">&#39;0xa4c138c495df5e80&#39;</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">friendly_name</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;enchufe&#39;</span><span class="w">
</span></span></span></code></pre></div><p>y arrancamos el servicio <strong>zigbee2mqtt</strong> para que haga efecto el cambio:</p>
<pre tabindex="0"><code>sudo systemctl start zigbee2mqtt
</code></pre><p>Ahora podremos hacer pruebas directamente con mensajes MQTT utilizando el <strong>topic</strong> <code>zigbee2mqtt/enchufe</code>.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="encendido-y-apagado-del-enchufe">
        Encendido y apagado del enchufe
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/02/iot-zigbee-enchufe-TS011F-plug-3/#encendido-y-apagado-del-enchufe" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Encendido y apagado del enchufe" href="#encendido-y-apagado-del-enchufe">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>La manipulación del enchufe la podemos hacer publicando mensajes en el servidor <strong>MQTT</strong> en el <strong>topic</strong> <code>zigbee2mqtt/enchufe/set</code>, incluyendo en el <strong>mensaje</strong> o <strong>payload</strong> un texto en formato <em>JSON</em> que tendrá el contenido <code>{&quot;state&quot;: &quot;&lt;estado&gt;&quot;}</code>, donde <code>&lt;estado&gt;</code> tendrá diferentes valores según la operación que queramos realizar.</p>
<p>Las llamadas que vemos a continuación se van a realizar desde la propia <em>Raspberry pi</em> donde se está ejecutando <strong>zigbee2mqtt</strong> y el <strong>broker MQTT</strong>, de ahí que se utilice <code>localhost</code> en el parámetro <code>-h</code> que indica el nombre o dirección del host <strong>MQTT</strong>. Si accedemos desde otro equipo tendremos que poner la dirección IP de la máquina donde se esté ejecutando el <strong>broker MQTT</strong>.</p>
<p>Para <strong>encender</strong>, el <strong>payload</strong> deberá ser <code>{&quot;state&quot;: &quot;ON&quot;}</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">mosquitto_pub -h localhost -p <span class="m">1883</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-u <span class="nv">$MQTTUSER</span> -P <span class="nv">$MQTTPASS</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-t <span class="s2">&#34;zigbee2mqtt/enchufe/set&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-m <span class="s1">&#39;{&#34;state&#34;: &#34;ON&#34;}&#39;</span>
</span></span></code></pre></div><p>Para <strong>apagar</strong>, el <strong>payload</strong> deberá ser <code>{&quot;state&quot;: &quot;OFF&quot;}</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">mosquitto_pub -h localhost -p <span class="m">1883</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-u <span class="nv">$MQTTUSER</span> -P <span class="nv">$MQTTPASS</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-t <span class="s2">&#34;zigbee2mqtt/enchufe/set&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-m <span class="s1">&#39;{&#34;state&#34;: &#34;OFF&#34;}&#39;</span>
</span></span></code></pre></div><p>Para <strong>cambiar al estado contrario</strong>, el <strong>payload</strong> deberá ser <code>{&quot;state&quot;: &quot;TOGGLE&quot;}</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">mosquitto_pub -h localhost -p <span class="m">1883</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-u <span class="nv">$MQTTUSER</span> -P <span class="nv">$MQTTPASS</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-t <span class="s2">&#34;zigbee2mqtt/enchufe/set&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-m <span class="s1">&#39;{&#34;state&#34;: &#34;TOGGLE&#34;}&#39;</span>
</span></span></code></pre></div><p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="configuración">
        Configuración
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/02/iot-zigbee-enchufe-TS011F-plug-3/#configuración" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Configuración" href="#configuraci%c3%b3n">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Este dispositivo admite algunas opciones de configuración que permiten por ejemplo, calibrar las mediciones o indicar cada cuánto tiempo queremos que informe del estado actual. La configuración se realiza a través de las siguientes <strong>variables de configuración</strong>:</p>
<ul>
<li><strong>measurement_poll_interval</strong>. Desde el último trimestre de 2021, <strong>Tuya</strong> ha desactivado el envío automático de la información de potencia, voltaje y corriente. Los dispositivos afectados por esta limitación se detectan como <em>TS011F_plug_3</em> en lugar de <em>TS011F_plug_1</em> como ocurría con las versiones previas. Para solucionar esto hay que preguntar (<em>poll</em>) al dispositivo de forma activa para obtener la información. El intervalo entre consultas se puede controlar a través de la variable <strong>measurement_poll_intervall</strong>. Si no se especifica un valor, no se realiza la consulta de estos valores. Si queremos desactivarla hay que especificar el valor <code>-1</code>.</li>
<li><strong>power_calibration</strong>. Calibra el valor de la <strong>potencia</strong>, mediante un valor numérico que supone un porcentaje de corrección y que se aplica en la siguiente lectura.</li>
<li><strong>current_calibration</strong>. Calibra el valor de la <em><strong>corriente</strong></em>, mediante un valor numérico que supone un porcentaje de corrección y que se aplica en la siguiente lectura.</li>
<li><strong>voltage_calibration</strong>. Calibra el valor del <strong>voltaje</strong>, mediante un valor numérico que supone un porcentaje de corrección y que se aplica en la siguiente lectura.</li>
</ul>
<p>Las opciones se pueden indicar añadiéndolas en el archivo <code>configuration.yaml</code> dentro de la sección <code>devices</code>, en el dispositivo correspondiente, realizando el reinicio del servicio <strong>zigbee2mqtt</strong> tras el cambio:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vim" data-lang="vim"><span class="line"><span class="cl"><span class="nx">devices</span>:
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;0xa4c138c495df5e80&#39;</span>:
</span></span><span class="line"><span class="cl">    <span class="nx">friendly_name</span>: <span class="nx">enchufe</span>
</span></span><span class="line"><span class="cl">    <span class="nx">measurement_poll_interval</span>: <span class="s1">&#39;60&#39;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">power_calibration</span>: <span class="m">5</span>
</span></span><span class="line"><span class="cl">    <span class="nx">current_calibration</span>: <span class="m">1</span>
</span></span><span class="line"><span class="cl">    <span class="nx">voltage_calibration</span>: <span class="m">2</span>
</span></span></code></pre></div><p>Otra forma de ajustar la configuración del dispositivo es enviar <strong>payloads</strong> al <strong>topic</strong> <code>zigbee2mqtt/bridge/request/device/options</code> con el contenido <code>{&quot;id&quot;: &quot;&lt;friendly_name&gt;&quot;, &quot;options&quot;:{&quot;&lt;nombre_opcion&gt;&quot;:&quot;&lt;valor&gt;&quot;}}</code>.</p>
<p>Actualización del <strong>intervalo de tiempo transcurrido entre medidas</strong> a 30 segundos:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">mosquitto_pub -h localhost -p <span class="m">1883</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-u <span class="nv">$MQTTUSER</span> -P <span class="nv">$MQTTPASS</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-t <span class="s2">&#34;zigbee2mqtt/bridge/request/device/options&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-m <span class="s1">&#39;{&#34;id&#34;: &#34;enchufe&#34;, &#34;options&#34;:{&#34;measurement_poll_interval&#34;:&#34;30&#34;}}&#39;</span> 
</span></span></code></pre></div><p>Calibración del <strong>voltaje</strong> para incrementarlo en un 2%:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">mosquitto_pub -h localhost -p <span class="m">1883</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-u <span class="nv">$MQTTUSER</span> -P <span class="nv">$MQTTPASS</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-t <span class="s2">&#34;zigbee2mqtt/bridge/request/device/options&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-m <span class="s1">&#39;{&#34;id&#34;: &#34;enchufe&#34;, &#34;options&#34;:{&#34;voltage_calibration&#34;:&#34;2&#34;}}&#39;</span> 
</span></span></code></pre></div><p>Calibración de la <strong>corriente</strong> para incrementarla en un 1%:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">mosquitto_pub -h localhost -p <span class="m">1883</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-u <span class="nv">$MQTTUSER</span> -P <span class="nv">$MQTTPASS</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-t <span class="s2">&#34;zigbee2mqtt/bridge/request/device/options&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-m <span class="s1">&#39;{&#34;id&#34;: &#34;enchufe&#34;, &#34;options&#34;:{&#34;current_calibration&#34;:&#34;1&#34;}}&#39;</span> 
</span></span></code></pre></div><p>Calibración de la <strong>potencia</strong> para reducirla en un 1%:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">mosquitto_pub -h localhost -p <span class="m">1883</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-u <span class="nv">$MQTTUSER</span> -P <span class="nv">$MQTTPASS</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-t <span class="s2">&#34;zigbee2mqtt/bridge/request/device/options&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-m <span class="s1">&#39;{&#34;id&#34;: &#34;enchufe&#34;, &#34;options&#34;:{&#34;power_calibration&#34;:&#34;-1&#34;}}&#39;</span> 
</span></span></code></pre></div><p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="lectura-de-valores">
        Lectura de valores
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/02/iot-zigbee-enchufe-TS011F-plug-3/#lectura-de-valores" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Lectura de valores" href="#lectura-de-valores">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Para leer los parámetros actuales de estado y consumo del dispositivo podemos utilizar <code>mosquitto_sub</code>, aunque primero tendremos que publicar inicialmente en el <strong>topic</strong> <code>zigbee2mqtt/&lt;friendly_name&gt;/state</code> el mensaje <code>{&quot;state&quot;:&quot;&quot;}</code> (a través de <code>mosquitto_pub</code>):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">mosquitto_pub -h localhost -p <span class="m">1883</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-u <span class="nv">$MQTTUSER</span> -P <span class="nv">$MQTTPASS</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-t <span class="s2">&#34;zigbee2mqtt/enchufe/state&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-m <span class="s1">&#39;{&#34;state&#34;: &#34;&#34;}&#39;</span> 
</span></span></code></pre></div><p>Una vez hecho eso, veremos en los <em>logs</em> mensajes con los valores que devuelve el dispositivo:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">tail -f /opt/zigbee2mqtt/data/log/zigbee2mqtt.log 
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vim" data-lang="vim"><span class="line"><span class="cl"><span class="nx">info</span>  <span class="m">2022-02-14</span> <span class="m">22</span>:<span class="m">30</span>:<span class="m">30</span>: <span class="nx">MQTT</span> <span class="nx">publish</span>: <span class="nx">topic</span> <span class="s1">&#39;zigbee2mqtt/enchufe&#39;</span><span class="p">,</span> <span class="nx">payload</span> <span class="s1">&#39;{&#34;current&#34;:0.14,&#34;energy&#34;:0,&#34;linkquality&#34;:72,&#34;power&#34;:13,&#34;power_outage_memory&#34;:null,&#34;state&#34;:&#34;ON&#34;,&#34;voltage&#34;:225}&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nx">ltage</span>&#34;:<span class="m">225</span>}&#39;
</span></span></code></pre></div><p>La información recibida es un mensaje <em>JSON</em> con los siguientes parámetros:</p>
<ul>
<li><strong>power</strong>. Medida instantánea de la <strong>potencia</strong>. Es un valor numérico cuya unidad es el <em>Watio (W)</em>.</li>
<li><strong>current</strong>. Medida instantánea de la <strong>corriente</strong>. Es un valor numérico cuya unidad es el <em>Amperio (A)</em>.</li>
<li><strong>voltage</strong>. Medida instantánea de <strong>potencial electríco</strong>. Es un valor numérico cuya unidad es el <em>Voltio (V)</em>.</li>
<li><strong>energy</strong>. Suma de <strong>energía consumida</strong>. La unidad de este valor es el <em>Kilowatio-hora (KWh)</em>.</li>
<li><strong>power_outage_memory</strong>. Indica el <strong>estado de activación</strong> en que quedará el echufe tras un <strong>corte de luz</strong>. Para escribir un valor nuevo, hay que escribir en el <strong>topic</strong> <code>zigbee2mqtt/&lt;friendly_name&gt;/set</code> un mensaje con el valor <code>{&quot;power_outage_memory&quot;: &lt;VALOR&gt;}</code>. Los posibles valores son: <code>on</code> (encendido), <code>off</code> (apagado) y <code>restore</code> (estado anterior).</li>
<li><strong>linkquality</strong>. Fuerza de la señal. El valor mínimo es 0 y el máximo 255. La unidad de este valor es <strong>lqi</strong> (<em>Link Quality Indicator</em>).</li>
</ul>
<p>Actualización del <strong>estado de activación</strong> (<em>power_outage_memory</em>):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">mosquitto_pub -h localhost -p <span class="m">1883</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-u <span class="nv">$MQTTUSER</span> -P <span class="nv">$MQTTPASS</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-t <span class="s2">&#34;zigbee2mqtt/enchufe/set&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-m <span class="s1">&#39;{&#34;power_outage_memory&#34;: &#34;restore&#34;}&#39;</span> 
</span></span></code></pre></div><p>En los logs podremos ver cómo aparece el valor de éste parámetro:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">tail -f /opt/zigbee2mqtt/data/log/zigbee2mqtt.log 
</span></span></code></pre></div><pre tabindex="0"><code>info  2022-02-14 22:48:21: MQTT publish: topic &#39;zigbee2mqtt/enchufe&#39;, payload &#39;{&#34;current&#34;:0,&#34;energy&#34;:0.01,&#34;linkquality&#34;:129,&#34;power&#34;:0,&#34;power_outage_memory&#34;:&#34;restore&#34;,&#34;state&#34;:&#34;OFF&#34;,&#34;voltage&#34;:230}&#39;
</code></pre><p>Al margen de los logs, podemos leer los valores de la información que muestra el enchufe utilizando <code>mosquitto_sub</code> con el siguiente comando:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">mosquitto_sub -C <span class="m">1</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-h localhost -p <span class="m">1883</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-u <span class="nv">$MQTTUSER</span> -P <span class="nv">$MQTTPASS</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-t <span class="s2">&#34;zigbee2mqtt/enchufe&#34;</span>
</span></span></code></pre></div><pre tabindex="0"><code>{&#34;current&#34;:0.09,&#34;energy&#34;:0.01,&#34;linkquality&#34;:120,&#34;power&#34;:13,&#34;power_outage_memory&#34;:&#34;restore&#34;,&#34;state&#34;:&#34;ON&#34;,&#34;voltage&#34;:222}
</code></pre><p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="resumen">
        Resumen
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/02/iot-zigbee-enchufe-TS011F-plug-3/#resumen" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Resumen" href="#resumen">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Disponiendo de un <strong>gateway Zigbee</strong> y un <strong>broker MQTT</strong> podemos emparejar un enchufe inteligente, configurarlo y manipularlo a través de la publicación y suscripción a <strong>topics</strong> <strong>MQTT</strong>. Esto nos permite gestionar el enchufe desde scripts y consultar detalles de consumo, voltaje y corriente.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="referencias">
        Referencias
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/02/iot-zigbee-enchufe-TS011F-plug-3/#referencias" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Referencias" href="#referencias">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><ul>
<li><a class="gblog-markdown__link" href="https://www.zigbee2mqtt.io/devices/TS011F_plug_3.html">https://www.zigbee2mqtt.io/devices/TS011F_plug_3.html</a></li>
<li><a class="gblog-markdown__link" href="https://www.zigbee2mqtt.io/guide/usage/mqtt_topics_and_messages.html">https://www.zigbee2mqtt.io/guide/usage/mqtt_topics_and_messages.html</a></li>
</ul>]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://dvdcr.gitlab.io/tags/IoT" term="IoT" label="IoT" />
                            
                        
                    
                
            
        </entry>
    
        
        <entry>
            <title type="html"><![CDATA[Utilizar Raspberry Pi como Gateway Zigbee]]></title>
            <link href="https://dvdcr.gitlab.io/posts/2022/02/iot-rpi-zigbee2mqtt/?utm_source=atom_feed" rel="alternate" type="text/html"  hreflang="en" />
            <id>https://dvdcr.gitlab.io/posts/2022/02/iot-rpi-zigbee2mqtt/</id>
            
            <published>2022-02-13T00:00:00+01:00</published>
            <updated>2022-02-13T00:00:00+01:00</updated>
            
            
            <content type="html"><![CDATA[<p><img src="/img/zigbee-rpi.svg#center" alt=""  /></p>
<p>El uso de dispositivos de domótica que funcionan bajo protocolo <a class="gblog-markdown__link" href="https://es.wikipedia.org/wiki/Zigbee">Zigbee</a> requiere para su conexión de un <strong>gateway Zigbee</strong>, un dispositivo hardware que sirve para interconectarlos. Podemos recurrir a dispositivos comerciales, que suelen estar basados en soluciones propietarias cerradas a un conjunto de dispositivos concretos de una marca, o montar nuestro propio <strong>gateway Zigbee</strong> utilizando el software <strong><a class="gblog-markdown__link" href="https://www.zigbee2mqtt.io">zigbee2mqtt</a></strong>, un adaptador <strong>USB Zigbee</strong> y una <strong>Raspberry Pi</strong>, PC o dispositivo similar que ejecute GNU/LINUX para realizar esta función.</p>
<p>En este caso veremos como montar esta solución utilizando una Raspberry Pi 3.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="modelos-de-adaptador-usb-zigbee">
        Modelos de Adaptador USB Zigbee
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/02/iot-rpi-zigbee2mqtt/#modelos-de-adaptador-usb-zigbee" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Modelos de Adaptador USB Zigbee" href="#modelos-de-adaptador-usb-zigbee">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Para obtener un buen rendimiento, la recomendación de <code>zigbee2mqtt</code> es que se utilice cualquier adaptador <strong>USB Zigbee</strong> basado en los chips CC2652 y CC1352 de Texas Instruments. Hay otros dispositivos soportados que pueden tener alguna limitación o que necesitan que el firmware sea actualizado. La lista completa está descrita en <a class="gblog-markdown__link" href="https://www.zigbee2mqtt.io/guide/adapters/">https://www.zigbee2mqtt.io/guide/adapters/</a> y se incluyen enlaces con las instrucciones de actualización del firmware para algunos de ellos.
En este caso vamos a utilizar un <a class="gblog-markdown__link" href="https://sonoff.tech/product/diy-smart-switch/sonoff-dongle-plus/">Sonoff Zigbee 3.0 USB Dongle Plus</a>, un dispositivo basado en el chip CC2652P, que se encuentra dentro de los recomendados, y cuyo firmware de serie es compatible con <strong>zigbee2mqtt</strong>.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="detección-del-adaptador">
        Detección del adaptador
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/02/iot-rpi-zigbee2mqtt/#detección-del-adaptador" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Detección del adaptador" href="#detecci%c3%b3n-del-adaptador">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Conectamos el adaptador a uno de los puertos de la Raspberry pi y desde un terminal comprobamos si se ha detectado el dispositivo, lanzando el siguiente comando:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">lsusb <span class="p">|</span> grep CP210
</span></span></code></pre></div><pre tabindex="0"><code>Bus 001 Device 005: ID 10c4:ea60 Silicon Labs CP210x UART Bridge
</code></pre><p>El dispositivo debe aparecer en el directorio <code>/dev</code>. Dependiendo del modelo, puede aparecer como <strong>ttyACM0</strong> o bien, como es nuestro caso como <strong>ttyUSB0</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">ls -l /dev/ttyUSB0
</span></span></code></pre></div><pre tabindex="0"><code>crw-rw----. 1 root dialout 188, 0 feb  7 22:21 /dev/ttyUSB0
</code></pre><p>Si no lo localizamos, podemos lanzar la siguiente instrucción:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">ls -l /dev/serial/by-id
</span></span></code></pre></div><pre tabindex="0"><code>total 0
lrwxrwxrwx. 1 root root 13 feb  7 22:21 usb-ITead_Sonoff_Zigbee_3.0_USB_Dongle_Plus_c6edf6bd90c8eb119099c1c3de5b81b4-if00-port0 -&gt; ../../ttyUSB0
</code></pre><p>Nos devolverá una descripción más larga que se corresponde a un enlace simbólico que apuntará al dispositivo anterior.</p>
<p>Tenemos que disponer de permisos de escritura en el puerto que corresponde al dispositivo. Lo podemos comprobar con el siguiente comando que devolverá <code>success</code> si todo está correcto:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">test</span> -w  /dev/ttyUSB0 <span class="o">&amp;&amp;</span> <span class="nb">echo</span> success <span class="o">||</span> <span class="nb">echo</span> failure
</span></span></code></pre></div><pre tabindex="0"><code>success
</code></pre><p>Si el resultado es <code>failure</code> hay que dar permisos al usuario con el que estamos trabajando, asignándolo al grupo <code>dialout</code> o al grupo al que pertenezca el archivo del dispositivo (<code>/dev/ttyUSB0</code> en nuestro caso):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">usermod -a -G dialout <span class="nv">$USER</span>
</span></span><span class="line"><span class="cl">newgrp dialout
</span></span></code></pre></div><p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="instalación-del-servidor-mqtt">
        Instalación del servidor MQTT
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/02/iot-rpi-zigbee2mqtt/#instalación-del-servidor-mqtt" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Instalación del servidor MQTT" href="#instalaci%c3%b3n-del-servidor-mqtt">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Debemos disponer de un broker <strong>MQTT</strong>, ya sea en la misma Raspberry pi o en otro dispositivo al que tengamos acceso por la red. El proceso de instalación del broker <strong>mosquitto</strong> en la Raspberry pi está descrito en <a class="gblog-markdown__link" href="https://dvdcr.gitlab.io/posts/2022/01/iot-rpi-mqtt/">este artículo</a></p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="instalación-de-zigbee2mqtt">
        Instalación de Zigbee2mqtt
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/02/iot-rpi-zigbee2mqtt/#instalación-de-zigbee2mqtt" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Instalación de Zigbee2mqtt" href="#instalaci%c3%b3n-de-zigbee2mqtt">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Para la configuración tenemos que instalar primero los paquetes <code>nodejs</code>, <code>npm</code>, <code>git</code>, <code>make</code>, <code>g++</code> y <code>gcc</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo apt-get install -y nodejs npm git make g++ gcc
</span></span></code></pre></div><p>Verificamos la versión de <strong>nodejs</strong>. Debe ser v10.X, v12.X, v14.X, v15.X or V16.X</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">node --version
</span></span></code></pre></div><pre tabindex="0"><code>v12.22.5
</code></pre><p>Verificamos la versión de <strong>npm</strong>. Debe ser 6.X o 7.X</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">npm --version
</span></span></code></pre></div><pre tabindex="0"><code>7.5.2
</code></pre><p>Una vez que tenemos instalados todos los paquetes en las versiones requeridas, procedemos a clonar el repositorio de <strong>zigbee2mqtt</strong> de <strong>Github</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">cd</span> /tmp
</span></span><span class="line"><span class="cl">git clone https://github.com/Koenkk/zigbee2mqtt.git
</span></span><span class="line"><span class="cl">sudo mv zigbee2mqtt /opt/zigbee2mqtt
</span></span></code></pre></div><p>Instalamos las dependencias del proyecto:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">cd</span> /opt/zigbee2mqtt
</span></span><span class="line"><span class="cl">npm ci
</span></span></code></pre></div><p>Antes de arrancar <strong>Zigbee2MQTT</strong> necesitamos editar el archivo de configuración <code>configuration.yaml</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">vi /opt/zigbee2mqtt/data/configuration.yaml
</span></span></code></pre></div><p>Debemos configurar la url y datos de autenticación del servidor <em>MQTT</em>. También debemos especificar la ruta donde se encuentra el adaptador USB Zigbee. El archivo quedaría de la siguiente forma:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vim" data-lang="vim"><span class="line"><span class="cl"># <span class="nx">MQTT</span> <span class="nx">settings</span>
</span></span><span class="line"><span class="cl"><span class="nx">homeassistant</span>: <span class="nx">false</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">permit_join</span>: <span class="nx">true</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">advanced</span>:
</span></span><span class="line"><span class="cl">    <span class="nx">network_key</span>: <span class="nx">GENERATE</span>
</span></span><span class="line"><span class="cl"><span class="nx">mqtt</span>:
</span></span><span class="line"><span class="cl">  # <span class="nx">MQTT</span> <span class="nx">base</span> <span class="nx">topic</span> <span class="nx">for</span> <span class="nx">Zigbee2MQTT</span> <span class="nx">MQTT</span> <span class="nx">messages</span>
</span></span><span class="line"><span class="cl">  <span class="nx">base_topic</span>: <span class="nx">zigbee2mqtt</span>
</span></span><span class="line"><span class="cl">  # <span class="nx">MQTT</span> <span class="nx">server</span> <span class="nx">URL</span>
</span></span><span class="line"><span class="cl">  <span class="nx">server</span>: <span class="s1">&#39;mqtt://localhost:1883&#39;</span>
</span></span><span class="line"><span class="cl">  # <span class="nx">MQTT</span> <span class="nx">server</span> <span class="nx">authentication</span><span class="p">,</span> <span class="nx">uncomment</span> <span class="k">if</span> <span class="nx">required</span>:
</span></span><span class="line"><span class="cl">  <span class="nx">user</span>: <span class="nx">zigbee</span>
</span></span><span class="line"><span class="cl">  <span class="nx">password</span>: <span class="nx">contraseña</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># <span class="nx">Serial</span> <span class="nx">settings</span>
</span></span><span class="line"><span class="cl"><span class="nx">serial</span>:
</span></span><span class="line"><span class="cl">  # <span class="nx">Location</span> <span class="nx">of</span> <span class="nx">the</span> <span class="nx">adapter</span> <span class="p">(</span><span class="nx">see</span> <span class="nx">first</span> <span class="nx">step</span> <span class="nx">of</span> <span class="nx">this</span> <span class="nx">guide</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="nx">port</span>: <span class="sr">/dev/</span><span class="nx">ttyUSB0</span>
</span></span></code></pre></div><p>Es recomendable utilizar una clave de red personalizada. Podemos hacer esto añadiendo lo siguiente al archivo <code>configuraiton.yaml</code>, que generá una clave de red en el siguiente arranque.</p>
<pre tabindex="0"><code>advanced:
    network_key: GENERATE
</code></pre><p>Guardamos el archivo y salimos del editor.</p>
<p>Ya podemos arrancar <strong>zigbee2mqtt</strong> manualmente:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">cd</span> /opt/zigbee2mqtt
</span></span><span class="line"><span class="cl">npm start
</span></span></code></pre></div><pre tabindex="0"><code>Building Zigbee2MQTT... (initial build), finished
Zigbee2MQTT:info  2022-01-20 19:10:30: Logging to console and directory: &#39;/opt/zigbee2mqtt/data/log/2022-01-20.19-10-26&#39; filename: log.txt
Zigbee2MQTT:info  2022-01-20 19:10:30: Starting Zigbee2MQTT version 1.22.2 (commit #1a0a9a6e)
Zigbee2MQTT:info  2022-01-20 19:10:30: Starting zigbee-herdsman (0.13.188)
Zigbee2MQTT:info  2022-01-20 19:10:38: zigbee-herdsman started (reset)
Zigbee2MQTT:info  2022-01-20 19:10:38: Coordinator firmware version: &#39;{&#34;meta&#34;:{&#34;maintrel&#34;:3,&#34;majorrel&#34;:2,&#34;minorrel&#34;:6,&#34;product&#34;:0,&#34;revision&#34;:20190608,&#34;transportrev&#34;:2},&#34;type&#34;:&#34;zStack12&#34;}&#39;
Zigbee2MQTT:info  2022-01-20 19:10:38: Currently 0 devices are joined:
Zigbee2MQTT:warn  2022-01-20 19:10:38: `permit_join` set to  `true` in configuration.yaml.
Zigbee2MQTT:warn  2022-01-20 19:10:38: Allowing new devices to join.
Zigbee2MQTT:warn  2022-01-20 19:10:38: Set `permit_join` to `false` once you joined all devices.
Zigbee2MQTT:info  2022-01-20 19:10:38: Zigbee: allowing new devices to join.
Zigbee2MQTT:info  2022-01-20 19:10:39: Connecting to MQTT server at mqtt://192.168.168.1:1883
Zigbee2MQTT:info  2022-01-20 19:10:39: Connected to MQTT server
Zigbee2MQTT:info  2022-01-20 19:10:39: MQTT publish: topic &#39;zigbee2mqtt/bridge/state&#39;, payload &#39;online&#39;
</code></pre><p>Una vez que comprobamos que arranca bien, podemos crear archivo de configuración de <strong>systemctl</strong> para que <strong>Zigbee2MQTT</strong> pueda arrancar como un <em>daemon</em>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo vi /etc/systemd/system/zigbee2mqtt.service
</span></span></code></pre></div><p>El contenido de ese archivo debe ser el siguiente:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vim" data-lang="vim"><span class="line"><span class="cl">[<span class="nx">Unit</span>]
</span></span><span class="line"><span class="cl"><span class="nx">Description</span><span class="p">=</span><span class="nx">zigbee2mqtt</span>
</span></span><span class="line"><span class="cl"><span class="nx">After</span><span class="p">=</span><span class="nx">network</span>.<span class="nx">target</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">[<span class="nx">Service</span>]
</span></span><span class="line"><span class="cl"><span class="nx">ExecStart</span><span class="p">=</span><span class="sr">/usr/</span><span class="nx">bin</span>/<span class="nx">npm</span> <span class="nx">start</span>
</span></span><span class="line"><span class="cl"><span class="nx">WorkingDirectory</span><span class="p">=</span><span class="sr">/opt/</span><span class="nx">zigbee2mqtt</span>
</span></span><span class="line"><span class="cl"># <span class="nx">Descomentar</span> <span class="nx">siguiente</span> <span class="nx">línea</span> <span class="nx">para</span> <span class="nx">mostrar</span> <span class="nx">logs</span> <span class="nx">de</span> <span class="nx">los</span> <span class="nx">mensajes</span> 
</span></span><span class="line"><span class="cl">#<span class="nx">StandardOutput</span><span class="p">=</span><span class="nx">inherit</span>
</span></span><span class="line"><span class="cl"># <span class="nx">La</span> <span class="nx">siguiente</span> <span class="nx">línea</span> <span class="nx">evita</span> <span class="nx">que</span> <span class="nx">se</span> <span class="nx">generen</span> <span class="nx">demasiados</span> <span class="nx">logs</span>
</span></span><span class="line"><span class="cl"><span class="nx">StandardOutput</span><span class="p">=</span><span class="nx">null</span>
</span></span><span class="line"><span class="cl"># <span class="nx">Or</span> <span class="nx">use</span> <span class="nx">StandardOutput</span><span class="p">=</span><span class="nx">null</span> <span class="k">if</span> <span class="nx">you</span> <span class="nx">don</span>&#39;<span class="nx">t</span> <span class="nx">want</span> <span class="nx">Zigbee2MQTT</span> <span class="nx">messages</span> <span class="nx">filling</span> <span class="nx">syslog</span><span class="p">,</span> <span class="nx">for</span> <span class="nx">more</span> <span class="nx">options</span> <span class="nx">see</span> <span class="nx">systemd</span>.<span class="nx">exec</span><span class="p">(</span><span class="m">5</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">StandardError</span><span class="p">=</span><span class="nx">inherit</span>
</span></span><span class="line"><span class="cl"><span class="nx">Restart</span><span class="p">=</span><span class="nx">always</span>
</span></span><span class="line"><span class="cl"><span class="nx">User</span><span class="p">=</span><span class="nx">pi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">[<span class="nx">Install</span>]
</span></span><span class="line"><span class="cl"><span class="nx">WantedBy</span><span class="p">=</span><span class="nx">multi</span><span class="p">-</span><span class="nx">user</span>.<span class="nx">target</span>
</span></span></code></pre></div><p>En caso de estar utilizando una Raspberry Pi 1 o Zero, hay que reemplazar <code>ExecStart=/usr/bin/npm start</code> por <code>ExecStart=/usr/local/bin/npm start</code>.</p>
<p>Si el sistema se está ejecutando desde una tarjeta SD, es recomendable minimizar el número de archivos de log, para prolongar la vida de la tarjeta. El servicio systemd con la opción <code>StandardOutput=inherit</code> dará como resultado el registro de todo dos veces, una en <em>journalctl</em> a través de la unidad systemd y otra a través del sistema de log predeterminado de Zigbee2MQTT. Podemos mantener sólo una copia de ellos utilizando <code>StandardOutput=null</code> en la unidad <strong>systemd</strong> y mantener sólo los logs que están dentro de <code>data/log</code>, o estaleciendo &lsquo;advanced.log_output = [&lsquo;console&rsquo;]<code>en la configuración de **Zigbee2MQTT** para mantener sólo los logs de *journalctl*. Complementando esto, podemos añadir algunos atributos en el archivo</code>configuration.yaml<code>para personalizar la ubicación, nivel de detalle y nombre de los archivos de log. En este caso optamos por un nivel de log</code>info<code>, guardar en el directorio en </code>/opt/zigbee2mqtt/data/log<code>en un archivo llamado</code>zigbee2mqtt.log`. El fragmento de parámetros de configuración sería:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vim" data-lang="vim"><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl"><span class="nx">advanced</span>:
</span></span><span class="line"><span class="cl">  <span class="nx">log_level</span>: <span class="nx">info</span>
</span></span><span class="line"><span class="cl">  <span class="nx">log_directory</span>: <span class="nx">data</span><span class="sr">/log/</span>
</span></span><span class="line"><span class="cl">  <span class="nx">log_file</span>: <span class="nx">zigbee2mqtt</span>.<span class="nx">log</span>
</span></span><span class="line"><span class="cl">  <span class="nx">log_rotation</span>: <span class="nx">true</span>
</span></span><span class="line"><span class="cl">  <span class="nx">log_output</span>:
</span></span><span class="line"><span class="cl">    <span class="p">-</span> <span class="nx">console</span>
</span></span><span class="line"><span class="cl">    <span class="p">-</span> <span class="nx">file</span>
</span></span><span class="line"><span class="cl">  <span class="nx">log_symlink_current</span>: <span class="nx">false</span>
</span></span></code></pre></div><p>Revisado todo esto, guardamos el archivo y salimos del editor.</p>
<p>Podemos comprobar que funciona la configuración arrancando el servicio:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo systemctl start zigbee2mqtt
</span></span></code></pre></div><p>Comprobamos el estado:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">systemctl status zigbee2mqtt.service
</span></span></code></pre></div><p>La salida debe mostrar que el servicio está activo:</p>
<pre tabindex="0"><code> zigbee2mqtt.service - zigbee2mqtt
     Loaded: loaded (/etc/systemd/system/zigbee2mqtt.service; disabled; vendor preset: enabled)
     Active: active (running) since Thu 2022-01-20 22:31:23 CET; 10s ago
</code></pre><p>Lo habilitamos para que inicie en el arranque del sistema de manera automática:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">systemctl <span class="nb">enable</span> zigbee2qtt.service
</span></span></code></pre></div><p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="actualizar-a-la-última-versión-de-zigbee2mqtt">
        Actualizar a la última versión de zigbee2mqtt
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/02/iot-rpi-zigbee2mqtt/#actualizar-a-la-última-versión-de-zigbee2mqtt" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Actualizar a la última versión de zigbee2mqtt" href="#actualizar-a-la-%c3%baltima-versi%c3%b3n-de-zigbee2mqtt">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>La actualización de versión de <strong>zigbee2mqtt</strong> consiste en los siguientes pasos</p>
<p>Parar <strong>zigbee2mqtt</strong> y entrar en el directorio donde se encuentra el código de <strong>github</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo systemctl stop zigbee2mqtt
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> /opt/zigbee2mqtt
</span></span></code></pre></div><p>Hacer backup de la configuración:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">cp -R data data-backup
</span></span></code></pre></div><p>Descargar el código más reciente de <strong>github</strong> y actualizar las dependencias:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">git checkout HEAD -- npm-shrinkwrap.json
</span></span><span class="line"><span class="cl">git pull
</span></span><span class="line"><span class="cl">npm ci
</span></span></code></pre></div><p>Restaurar la configuración:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">cp -R data-backup/* data
</span></span><span class="line"><span class="cl">rm -rf data-backup
</span></span></code></pre></div><p>Arrancar el servicio:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo systemctl start zigbee2mqtt
</span></span></code></pre></div><p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="configurar-sensor-de-temperatura-y-humedad">
        Configurar sensor de temperatura y humedad
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/02/iot-rpi-zigbee2mqtt/#configurar-sensor-de-temperatura-y-humedad" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Configurar sensor de temperatura y humedad" href="#configurar-sensor-de-temperatura-y-humedad">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p><img src="/img/sth-tuya-WSD500A.jpg#center" alt=""  /></p>
<p>Para comprobar el funcionamiento, vamos a emparejar un <strong>sensor de temperatura y humedad WD500A</strong> de <em>Tuya</em>, cuya documentación para <strong>Zigbee2mqtt</strong> se encuentra en <a class="gblog-markdown__link" href="https://www.zigbee2mqtt.io/devices/WSD500A.html">https://www.zigbee2mqtt.io/devices/WSD500A.html</a>.</p>
<p>Para ver todo el proceso, podemos consultar el archivo de log, que se encontrará en <code>/opt/zigbee2mqtt/data/log</code>, con el nombre que indicamos en nuestra configuración específica. Ejemplo:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">tail -f /opt/zigbee2mqtt/data/log/zigbee2mqtt.log
</span></span></code></pre></div><p>Para realizar el emparejamiento del dispositivo con pinchamos el botón reset durante 5 segundos, hasta que el led del sensor se pone a parpadear. <em>Zigbee2mqtt</em> lo detecta y muestra lo siguiente:</p>
<pre tabindex="0"><code>Zigbee2MQTT:info  2022-01-20 19:16:41: MQTT publish: topic &#39;zigbee2mqtt/bridge/event&#39;, payload &#39;{&#34;data&#34;:{&#34;friendly_name&#34;:&#34;0xa4c13831700f4951&#34;,&#34;ieee_address&#34;:&#34;0xa4c13831700f4951&#34;},&#34;type&#34;:&#34;device_announce&#34;}&#39;
Zigbee2MQTT:info  2022-01-20 19:16:45: Successfully interviewed &#39;0xa4c13831700f4951&#39;, device has successfully been paired
Zigbee2MQTT:info  2022-01-20 19:16:45: Device &#39;0xa4c13831700f4951&#39; is supported, identified as: TuYa Temperature &amp; humidity sensor (WSD500A)
Zigbee2MQTT:info  2022-01-20 19:16:45: MQTT publish: topic &#39;zigbee2mqtt/bridge/event&#39;, payload &#39;{&#34;data&#34;:{&#34;definition&#34;:{&#34;description&#34;:&#34;Temperature &amp; humidity sensor&#34;,&#34;exposes&#34;:[{&#34;access&#34;:1,&#34;description&#34;:&#34;Remaining battery in %&#34;,&#34;name&#34;:&#34;battery&#34;,&#34;property&#34;:&#34;battery&#34;,&#34;type&#34;:&#34;numeric&#34;,&#34;unit&#34;:&#34;%&#34;,&#34;value_max&#34;:100,&#34;value_min&#34;:0},{&#34;access&#34;:1,&#34;description&#34;:&#34;Measured temperature value&#34;,&#34;name&#34;:&#34;temperature&#34;,&#34;property&#34;:&#34;temperature&#34;,&#34;type&#34;:&#34;numeric&#34;,&#34;unit&#34;:&#34;°C&#34;},{&#34;access&#34;:1,&#34;description&#34;:&#34;Measured relative humidity&#34;,&#34;name&#34;:&#34;humidity&#34;,&#34;property&#34;:&#34;humidity&#34;,&#34;type&#34;:&#34;numeric&#34;,&#34;unit&#34;:&#34;%&#34;},{&#34;access&#34;:1,&#34;description&#34;:&#34;Voltage of the battery in millivolts&#34;,&#34;name&#34;:&#34;voltage&#34;,&#34;property&#34;:&#34;voltage&#34;,&#34;type&#34;:&#34;numeric&#34;,&#34;unit&#34;:&#34;mV&#34;},{&#34;access&#34;:1,&#34;description&#34;:&#34;Link quality (signal strength)&#34;,&#34;name&#34;:&#34;linkquality&#34;,&#34;property&#34;:&#34;linkquality&#34;,&#34;type&#34;:&#34;numeric&#34;,&#34;unit&#34;:&#34;lqi&#34;,&#34;value_max&#34;:255,&#34;value_min&#34;:0}],&#34;model&#34;:&#34;WSD500A&#34;,&#34;options&#34;:[{&#34;access&#34;:2,&#34;description&#34;:&#34;Number of digits after decimal point for temperature, takes into effect on next report of device.&#34;,&#34;name&#34;:&#34;temperature_precision&#34;,&#34;property&#34;:&#34;temperature_precision&#34;,&#34;type&#34;:&#34;numeric&#34;,&#34;value_max&#34;:3,&#34;value_min&#34;:0},{&#34;access&#34;:2,&#34;description&#34;:&#34;Calibrates the temperature value (absolute offset), takes into effect on next report of device.&#34;,&#34;name&#34;:&#34;temperature_calibration&#34;,&#34;property&#34;:&#34;temperature_calibration&#34;,&#34;type&#34;:&#34;numeric&#34;},{&#34;access&#34;:2,&#34;description&#34;:&#34;Number of digits after decimal point for humidity, takes into effect on next report of device.&#34;,&#34;name&#34;:&#34;humidity_precision&#34;,&#34;property&#34;:&#34;humidity_precision&#34;,&#34;type&#34;:&#34;numeric&#34;,&#34;value_max&#34;:3,&#34;value_min&#34;:0},{&#34;access&#34;:2,&#34;description&#34;:&#34;Calibrates the humidity value (absolute offset), takes into effect on next report of device.&#34;,&#34;name&#34;:&#34;humidity_calibration&#34;,&#34;property&#34;:&#34;humidity_calibration&#34;,&#34;type&#34;:&#34;numeric&#34;}],&#34;supports_ota&#34;:false,&#34;vendor&#34;:&#34;TuYa&#34;},&#34;friendly_name&#34;:&#34;0xa4c13831700f4951&#34;,&#34;ieee_address&#34;:&#34;0xa4c13831700f4951&#34;,&#34;status&#34;:&#34;successful&#34;,&#34;supported&#34;:true},&#34;type&#34;:&#34;device_interview&#34;}&#39;
Zigbee2MQTT:info  2022-01-20 19:16:48: MQTT publish: topic &#39;zigbee2mqtt/0xa4c13831700f4951&#39;, payload &#39;{&#34;linkquality&#34;:123,&#34;temperature&#34;:23.21}&#39;
Zigbee2MQTT:info  2022-01-20 19:16:48: MQTT publish: topic &#39;zigbee2mqtt/0xa4c13831700f4951&#39;, payload &#39;{&#34;humidity&#34;:42.01,&#34;linkquality&#34;:120,&#34;temperature&#34;:23.21}&#39;
</code></pre><p>Si volvemos a editar el archivo <code>configuration.yaml</code> de <strong>zigbee2mqtt</strong>, aparecerá lo siguiente:</p>
<pre tabindex="0"><code>devices:
  &#39;0xa4c13831700f4951&#39;:
    friendly_name: &#39;0xa4c13831700f4951&#39;
</code></pre><p>Podemos cambiar el atributo <code>friendly_name</code> por un nombre más descriptivo, como <code>sensor_habitación</code>. También podemos añadir otras propiedades que se encuentren dentro de la <a class="gblog-markdown__link" href="https://www.zigbee2mqtt.io/devices/WSD500A.html">documentación del sensor</a>, aunque de momento cambiaremos sólo el nombre:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vim" data-lang="vim"><span class="line"><span class="cl"><span class="nx">devices</span>:
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;0xa4c13831700f4951&#39;</span>:
</span></span><span class="line"><span class="cl">    <span class="nx">friendly_name</span>: <span class="nx">sensor_habitacion</span>
</span></span></code></pre></div><p>y reiniciamos el servicio para que se aplique el cambio:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo systemctl restart zigbee2mqtt
</span></span></code></pre></div><p>Una vez reiniciado el servicio y asignado un nombre fácil de reconocer, <strong>zigbee2mqtt</strong> se encarga de recibir la información del sensor y publicarla en el broker <strong>MQTT</strong> en un <strong>topic</strong>  llamado <code>zigbee2mqtt/friendly_name</code> en nuestro caso <code>zigbee2mqtt/sensor_habitacion</code>. Los datos que se publican son un objeto <strong>JSON</strong> que variará según el dispositivo. Para comprobar los datos que se están publicando en nuestro broker <strong>MQTT</strong> nos vamos a suscribir a ese <strong>topic</strong>. Si lo hacemos desde la propia RPi, pondremos en el parámetro <code>-h</code> el valor <code>localhost</code>. Si lo hacemos desde otro equipo pondremos la dirección IP de la RPi. Tendremos que especificar el usuario y contraseña que especificáramos durante la instalación de <strong>mosquitto</strong>. En este caso se utilizan un par de variables de entorno:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">mosquitto_sub -h localhost -p <span class="m">1883</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-u <span class="nv">$MQTTUSER</span> -P <span class="nv">$MQTTPASS</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-t <span class="s2">&#34;zigbee2mqtt/sensor_habitacion&#34;</span>
</span></span></code></pre></div><pre tabindex="0"><code>zigbee2mqtt/sensor_habitacion {&#34;battery&#34;:null,&#34;humidity&#34;:61.29,&#34;linkquality&#34;:69,&#34;temperature&#34;:20.39,&#34;voltage&#34;:null}
zigbee2mqtt/sensor_habitacion {&#34;battery&#34;:null,&#34;humidity&#34;:61.01,&#34;linkquality&#34;:72,&#34;temperature&#34;:20.39,&#34;voltage&#34;:null}
</code></pre><p>El tiempo que tardemos en recibir el valor depende de varios factores, como las características del sensor en concreto y si el ambiente está cambiando, ya que si la temperatura y/o humedad están estables, enviará datos con menos frecuencia.</p>
<p>Vemos que recibimos como respuesta datos en formato <strong>JSON</strong>, parejas <em>clave-valor</em> separadas por comas, y en las que se separa el nombre de parámetro de su valor por dos puntos. En este caso no se reciben valores en los parámetros <code>battery</code> y <code>voltage</code> (<code>null</code>). En cambio sí que se están enviando valores para los parámetros:</p>
<ul>
<li><code>linkquality</code>. Fuerza de la señal. Es un valor comprendido entre 0 y 255.</li>
<li><code>humidity</code>. Humedad relativa, expresada en tanto por ciento (%).</li>
<li><code>temperature</code>. Temperatura, expresada en grados centígrados.</li>
</ul>
<p>Es posible hacer algunos ajustes del sensor para personalizar la precisión de las mediciones o calibrar la temperatura y la humedad. Los parámetros que admite este sensor son:</p>
<ul>
<li><code>temperature_precision</code>. Número de dígitos decimales para la temperatura. El valor admitido va de 0 (ningún decimal) a 3 (3 decimales).</li>
<li><code>temperature_calibration</code>. Permite calibrar la temperatura, sumando o restando un valor numérico para ajustarlo al valor real que queramos obtener. Si nuestro sensor marca 1 grado menos, pondríamos un valor <code>1</code>. Si marca 2,5 grados de más, pondríamos <code>-2.5</code>.</li>
<li><code>humidity_precision</code>. Número de dígitos decimales para la humedad. El valor admitido va de 0 (ningún decimal) a 3 (3 decimales).</li>
<li><code>humidity_calibration</code>. Permite calibrar la humedad, sumando o restando un valor numérico para ajustarlo al valor real que queramos obtener.</li>
</ul>
<p>Para ajustar estos parámetros podemos añadir uno o varios de ellos en el archivo de configuración <code>/opt/zigbee2mqtt/data/configuration.yaml</code>, en la sección correspondiente al dispositivo. Un ejemplo de ello sería lo siguiente, para el sensor que hemos llamado <code>sensor_habitacion</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vim" data-lang="vim"><span class="line"><span class="cl"><span class="nx">devices</span>:
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;0xa4c13831700f4951&#39;</span>:
</span></span><span class="line"><span class="cl">    <span class="nx">friendly_name</span>: <span class="nx">sensor_habitacion</span>
</span></span><span class="line"><span class="cl">    <span class="nx">temperature_precision</span>: <span class="m">1</span>
</span></span><span class="line"><span class="cl">    <span class="nx">humidity_precision</span>: <span class="m">0</span>
</span></span><span class="line"><span class="cl">    <span class="nx">temperature_calibration</span>: <span class="m">2</span>.<span class="m">5</span>
</span></span><span class="line"><span class="cl">    <span class="nx">humidity_calibration</span>: <span class="m">3</span>
</span></span></code></pre></div><p>Le estamos indicando que queremos la temperatura con un decimal, la humedad sin decimales, que sume 2 grados y medio a la temperatura que recoja el sensor y que sume 3 tantos porcentuales a la humedad. Para que se apliquen estos cambios tenemos que reiniciar el servicio <code>zigbee2mqtt</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo systemctl restart zigbee2mqtt
</span></span></code></pre></div><p>Al cabo de unos minutos, veremos que los datos recibidos cambian de formato y de valor:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">mosquitto_sub -h localhost -p <span class="m">1883</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-u <span class="nv">$MQTTUSER</span> -P <span class="nv">$MQTTPASS</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-t <span class="s2">&#34;zigbee2mqtt/sensor_habitacion&#34;</span>
</span></span></code></pre></div><pre tabindex="0"><code>zigbee2mqtt/sensor_habitacion {&#34;battery&#34;:null,&#34;humidity&#34;:64,&#34;linkquality&#34;:93,&#34;temperature&#34;:22.8,&#34;voltage&#34;:null}
</code></pre><p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="anular-registro-de-un-dispositivo">
        Anular registro de un dispositivo
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/02/iot-rpi-zigbee2mqtt/#anular-registro-de-un-dispositivo" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Anular registro de un dispositivo" href="#anular-registro-de-un-dispositivo">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Enviar al topic <code>zigbee2mqtt/bridge/request/device/remove</code> el payload <code>{&quot;id&quot;: &quot;deviceID&quot;}</code>. Ejemplo:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">mosquitto_pub -h localhost -p <span class="m">1883</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-u <span class="nv">$MQTTUSER</span> -P <span class="nv">$MQTTPASS</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-t <span class="s2">&#34;zigbee2mqtt/bridge/request/device/remove&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-m <span class="s1">&#39;{&#34;id&#34;: &#34;0xa4c13831700f4950&#34;}&#39;</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">mosquitto_pub -h localhost -p <span class="m">1883</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-u <span class="nv">$MQTTUSER</span> -P <span class="nv">$MQTTPASS</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-t <span class="s2">&#34;zigbee2mqtt/bridge/config/force_remove&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-m <span class="s1">&#39;{&#34;id&#34;: &#34;0xa4c13831700f4950&#34;}&#39;</span>
</span></span></code></pre></div><p> </p>
<p>== Resumen</p>
<p>Como hemos visto, podemos recibir información de un sensor a través del comando mosquitto_sub, con lo que podríamos aprovechar esta información desde scripts. Además de esta posibilidad, una forma muy flexible de utilizar esta información es instalar <a class="gblog-markdown__link" href="https://www.home-assistant.io/">Home Assistant</a> e integrar nuestro <strong>gateway</strong> con esta aplicación. Nos permitirá mostrar de forma visual la información y aprovecharla junto con otros dispositivos para hacer automatizaciones, como por ejemplo, avisar a través de Telegram cuando la temperatura pase de un determinado valor, o encender el aire acondicionado.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="referencias">
        Referencias
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/02/iot-rpi-zigbee2mqtt/#referencias" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Referencias" href="#referencias">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><ul>
<li><a class="gblog-markdown__link" href="https://www.zigbee2mqtt.io/guide/adapters/">https://www.zigbee2mqtt.io/guide/adapters/</a></li>
<li><a class="gblog-markdown__link" href="https://www.zigbee2mqtt.io/guide/installation/01_linux.html">https://www.zigbee2mqtt.io/guide/installation/01_linux.html</a></li>
<li><a class="gblog-markdown__link" href="https://www.zigbee2mqtt.io/devices/WSD500A.html">https://www.zigbee2mqtt.io/devices/WSD500A.html</a></li>
<li><a class="gblog-markdown__link" href="https://zigbee2mqtt.discourse.group/t/how-can-i-unpair-devices/1041/3">https://zigbee2mqtt.discourse.group/t/how-can-i-unpair-devices/1041/3</a></li>
</ul>]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://dvdcr.gitlab.io/tags/IoT" term="IoT" label="IoT" />
                            
                        
                    
                
            
        </entry>
    
        
        <entry>
            <title type="html"><![CDATA[Consultar y gestionar almacenes de certificados de confianza Java]]></title>
            <link href="https://dvdcr.gitlab.io/posts/2022/02/java-truststore/?utm_source=atom_feed" rel="alternate" type="text/html"  hreflang="en" />
            <id>https://dvdcr.gitlab.io/posts/2022/02/java-truststore/</id>
            
            <published>2022-02-06T00:00:00+01:00</published>
            <updated>2022-02-26T00:00:00+01:00</updated>
            
            
            <content type="html"><![CDATA[<p><img src="/img/java-truststore.svg#center" alt=""  /></p>
<p>Los almacenes de certificados de confianza de <em>Java</em> o <strong>truststores</strong> son archivos binarios en los que se almacenan claves públicas de <strong>Autoridades de Certificación</strong> (<strong>CA</strong>). Las aplicaciones Java, a la hora de hacer peticiones a sitios web que ofrecen su contenido (APIs REST, APIs SOAP, contenido HTML, etc.) a través del protocolo <em>HTTPS</em>, comprueban como parte del proceso de negociación <em>TLS/SSL</em> que el certificado de ese sitio se encuentra emitido por una de las <strong>CA</strong> que se encuentren en su <strong>truststore</strong>. Vamos a ver cómo consultar y gestionar el contenido de estos almacenes.</p>
<p> </p>
<p><strong>Contenido</strong></p>



  <div class="gblog-toc gblog-toc__level--6"><nav id="TableOfContents">
  <ul>
    <li>
      <ul>
        <li><a href="#almacén-de-certificados-de-confianza-predeterminado-en-java">Almacén de certificados de confianza predeterminado en Java</a></li>
        <li><a href="#herramienta-de-gestión-de-certificados">Herramienta de gestión de certificados</a></li>
        <li><a href="#listar-certificados-contenidos-en-un-truststore">Listar certificados contenidos en un truststore</a></li>
        <li><a href="#cambiar-contraseña-de-un-almacén-de-certificados">Cambiar contraseña de un almacén de certificados</a></li>
        <li><a href="#archivos-de-certificados">Archivos de certificados</a></li>
        <li><a href="#comprobación-de-caducidad-de-certificados">Comprobación de caducidad de certificados</a></li>
        <li><a href="#crear-un-almacén-de-tipo-jks-en-modo-interactivo">Crear un almacén de tipo jks en modo interactivo</a></li>
        <li><a href="#añadir-un-certificado-de-confianza-a-un-almacén">Añadir un certificado de confianza a un almacén</a></li>
        <li><a href="#añadir-todos-los-certificados-contenidos-en-un-directorio-a-un-almacén">Añadir todos los certificados contenidos en un directorio a un almacén</a></li>
        <li><a href="#especificar-truststore-para-aplicaciones-java">Especificar truststore para aplicaciones Java</a></li>
        <li><a href="#referencias">Referencias</a></li>
        <li><a href="#atribución">Atribución</a></li>
      </ul>
    </li>
  </ul>
</nav><hr></div>


<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="almacén-de-certificados-de-confianza-predeterminado-en-java">
        Almacén de certificados de confianza predeterminado en Java
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/02/java-truststore/#almacén-de-certificados-de-confianza-predeterminado-en-java" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Almacén de certificados de confianza predeterminado en Java" href="#almac%c3%a9n-de-certificados-de-confianza-predeterminado-en-java">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>En cualquier equipo con un <strong>JRE/JDK</strong> instalado, el <strong>truststore</strong> predeterminado se encuentra dentro del directorio donde está ubicado el <strong>JRE</strong> (frecuentemente referenciado por la variable <code>JAVA_HOME</code>). Está concretamente en el subdirectorio <code>lib/security</code> y el archivo se denomina <code>cacerts</code>. Por defecto está protegido por la contraseña <code>changeit</code>.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="herramienta-de-gestión-de-certificados">
        Herramienta de gestión de certificados
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/02/java-truststore/#herramienta-de-gestión-de-certificados" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Herramienta de gestión de certificados" href="#herramienta-de-gesti%c3%b3n-de-certificados">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Cualquier <strong>JRE/JDK</strong> incluye en el directorio <code>bin</code> la herramienta <code>keytool</code> que permite realizar varias operaciones sobre almacenes de certificados y archivos de certificados. Vamos a ver unas cuantas operaciones con esta herramienta que pueden resultar útiles.</p>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="listar-certificados-contenidos-en-un-truststore">
        Listar certificados contenidos en un truststore
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/02/java-truststore/#listar-certificados-contenidos-en-un-truststore" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Listar certificados contenidos en un truststore" href="#listar-certificados-contenidos-en-un-truststore">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Si tenemos correctamente definida la variable de entorno <code>JAVA_HOME</code>, podemos utilizarla para referenciar la ruta del <strong>truststore</strong> predeterminado. Si no está definida o estamos utilizando un <strong>JRE/JDK</strong> distinto del predeterminado, tendremos que hacer referencia a la ruta que apunte al almacén concreto que queramos gestionar.</p>
<p>Con el parámetro <code>-list</code> mostramos la lista de certificados contenidos en el <strong>truststore</strong> predeterminado. Si está protegido por contraseña, debemos indicar la contraseña en el parámetro <code>-storepass</code>. La ruta al almacén la indicamos con el parámetro <code>-keystore</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">keytool -list -storepass changeit -keystore <span class="nv">$JAVA_HOME</span>/lib/security/cacerts
</span></span></code></pre></div><pre tabindex="0"><code>keystore type: jks
Keystore provider: SUN

Your keystore contains 138 entries

actalisauthenticationrootca, Dec 14, 2021, trustedCertEntry, 
Certificate fingerprint (SHA-256): 55:92:60:84:EC:96:3A:64:B9:6E:2A:BE:01:CE:0B:A8:6A:64:FB:FE:BC:C7:AA:B5:AF:C1:55:B3:7F:D7:60:66
affirmtrustcommercial, Dec 14, 2021, trustedCertEntry, 
Certificate fingerprint (SHA-256): 03:76:AB:1D:54:C5:F9:80:3C:E4:B2:E2:01:A0:EE:7E:EF:7B:57:B6:36:E8:A9:3C:9B:8D:48:60:C9:6F:5F:A7

...

xrampglobalcaroot, Dec 14, 2021, trustedCertEntry, 
Certificate fingerprint (SHA-256): CE:CD:DC:90:50:99:D8:DA:DF:C5:B1:D2:09:B7:37:CB:E2:C1:8C:FB:2C:10:C0:FF:0B:CF:0D:32:86:FC:1A:A2
</code></pre><p>Si al comando anterior le agregamos el parámetro <code>-v</code> nos mostrará todos los detalles de cada uno de los certificados.</p>
<p>También podemos obtener información detallada de uno de los certificados del almacén en concreto, especificando su <em>alias</em> con el parámetro <code>-alias</code>. El alias lo habremos visto a raíz del resultado de los comandos anteriores. En el siguiente ejemplo consultamos el detalle del certificado con alias <em>thawteserverca</em>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">keytool -list -v -storepass changeit -keystore <span class="nv">$JAVA_HOME</span>/lib/security/cacerts -alias thawteserverca
</span></span></code></pre></div><pre tabindex="0"><code>Nombre de Alias: thawteserverca
Fecha de Creación: 12 feb. 1999
Tipo de Entrada: trustedCertEntry

Propietario: EMAILADDRESS=server-certs@thawte.com, CN=Thawte Server CA, OU=Certification Services Division, O=Thawte Consulting cc, L=Cape Town, ST=Western Cape, C=ZA
Emisor: EMAILADDRESS=server-certs@thawte.com, CN=Thawte Server CA, OU=Certification Services Division, O=Thawte Consulting cc, L=Cape Town, ST=Western Cape, C=ZA
Número de serie: 1
Válido desde: Thu Aug 01 02:00:00 CEST 1996 hasta: Fri Jan 01 00:59:59 CET 2021
Huellas digitales del certificado:
	SHA1: 23:E5:94:94:51:95:F2:41:48:03:B4:D5:64:D2:A3:A3:F5:D8:8B:8C
	SHA256: B4:41:0B:73:E2:E6:EA:CA:47:FB:C4:2F:8F:A4:01:8A:F4:38:1D:C5:4C:FA:A8:44:50:46:1E:ED:09:45:4D:E9
Nombre del algoritmo de firma: MD5withRSA
Algoritmo de clave pública de asunto: Clave RSA de 1024 bits (débil)
Versión: 3

Extensiones: 

#1: ObjectId: 2.5.29.19 Criticality=true
BasicConstraints:[
  CA:true
  PathLen:2147483647
]
</code></pre><p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="cambiar-contraseña-de-un-almacén-de-certificados">
        Cambiar contraseña de un almacén de certificados
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/02/java-truststore/#cambiar-contraseña-de-un-almacén-de-certificados" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Cambiar contraseña de un almacén de certificados" href="#cambiar-contrase%c3%b1a-de-un-almac%c3%a9n-de-certificados">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Podemos cambiar la contraseña de un almacén por otra con el parámetro <code>-storepasswd</code>, indicando la contraseña actual con el parámetro <code>-storepass</code> y la nueva con el parámetro <code>-new</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">keytool -storepasswd -new nueva_contraseña -keystore <span class="nv">$JAVA_HOME</span>/lib/security/cacerts -storepass contraseña_actual
</span></span></code></pre></div><p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="archivos-de-certificados">
        Archivos de certificados
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/02/java-truststore/#archivos-de-certificados" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Archivos de certificados" href="#archivos-de-certificados">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Los certificados digitales se pueden almacenar en diversos formatos. En lo que se refiere a los certificados de clave pública, que son los que se incluyen en un almacén de certificados de confianza para establecer las comunicaciones <strong>SSL/TLS</strong>, los formatos más habituales son dos:</p>
<ul>
<li><strong>PEM</strong>. Archivo cifrado en formato Base64. Suele tener extensiones <em>.pem</em>, <em>.cer</em> o <em>.crt</em>, aunque la extensión no es determinante del formato real del archivo. Se distinguen más fácilmente porque su contenido se puede ver claramente con un editor de texto y en él se ven las líneas <code>----BEGIN CERTIFICATE----</code> y <code>----END CERTIFICATE----</code>. En el caso de archivos que contengan una cadena completa de certificación, se puede distinguir el inicio y fin de cada uno de los certificados que contiene gracias a esas etiquetas.</li>
<li><strong>DER</strong>. Archivo binario. Suele tener extensiones <em>.der</em>, <em>.crt</em> o incluso <em>.cer</em>. Su contenido no se puede ver de forma &ldquo;entendible&rdquo; a través de un editor de textos.</li>
</ul>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="comprobación-de-caducidad-de-certificados">
        Comprobación de caducidad de certificados
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/02/java-truststore/#comprobación-de-caducidad-de-certificados" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Comprobación de caducidad de certificados" href="#comprobaci%c3%b3n-de-caducidad-de-certificados">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Con <code>keytool</code> podemos <strong>obtener el detalle de un certificado</strong>, tanto si está dentro de un almacén como si está en un archivo independiente.</p>
<p>Vamos a ver cómo extraer el detalle de un certificado que viene en un archivo individual. Como ejemplo, vamos a descargarnos el archivo de un certificado raíz de <em>GlobalSign</em>, una de las autoridades de certificación más conocidas. Podemos descargarlo de <a class="gblog-markdown__link" href="https://secure.globalsign.net/cacert/root-r6.crt">https://secure.globalsign.net/cacert/root-r6.crt</a> con:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">curl https://secure.globalsign.net/cacert/root-r6.crt -o globalsign-root-r6.crt
</span></span></code></pre></div><p>Podemos mostrar los detalles de este certificado con:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">keytool -printcert -file globalsign-root-r6.crt
</span></span></code></pre></div><p>Los datos de cabecera que nos devuelve el comando son:</p>
<pre tabindex="0"><code>Propietario: CN=GlobalSign, O=GlobalSign, OU=GlobalSign Root CA - R6
Emisor: CN=GlobalSign, O=GlobalSign, OU=GlobalSign Root CA - R6
Número de serie: 45e6bb038333c3856548e6ff4551
Válido desde: Wed Dec 10 01:00:00 CET 2014 hasta: Sun Dec 10 01:00:00 CET 2034
Huellas digitales del certificado:
	SHA1: 80:94:64:0E:B5:A7:A1:CA:11:9C:1F:DD:D5:9F:81:02:63:A7:FB:D1
	SHA256: 2C:AB:EA:FE:37:D0:6C:A2:2A:BA:73:91:C0:03:3D:25:98:29:52:C4:53:64:73:49:76:3A:3A:B5:AD:6C:CF:69
Nombre del algoritmo de firma: SHA384withRSA
Algoritmo de clave pública de asunto: Clave RSA de 4096 bits
Versión: 3
</code></pre><p>Muestra mucha más información que la del ejemplo anterior, pero nos vamos a centrar en primer bloque de información, concretamente en la línea que nos indica las fechas de validez, que en este caso vemos que van del <em>10/12/2014</em> al <em>10/12/2034</em>. Vamos a procesar el resultado para quedarnos sólo con esta parte de la información, filtrándo por la palabra &ldquo;Válido&rdquo;, aunque dependiendo del idioma del sistema puede que tengamos que cambiarla por cualquier otra equivalente:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">keytool -printcert -file globalsign-root-r6.crt <span class="p">|</span> grep -E <span class="s2">&#34;Válido&#34;</span> 
</span></span></code></pre></div><pre tabindex="0"><code>Válido desde: Wed Dec 10 01:00:00 CET 2014 hasta: Sun Dec 10 01:00:00 CET 2034
</code></pre><p>Ahora modificamos el comando anterior para filtrar con <code>awk</code>, que por defecto divide en elementos separados por espacio en blanco, especificando las posiciones en las que se encuentran el día, el mes y el año de la fecha de fin de validez:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">keytool -printcert -file globalsign-root-r6.crt <span class="p">|</span> grep -E <span class="s2">&#34;Válido&#34;</span> <span class="p">|</span> awk  <span class="s1">&#39;{print $12&#34; &#34;$11&#34; &#34;$15}&#39;</span>
</span></span></code></pre></div><pre tabindex="0"><code>10 Dec 2034
</code></pre><p>Una vez que tenemos la fecha de fin de validez podemos hacer un pequeño script en <em>bash</em> para calcular los días de validez. Los pásos básicos son:</p>
<ol>
<li>Convertir la fecha de fin de validez a un valor <strong><a class="gblog-markdown__link" href="https://es.wikipedia.org/wiki/Tiempo_Unix">unix epoch</a></strong>, que representa el número de segundos transcurridos entre el 1 de enero de 1970 y la fecha.</li>
<li>Obtener la fecha actual en formato <strong>unix epoch</strong></li>
<li>Obtener la diferencia entre la fecha de fin de validez y la fecha actual, lo que nos dará el número de segundos que faltan hasta la caducidad. Dividimos ese valor entre el número de segundos por día y así obtendremos los días que faltan (o exceden) hasta la fecha de caducidad.</li>
<li>Mostraremos un mensaje diferente según el certificado esté ya caducado, le falten menos de 30 días para su caducidad o le falten más de 30 días.</li>
</ol>
<p>Para ello, creamos el archivo <code>check-certificate-expiration</code> con el siguiente contenido:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl"><span class="cp"></span><span class="nv">archivo_cert</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$1</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">finval</span><span class="o">=</span><span class="k">$(</span>keytool -printcert -file <span class="nv">$archivo_cert</span> <span class="p">|</span> grep -E <span class="s2">&#34;Válido&#34;</span> <span class="p">|</span> awk  <span class="s1">&#39;{print $12&#34; &#34;$11&#34; &#34;$15}&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">finval</span><span class="o">=</span><span class="k">$(</span>date -d <span class="s2">&#34;</span><span class="nv">$finval</span><span class="s2">&#34;</span> <span class="s1">&#39;+%s&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">hoy</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span>date <span class="s1">&#39;+%s&#39;</span><span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">dias_caducidad</span><span class="o">=</span><span class="k">$((</span> <span class="o">(</span><span class="nv">$finval</span> <span class="o">-</span> <span class="nv">$hoy</span><span class="o">)</span> <span class="o">/</span> <span class="o">(</span><span class="m">60</span><span class="o">*</span><span class="m">60</span><span class="o">*</span><span class="m">24</span><span class="o">)</span> <span class="k">))</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[</span> <span class="nv">$dias_caducidad</span> -lt <span class="m">1</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;El certificado ha caducado&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">elif</span> <span class="o">[</span> <span class="nv">$dias_caducidad</span> -lt <span class="m">30</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;Quedan menos de 30 días para que caduque el certificado&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;Quedan </span><span class="nv">$dias_caducidad</span><span class="s2"> dias para que caduque el certificado&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span></code></pre></div><p>Le damos permisos de ejecución:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">chmod +x check-certificate-expiration
</span></span></code></pre></div><p>Y lo ejecutamos para verificar nuestro certificado</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">./check-certificate-expiration globalsign-root-r6.crt 
</span></span></code></pre></div><pre tabindex="0"><code>Quedan 4689 dias para que caduque el certificado
</code></pre><p>Con la misma idea, podríamos hacer un script algo más complejo al que le indiquemos como argumento la ruta a un <strong>truststore</strong> y su contraseña, que compruebe todos los certificados que contiene e informe de aquellos que han caducado o quedan menos de 30 días para que caduquen. Creamos una función <code>validarcertificado</code> que valida la caducidad de un certificado dentro de un almacén que habremos filtrado por su alias. Una vez que tengamos esa función, obtenemos la lista de todos los alias contenidos en un almacén y llamamos a la función <code>validarcertificado</code> para cada uno de ellos.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl">validarcertificado<span class="o">(){</span>
</span></span><span class="line"><span class="cl">  <span class="nv">truststore</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$1</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="nv">password</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$2</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="nv">alias</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$3</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  keytool -list -v -storepass <span class="s2">&#34;</span><span class="nv">$password</span><span class="s2">&#34;</span> -keystore <span class="s2">&#34;</span><span class="nv">$truststore</span><span class="s2">&#34;</span> -alias <span class="s2">&#34;</span><span class="nv">$alias</span><span class="s2">&#34;</span> 2&gt;/dev/null <span class="p">|</span> grep -E <span class="s2">&#34;Propietario|Válido&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    <span class="p">|</span> <span class="k">while</span> <span class="nb">read</span> propietario <span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="nb">read</span> fechas
</span></span><span class="line"><span class="cl">      <span class="nv">propietario</span><span class="o">=</span><span class="si">${</span><span class="nv">propietario</span><span class="p">#</span><span class="s2">&#34;Propietario: &#34;</span><span class="si">}</span>
</span></span><span class="line"><span class="cl">      <span class="nv">finval</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$fechas</span><span class="s2">&#34;</span> <span class="p">|</span> awk  <span class="s1">&#39;{print $12&#34; &#34;$11&#34; &#34;$15}&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">      <span class="nv">finval</span><span class="o">=</span><span class="k">$(</span>date -d <span class="s2">&#34;</span><span class="nv">$finval</span><span class="s2">&#34;</span> <span class="s1">&#39;+%s&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">      <span class="nv">hoy</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span>date <span class="s1">&#39;+%s&#39;</span><span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="nv">dias_caducidad</span><span class="o">=</span><span class="k">$((</span> <span class="o">(</span><span class="nv">$finval</span> <span class="o">-</span> <span class="nv">$hoy</span><span class="o">)</span> <span class="o">/</span> <span class="o">(</span><span class="m">60</span><span class="o">*</span><span class="m">60</span><span class="o">*</span><span class="m">24</span><span class="o">)</span> <span class="k">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="o">[</span> <span class="nv">$dias_caducidad</span> -lt <span class="m">1</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">        <span class="nb">echo</span> <span class="s2">&#34;El certificado </span><span class="nv">$alias</span><span class="s2"> (</span><span class="nv">$propietario</span><span class="s2">) ha caducado&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="k">elif</span> <span class="o">[</span> <span class="nv">$dias_caducidad</span> -lt <span class="m">30</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">        <span class="nb">echo</span> <span class="s2">&#34;Quedan </span><span class="nv">$dias_caducidad</span><span class="s2"> para que caduque el certificado </span><span class="nv">$alias</span><span class="s2"> (</span><span class="nv">$propietario</span><span class="s2">)&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">done</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">truststore</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$1</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">password</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$2</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">keytool -list -storepass <span class="nv">$password</span> -keystore <span class="nv">$truststore</span> 2&gt;/dev/null <span class="p">|</span> grep <span class="s2">&#34;trustedCertEntry&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span><span class="p">|</span> <span class="k">while</span> <span class="nb">read</span> linea_alias<span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">   <span class="nv">alias</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$linea_alias</span><span class="s2">&#34;</span> <span class="p">|</span> cut -d <span class="s1">&#39;,&#39;</span> -f 1<span class="k">)</span>
</span></span><span class="line"><span class="cl">   validarcertificado <span class="s2">&#34;</span><span class="nv">$truststore</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$password</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$alias</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span>
</span></span></code></pre></div><p>Por último podemos <strong>verificar la caducidad de todos los certificados que se encuentren en un directorio</strong>. Algunos de los archivos de certificados pueden contener más de un certificado, incluyendo toda una cadena, por lo que se incluye la posibilidad de procesar más de un certificado en cada archivo.
Esto se consigue filtrando de la información de cada certificado por las expresiones &ldquo;Propietario&rdquo; y &ldquo;Válido&rdquo;, con lo que obtendremos todas las líneas en las que aparezca el propietario del certificado y la fecha de validez, en la mayor parte de los casos sólo una de cada tipo. Por la posibilidad de que aparecieran varias, se utiliza la expresión <code>while read propietario</code> y a continuación se utiliza la instrucción <code>read fechas</code> que leerá la siguiente línea, que será la que contenga las fechas correspondientes al certificado del propietario obtenido a través del <code>while</code>. En este ejemplo se buscarán archivos con extensiones <code>.cer</code>, <code>.pem</code> y <code>.crt</code> dentro del directorio especificado:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl">validarcertificado<span class="o">(){</span>
</span></span><span class="line"><span class="cl">  <span class="nv">archivo_cert</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$1</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  keytool -printcert -file <span class="nv">$archivo_cert</span> 2&gt;/dev/null 2&gt;/dev/null <span class="p">|</span> grep -E <span class="s2">&#34;Propietario|Válido&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  <span class="p">|</span> <span class="k">while</span> <span class="nb">read</span> propietario <span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="nb">read</span> fechas
</span></span><span class="line"><span class="cl">      <span class="nv">propietario</span><span class="o">=</span><span class="si">${</span><span class="nv">propietario</span><span class="p">#</span><span class="s2">&#34;Propietario: &#34;</span><span class="si">}</span>
</span></span><span class="line"><span class="cl">      <span class="nv">finval</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$fechas</span><span class="s2">&#34;</span> <span class="p">|</span> awk  <span class="s1">&#39;{print $12&#34; &#34;$11&#34; &#34;$15}&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">      <span class="nv">finval</span><span class="o">=</span><span class="k">$(</span>date -d <span class="s2">&#34;</span><span class="nv">$finval</span><span class="s2">&#34;</span> <span class="s1">&#39;+%s&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">      <span class="nv">hoy</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span>date <span class="s1">&#39;+%s&#39;</span><span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="nv">dias_caducidad</span><span class="o">=</span><span class="k">$((</span> <span class="o">(</span><span class="nv">$finval</span> <span class="o">-</span> <span class="nv">$hoy</span><span class="o">)</span> <span class="o">/</span> <span class="o">(</span><span class="m">60</span><span class="o">*</span><span class="m">60</span><span class="o">*</span><span class="m">24</span><span class="o">)</span> <span class="k">))</span>
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="o">[</span> <span class="nv">$dias_caducidad</span> -lt <span class="m">1</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">        <span class="nb">echo</span> <span class="s2">&#34;El certificado (</span><span class="nv">$propietario</span><span class="s2">) de </span><span class="nv">$archivo_cert</span><span class="s2"> ha caducado&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="k">elif</span> <span class="o">[</span> <span class="nv">$dias_caducidad</span> -lt <span class="m">30</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">        <span class="nb">echo</span> <span class="s2">&#34;Quedan menos de 30 días para que caduque el certificado (</span><span class="nv">$propietario</span><span class="s2">) de </span><span class="nv">$archivo_cert</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="k">fi</span>
</span></span><span class="line"><span class="cl">    <span class="k">done</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">directorio</span><span class="o">=</span><span class="nv">$1</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> certificado in <span class="nv">$directorio</span>/*.<span class="o">{</span>cer,pem,crt<span class="o">}</span>
</span></span><span class="line"><span class="cl"><span class="k">do</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span> -f <span class="s2">&#34;</span><span class="nv">$certificado</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">||</span> <span class="k">continue</span>
</span></span><span class="line"><span class="cl">    validarcertificado <span class="s2">&#34;</span><span class="nv">$certificado</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span>
</span></span></code></pre></div><p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="crear-un-almacén-de-tipo-jks-en-modo-interactivo">
        Crear un almacén de tipo jks en modo interactivo
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/02/java-truststore/#crear-un-almacén-de-tipo-jks-en-modo-interactivo" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Crear un almacén de tipo jks en modo interactivo" href="#crear-un-almac%c3%a9n-de-tipo-jks-en-modo-interactivo">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Hay casos en los que tenemos que utilizar un almacén específico. Podemos <strong>crear un nuevo almacén</strong> utilizando el parámetro <code>-genkey</code>, especificando el algoritmo de clave (parámetro <code>-keyalg</code>) y el tamaño de clave (parámetro <code>-keysize</code>). Nos solicitará los datos necesarios de forma interactiva:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">keytool -genkey -keyalg RSA -keystore keystore.jks -keysize <span class="m">2048</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vim" data-lang="vim"><span class="line"><span class="cl"><span class="nx">Introduzca</span> <span class="nx">la</span> <span class="nx">contraseña</span> <span class="nx">del</span> <span class="nx">almacén</span> <span class="nx">de</span> <span class="nx">claves</span>:  
</span></span><span class="line"><span class="cl"><span class="nx">Volver</span> <span class="nx">a</span> <span class="nx">escribir</span> <span class="nx">la</span> <span class="nx">contraseña</span> <span class="nx">nueva</span>: 
</span></span><span class="line"><span class="cl">¿<span class="nx">Cuáles</span> <span class="nx">son</span> <span class="nx">su</span> <span class="nx">nombre</span> <span class="nx">y</span> <span class="nx">su</span> <span class="nx">apellido</span>?
</span></span><span class="line"><span class="cl">  [<span class="nx">Unknown</span>]:  <span class="nx">Dvdcr</span>
</span></span><span class="line"><span class="cl">¿<span class="nx">Cuál</span> <span class="nx">es</span> <span class="nx">el</span> <span class="nx">nombre</span> <span class="nx">de</span> <span class="nx">su</span> <span class="nx">unidad</span> <span class="nx">de</span> <span class="nx">organización</span>?
</span></span><span class="line"><span class="cl">  [<span class="nx">Unknown</span>]:  <span class="nx">publicaciones</span>
</span></span><span class="line"><span class="cl">¿<span class="nx">Cuál</span> <span class="nx">es</span> <span class="nx">el</span> <span class="nx">nombre</span> <span class="nx">de</span> <span class="nx">su</span> <span class="nx">organización</span>?
</span></span><span class="line"><span class="cl">  [<span class="nx">Unknown</span>]:  <span class="nx">Dvdcr</span> <span class="nx">Blog</span>
</span></span><span class="line"><span class="cl">¿<span class="nx">Cuál</span> <span class="nx">es</span> <span class="nx">el</span> <span class="nx">nombre</span> <span class="nx">de</span> <span class="nx">su</span> <span class="nx">ciudad</span> <span class="nx">o</span> <span class="nx">localidad</span>?
</span></span><span class="line"><span class="cl">  [<span class="nx">Unknown</span>]:  <span class="nx">Madrid</span>
</span></span><span class="line"><span class="cl">¿<span class="nx">Cuál</span> <span class="nx">es</span> <span class="nx">el</span> <span class="nx">nombre</span> <span class="nx">de</span> <span class="nx">su</span> <span class="nx">estado</span> <span class="nx">o</span> <span class="nx">provincia</span>?
</span></span><span class="line"><span class="cl">  [<span class="nx">Unknown</span>]:  <span class="nx">Madrid</span>
</span></span><span class="line"><span class="cl">¿<span class="nx">Cuál</span> <span class="nx">es</span> <span class="nx">el</span> <span class="nx">código</span> <span class="nx">de</span> <span class="nx">país</span> <span class="nx">de</span> <span class="nx">dos</span> <span class="nx">letras</span> <span class="nx">de</span> <span class="nx">la</span> <span class="nx">unidad</span>?
</span></span><span class="line"><span class="cl">  [<span class="nx">Unknown</span>]:  <span class="nx">ES</span>
</span></span><span class="line"><span class="cl">¿<span class="nx">Es</span> <span class="nx">correcto</span> <span class="nx">CN</span><span class="p">=</span><span class="nx">Dvdcr</span><span class="p">,</span> <span class="nx">OU</span><span class="p">=</span><span class="nx">publicaciones</span><span class="p">,</span> <span class="nx">O</span><span class="p">=</span><span class="nx">Dvdcr</span> <span class="nx">Blog</span><span class="p">,</span> <span class="nx">L</span><span class="p">=</span><span class="nx">Madrid</span><span class="p">,</span> <span class="nx">ST</span><span class="p">=</span><span class="nx">Madrid</span><span class="p">,</span> <span class="nx">C</span><span class="p">=</span><span class="nx">ES</span>?
</span></span><span class="line"><span class="cl">  [<span class="nx">no</span>]:  <span class="nx">si</span>
</span></span></code></pre></div><p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="añadir-un-certificado-de-confianza-a-un-almacén">
        Añadir un certificado de confianza a un almacén
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/02/java-truststore/#añadir-un-certificado-de-confianza-a-un-almacén" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Añadir un certificado de confianza a un almacén" href="#a%c3%b1adir-un-certificado-de-confianza-a-un-almac%c3%a9n">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Para <strong>añadir un certificado de confianza a un almacén</strong> existente, utilizamos el parámetro <code>-importcert</code>. La sintaxis del siguiente ejemplo, especifica todos los datos necesarios para que se haga en un sólo paso y no sea necesario que la herramienta nos pregunte:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">keytool -importcert -keystore cacerts18 -storepass <span class="s2">&#34;changeit&#34;</span> -noprompt <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>-trustcacerts -alias <span class="s2">&#34;globalsign-root-r6-custom&#34;</span>  -file globalsign-root-r6.crt
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vim" data-lang="vim"><span class="line"><span class="cl"><span class="nx">Se</span> <span class="nx">ha</span> <span class="nx">agregado</span> <span class="nx">el</span> <span class="nx">certificado</span> <span class="nx">al</span> <span class="nx">almacén</span> <span class="nx">de</span> <span class="nx">claves</span>
</span></span></code></pre></div><p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="añadir-todos-los-certificados-contenidos-en-un-directorio-a-un-almacén">
        Añadir todos los certificados contenidos en un directorio a un almacén
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/02/java-truststore/#añadir-todos-los-certificados-contenidos-en-un-directorio-a-un-almacén" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Añadir todos los certificados contenidos en un directorio a un almacén" href="#a%c3%b1adir-todos-los-certificados-contenidos-en-un-directorio-a-un-almac%c3%a9n">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>A partir de la información del apartado anterior, podríamos hacer un sencillo script (<em>import-certs-to-store</em>) al que se especifique la ruta al almacén, la ruta al directorio que contiene certificados a importar y la contraseña del almacén y que realice la importación en bloque de todos los certificados que se encuentran en un directorio concreto. Este ejemplo en concreto, busca archivos con extensiones <code>.cer</code>, <code>.pem</code> y <code>.crt</code>. Los importa con un alias que es el nombre del archivo sin extensión ni la barra inclinada de la ruta:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="nv">almacen</span><span class="o">=</span><span class="nv">$1</span>
</span></span><span class="line"><span class="cl"><span class="nv">dir</span><span class="o">=</span><span class="nv">$2</span>
</span></span><span class="line"><span class="cl"><span class="nv">password</span><span class="o">=</span><span class="nv">$3</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">for</span> certificado in <span class="nv">$dir</span>/*.<span class="o">{</span>cer,pem,crt<span class="o">}</span>
</span></span><span class="line"><span class="cl"><span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="o">[</span> -f <span class="s2">&#34;</span><span class="nv">$certificado</span><span class="s2">&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    <span class="nv">alias</span><span class="o">=</span><span class="si">${</span><span class="nv">certificado</span><span class="p">%</span><span class="s2">&#34;.cer&#34;</span><span class="si">}</span>
</span></span><span class="line"><span class="cl">    <span class="nv">alias</span><span class="o">=</span><span class="si">${</span><span class="nv">alias</span><span class="p">%</span><span class="s2">&#34;.pem&#34;</span><span class="si">}</span>
</span></span><span class="line"><span class="cl">    <span class="nv">alias</span><span class="o">=</span><span class="si">${</span><span class="nv">alias</span><span class="p">%</span><span class="s2">&#34;.crt&#34;</span><span class="si">}</span>
</span></span><span class="line"><span class="cl">    <span class="nv">alias</span><span class="o">=</span><span class="si">${</span><span class="nv">alias</span><span class="p">##*</span><span class="s2">&#34;/&#34;</span><span class="si">}</span>    
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;Importando a [</span><span class="nv">$almacen</span><span class="s2">] certificado de archivo [</span><span class="nv">$certificado</span><span class="s2">] con alias [</span><span class="nv">$alias</span><span class="s2">]...&#34;</span>
</span></span><span class="line"><span class="cl">    keytool -importcert -noprompt -trustcacerts -keystore <span class="s2">&#34;</span><span class="nv">$almacen</span><span class="s2">&#34;</span> -storepass <span class="s2">&#34;</span><span class="nv">$password</span><span class="s2">&#34;</span> -alias <span class="s2">&#34;</span><span class="nv">$alias</span><span class="s2">&#34;</span> -file <span class="s2">&#34;</span><span class="nv">$certificado</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">fi</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span>
</span></span></code></pre></div><p>Ejemplo de ejecución:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">./import-certs-to-store ./cacerts ./dir-con-certificados changeit
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vim" data-lang="vim"><span class="line"><span class="cl"><span class="nx">Importando</span> <span class="nx">a</span> [.<span class="sr">/cacerts] certificado de archivo [./</span><span class="nx">dir</span><span class="p">-</span><span class="nx">con</span><span class="p">-</span><span class="nx">certificados</span>/<span class="nx">globalsign</span><span class="p">-</span><span class="nx">root</span><span class="p">-</span><span class="nx">r6</span>.<span class="nx">crt</span>] <span class="nx">con</span> <span class="nx">alias</span> [<span class="nx">globalsign</span><span class="p">-</span><span class="nx">root</span><span class="p">-</span><span class="nx">r6</span>]...
</span></span><span class="line"><span class="cl"><span class="nx">Se</span> <span class="nx">ha</span> <span class="nx">agregado</span> <span class="nx">el</span> <span class="nx">certificado</span> <span class="nx">al</span> <span class="nx">almacén</span> <span class="nx">de</span> <span class="nx">claves</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">Importando</span> <span class="nx">a</span> [.<span class="sr">/cacerts] certificado de archivo [./</span><span class="nx">dir</span><span class="p">-</span><span class="nx">con</span><span class="p">-</span><span class="nx">certificados</span>/<span class="nx">ejemplo</span>.<span class="nx">pem</span>] <span class="nx">con</span> <span class="nx">alias</span> [<span class="nx">ejemplo</span>]...
</span></span><span class="line"><span class="cl"><span class="nx">Se</span> <span class="nx">ha</span> <span class="nx">agregado</span> <span class="nx">el</span> <span class="nx">certificado</span> <span class="nx">al</span> <span class="nx">almacén</span> <span class="nx">de</span> <span class="nx">claves</span>
</span></span></code></pre></div><p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="especificar-truststore-para-aplicaciones-java">
        Especificar truststore para aplicaciones Java
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/02/java-truststore/#especificar-truststore-para-aplicaciones-java" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Especificar truststore para aplicaciones Java" href="#especificar-truststore-para-aplicaciones-java">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Para que una aplicación Java utilice un <strong>trustStore</strong> específico diferente del que incluye por defecto el JDK/JRE sobre el que se ejecuta, hay que indicar la ubicación de dicho <strong>truststore</strong> con la variable <code>javax.net.ssl.trustStore</code> y la contraseña de dicho almacén a través de la variable <code>javax.net.ssl.trustStorePassword</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">java -Djavax.net.ssl.trustStore<span class="o">=</span>/opt/java-alt/JRE/lib/security/cacerts <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>     -Djavax.net.ssl.trustStorePassword<span class="o">=</span><span class="s2">&#34;contraseña&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>     -jar aplicacion.jar
</span></span></code></pre></div><p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="referencias">
        Referencias
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/02/java-truststore/#referencias" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Referencias" href="#referencias">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><ul>
<li><a class="gblog-markdown__link" href="https://docs.oracle.com/javase/8/docs/technotes/tools/unix/keytool.html">https://docs.oracle.com/javase/8/docs/technotes/tools/unix/keytool.html</a></li>
</ul>
<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="atribución">
        Atribución
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/02/java-truststore/#atribución" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Atribución" href="#atribuci%c3%b3n">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Icono de caja fuerte creado por <a class="gblog-markdown__link" href="https://icon-icons.com/es/users/2PNt4siPJ6WkoUJy1zhqY/icon-sets/">Diego Marquetti</a></p>
<p> </p>]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://dvdcr.gitlab.io/tags/programaci%C3%B3n" term="programaci%C3%B3n" label="programación" />
                            
                        
                    
                
            
        </entry>
    
        
        <entry>
            <title type="html"><![CDATA[Instalar broker MQTT con autenticación en Raspberry Pi]]></title>
            <link href="https://dvdcr.gitlab.io/posts/2022/01/iot-rpi-mqtt/?utm_source=atom_feed" rel="alternate" type="text/html"  hreflang="en" />
            <id>https://dvdcr.gitlab.io/posts/2022/01/iot-rpi-mqtt/</id>
            
            <published>2022-01-30T00:00:00+01:00</published>
            <updated>2022-01-30T00:00:00+01:00</updated>
            
            
            <content type="html"><![CDATA[<p><img src="/img/mqtt-logo.svg#center" alt=""  /></p>
<p><strong>MQTT</strong> (Message Queue Telemetry Transport) es un protocolo estándar de mensajería entre dispositivos del <strong>Internet of Things</strong> (<strong>IoT</strong>). El software que sirve como nexo de unión a todos los dispositivos que utilizan este protocolo se denomina <strong>broker</strong>. Vamos a instalar <strong><a class="gblog-markdown__link" href="https://mosquitto.org/">mosquitto</a></strong>, uno de los <strong>brokers</strong> más conocidos, y a configurar la autenticación por usuario y contraseña.</p>
<p><strong>MQTT</strong> funciona siguiendo un modelo de <strong>publicación-subscripción</strong>. Los dispositivos se suscriben a uno o varios <strong>topics</strong> o <strong>temas</strong>, que consisten en una ruta jerárquica separada por la barra inclinada, y recibirán todos los mensajes publicados sobre ese <strong>topic</strong>. Podemos tener por ejemplo un tema raíz que se llama <em>hogar</em> y dentro de él podemos tener temas que representen las estancias, como <em>hogar/dormitorio</em>, <em>hogar/cocina</em> u <em>hogar/salon</em>. Podríamos detallar todos los niveles que queramos: <em>hogar/salon/temperatura</em> o <em>hogar/salon/lampara_techo</em>.
Cualquier dispositivo puede <strong>publicar</strong> un mensaje en uno de esos <strong>topics</strong> y automáticamente todos los que estén suscritos a ese <strong>topic</strong> lo recibirán.</p>
<p>Para hacer todo esto posible es necesario un software que actúe como servidor, denominado <strong>broker</strong>. Vamos a proceder a la instalación y configuración de  <a class="gblog-markdown__link" href="https://mosquitto.org/">mosquitto</a>, el servidor <strong>MQTT</strong> más conocido, en una Raspberry Pi.
Podemos instalar <strong>mosquitto</strong> a través del gestor de paquetes de nuestra distribución Linux o utilizar un <a class="gblog-markdown__link" href="https://hub.docker.com/_/eclipse-mosquitto">contenedor</a>. Nos centraremos en el primer sistema.</p>
<p> 
 </p>
<div class="gblog-post__anchorwrap">
    <h2 id="instalación-y-configuración-de-mosquitto-a-través-de-los-paquetes-del-sistema">
        Instalación y configuración de Mosquitto a través de los paquetes del sistema
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/01/iot-rpi-mqtt/#instalación-y-configuración-de-mosquitto-a-través-de-los-paquetes-del-sistema" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Instalación y configuración de Mosquitto a través de los paquetes del sistema" href="#instalaci%c3%b3n-y-configuraci%c3%b3n-de-mosquitto-a-trav%c3%a9s-de-los-paquetes-del-sistema">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p><strong>Mosquitto</strong> está disponible en los repositorios oficiales de la Raspberry Pi. Para instalarlo ejecutamos lo siguiente:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo apt-get install -y mosquitto
</span></span></code></pre></div><p>Para configurar el servicio de manera que se exija autenticación para la publicación y la suscripción, tenemos en primer lugar de asegurarnos de que el servicio está parado:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo systemctl stop mosquitto
</span></span></code></pre></div><p>Creamos un archivo de credenciales al que añadimos un usuario. En este caso añadimos el usuario <code>zigbee</code> con el siguiente comando que nos pedirá la contraseña de forma interactiva dos veces:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo mosquitto_passwd -c /etc/mosquitto/users zigbee
</span></span></code></pre></div><p>Podemos añadir más usuarios al archivo, o cambiar la contraseña a un usuario existente, eliminando el parámetro <code>-c</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo mosquitto_passwd /etc/mosquitto/users zigbee
</span></span></code></pre></div><p>Si queremos eliminar un usuario del archivo, utilizaremos el parámetro <code>-D</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo mosquitto_passwd -D /etc/mosquitto/users usuario_a_eliminar
</span></span></code></pre></div><p>Una vez agregados los usuarios que necesitemos, eliminamos permisos de lectura sobre el archivo de credenciales para los usuarios en general:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo chgrp mosquitto /etc/mosquitto/users
</span></span><span class="line"><span class="cl">sudo chmod o-r /etc/mosquitto/users
</span></span></code></pre></div><p>Para activar la autenticación en el servicio en base a este archivo de credenciales podemos editar el archivo <code>/etc/mosquitto/mosquitto.conf</code> o crear un archivo nuevo en el directorio <code>/etc/mosquitto/conf.d/</code>. En ambos casos debemos incluir la instrucción <code>password_file</code> y la ruta del archivo de credenciales. La siguiente instrucción utiliza la segunda opción:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">echo</span> -e <span class="s2">&#34;password_file /etc/mosquitto/users\nlistener 1883\nallow_anonymous false&#34;</span> <span class="p">|</span> sudo tee /etc/mosquitto/conf.d/auth.conf
</span></span></code></pre></div><p>Ahora pasamos a activar el servicio para que arranque al inicio del sistema:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo systemctl <span class="nb">enable</span> mosquitto.service
</span></span></code></pre></div><p>Y lo arrancamos con la configuración aplicada:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo systemctl start mosquitto.service
</span></span></code></pre></div><p>Comprobamos que el servicio está activo:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo systemctl status mosquitto
</span></span></code></pre></div><pre tabindex="0"><code>mosquitto.service - Mosquitto MQTT Broker
     Loaded: loaded (/lib/systemd/system/mosquitto.service; enabled; vendor preset: enabled)
     Active: active (running) since Thu 2022-01-20 13:23:54 CET; 5s ago
       Docs: man:mosquitto.conf(5)
             man:mosquitto(8)
</code></pre><p>Para comprobar que está funcionando correctamente, desde la propia Raspberry Pi podemos instalarnos la herramienta cliente:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo apt-get install -y mosquitto-clients
</span></span></code></pre></div><p>Utilizamos el comando <code>mosquitto_sub</code> para suscribirnos a un <strong>topic</strong>, en este caso <code>rpi/test</code>, sin autenticar. Si hemos configurado correctamente el servicio, esta operación debe dar un error de autorización:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">mosquitto_sub -d -h localhost -p <span class="m">1883</span> -t <span class="s2">&#34;rpi/test&#34;</span>
</span></span></code></pre></div><pre tabindex="0"><code>Client (null) sending CONNECT
Client (null) received CONNACK (5)
Connection error: Connection Refused: not authorised.
Client (null) sending DISCONNECT
</code></pre><p>Ahora probamos de nuevo el comando de suscripción, incluyendo el usuario (parámetro <code>-u</code>)  y la contraseña (parámetro <code>-P</code>):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">mosquitto_sub -d -h localhost -p <span class="m">1883</span> -u zigbee -P contraseña -t <span class="s2">&#34;rpi/test&#34;</span>
</span></span></code></pre></div><p>Publicamos un valor en ese <strong>topic</strong> utilizando el comando <code>mosquitto_pub</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">mosquitto_pub -d -h localhost -p <span class="m">1883</span> -t <span class="s2">&#34;rpi/test&#34;</span> -u zigbee -P contraseña -m <span class="s2">&#34;Buenos días&#34;</span>
</span></span></code></pre></div><p>y comprobamos que es recibido en el otro lado:</p>
<pre tabindex="0"><code> Client (null) received PUBLISH (d0, q0, r0, m0, &#39;rpi/test&#39;, ... (10 bytes))
Buenos días
</code></pre><p>Ahora podemos repetir la prueba desde otro equipo distinto de la Rpi. El comando será el mismo, pero en el parámetro <code>-h</code> indicaremos la dirección IP de la RPi en lugar de <em>localhost</em> (en el ejemplo la dirección de la RPi es 192.168.1.10).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">mosquitto_sub -d -h 192.168.1.0 -p <span class="m">1883</span> -u zigbee -P contraseña -t <span class="s2">&#34;rpi/test&#34;</span>
</span></span></code></pre></div><p>Si en la RPi está activado el firewall, habrá que permitir el puerto <em>1883/tcp</em>, que es el que utiliza <strong>MQTT</strong>, por defecto o desactivar el firewall para poder conectar.</p>
<p>A continuación publicaremos un mensaje en el <strong>topic</strong> <code>rpi/test</code>, también desde otro equipo:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">mosquitto_pub -d -h 192.168.1.10 -p <span class="m">1883</span> -t <span class="s2">&#34;rpi/test&#34;</span> -u zigbee -P contraseña -m <span class="s2">&#34;Saludos&#34;</span>
</span></span></code></pre></div><p>en la sesión de suscripción recibimos:</p>
<pre tabindex="0"><code>Client null sending PINGREQ
Client null received PINGRESP
Client null received PUBLISH (d0, q0, r0, m0, &#39;rpi/test&#39;, ... (7 bytes))
Saludos
</code></pre><p>Ya tenemos disponible nuestro <strong>broker</strong>, que será la base para interconectar muchos disposititos <em>IoT</em>.
Esto puede ser de utilidad para controlar o recibir información de dispositivos <em>IoT</em> Wifi basados en el chip ESP8266 o ESP32 con el firmware <a class="gblog-markdown__link" href="https://github.com/xoseperez/espurna">Espurna</a> o <a class="gblog-markdown__link" href="https://tasmota.github.io/docs/">Tasmota</a>.
También para gestionar dispositivos <strong>Zigbee</strong> a través de <a class="gblog-markdown__link" href="https://www.zigbee2mqtt.io">zigbee2mqtt</a>.
En otros tutoriales veremos cómo trabajar con algunos de ellos.</p>
<div class="gblog-post__anchorwrap">
    <h2 id="referencias">
        Referencias
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/01/iot-rpi-mqtt/#referencias" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Referencias" href="#referencias">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><ul>
<li><a class="gblog-markdown__link" href="https://mosquitto.org/documentation/authentication-methods/">https://mosquitto.org/documentation/authentication-methods/</a></li>
<li><a class="gblog-markdown__link" href="https://mqtt.org/">https://mqtt.org/</a></li>
</ul>]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://dvdcr.gitlab.io/tags/IoT" term="IoT" label="IoT" />
                            
                        
                    
                
            
        </entry>
    
        
        <entry>
            <title type="html"><![CDATA[Vim como editor por defecto en Fedora]]></title>
            <link href="https://dvdcr.gitlab.io/posts/2022/01/fedora-vim-default-editor/?utm_source=atom_feed" rel="alternate" type="text/html"  hreflang="en" />
            <id>https://dvdcr.gitlab.io/posts/2022/01/fedora-vim-default-editor/</id>
            
            <published>2022-01-23T00:01:01+01:00</published>
            <updated>2022-01-23T00:01:01+01:00</updated>
            
            
            <content type="html"><![CDATA[<p><img src="/img/vim-logo.svg#center" alt=""  /></p>
<p>Por defecto, en Fedora <strong>nano</strong> es el editor por defecto. Pero si lo que utilizamos habitualmente es <strong>vim</strong>, es algo sencillo de cambiar.</p>
<p>Para establecer <strong>vim</strong> como editor por defecto basta con ejecutar:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"> sudo dnf install --allowerasing vim-default-editor
</span></span></code></pre></div><p>y reiniciar el equipo para que el cambio tenga efecto.</p>
<p>El comando anterior simplemente cambia la variable de entorno <code>$EDITOR</code>.</p>]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://dvdcr.gitlab.io/tags/linux" term="linux" label="linux" />
                            
                        
                    
                
            
        </entry>
    
        
        <entry>
            <title type="html"><![CDATA[Instalar Open Cluster Management en un equipo personal]]></title>
            <link href="https://dvdcr.gitlab.io/posts/2022/01/mk8s-ocm/?utm_source=atom_feed" rel="alternate" type="text/html"  hreflang="en" />
            <id>https://dvdcr.gitlab.io/posts/2022/01/mk8s-ocm/</id>
            
            <published>2022-01-16T00:00:00+01:00</published>
            <updated>2022-01-16T00:00:00+01:00</updated>
            
            
            <content type="html"><![CDATA[<p><img src="/img/ocm-logo-text.png#center" alt=""  /></p>
<p><strong><a class="gblog-markdown__link" href="https://open-cluster-management.io/">Open Cluster Management</a></strong> (<strong>OCM</strong>) es una <strong>herramienta modular y extensible</strong> para <strong>orquestar múltiples clusters</strong> de <strong>Kubernetes</strong>. Este artículo es un tutorial paso a paso de instalación de una versión mínima de <strong>Kubernetes</strong> y <strong>OCM</strong> en un equipo personal para poder experimentar con esta herramienta.</p>
<p>Se trata de un proyecto de código abierto, con licencia <a class="gblog-markdown__link" href="https://www.apache.org/licenses/LICENSE-2.0">Apache 2.0</a> cuyo código fuente podemos encontrar en <a class="gblog-markdown__link" href="https://github.com/open-cluster-management-io">https://github.com/open-cluster-management-io</a> y que constituye el proyecto <em>upstream</em> de <strong><a class="gblog-markdown__link" href="https://www.redhat.com/es/technologies/management/advanced-cluster-management">Advanced Cluster Management</a></strong> (<strong>RHACM</strong>) de <strong>Red Hat</strong>.</p>
<p>Este tutorial se basa en el artículo de introducción a esta herramienta que figura directamente como <a class="gblog-markdown__link" href="https://open-cluster-management.io/getting-started/quick-start/">guía de inicio rápido</a> en la documentación <strong>OCM</strong>.</p>
<p> 
 </p>
<p><strong>Contenido</strong></p>



  <div class="gblog-toc gblog-toc__level--6"><nav id="TableOfContents">
  <ul>
    <li>
      <ul>
        <li><a href="#requisitos-de-hardware-y-software">Requisitos de hardware y software</a></li>
        <li><a href="#conceptos-generales">Conceptos generales</a></li>
        <li><a href="#kubectl">Kubectl</a></li>
        <li><a href="#instalación-de-multipass">Instalación de multipass</a></li>
        <li><a href="#creación-de-un-cluster-de-un-solo-nodo-para-el-hub">Creación de un cluster de un solo nodo para el hub</a></li>
        <li><a href="#configuración-de-acceso-externo-por-nombre-a-través-de-kubectl-para-cluster-ocm-hub">Configuración de acceso externo por nombre a través de kubectl para cluster ocm-hub</a></li>
        <li><a href="#creación-de-un-cluster-de-un-solo-nodo-que-será-gestionado-desde-el-cluster-hub">Creación de un cluster de un solo nodo que será gestionado desde el cluster hub</a></li>
        <li><a href="#configuración-de-acceso-externo-a-través-de-kubectl-para-el-cluster-ocm-managed">Configuración de acceso externo a través de kubectl para el cluster ocm-managed</a></li>
        <li><a href="#instalación-de-open-cluster-management">Instalación de Open Cluster Management</a>
          <ul>
            <li><a href="#instalación-de-clusteradm">Instalación de clusteradm</a></li>
            <li><a href="#instalación-del-control-plane-de-ocm-en-el-cluster-hub">Instalación del Control Plane de OCM en el cluster hub</a></li>
            <li><a href="#desplegar-un-agente-klusterlet-en-el-cluster-gestionado">Desplegar un agente klusterlet en el cluster gestionado</a></li>
          </ul>
        </li>
        <li><a href="#despliegue-de-una-aplicación-en-el-cluster-gestionado">Despliegue de una aplicación en el cluster gestionado</a></li>
        <li><a href="#parada-de-clusters-y-máquinas-virtuales">Parada de clusters y máquinas virtuales</a></li>
        <li><a href="#arranque-de-máquinas-virtuales-y-clusters">Arranque de máquinas virtuales y clusters</a></li>
        <li><a href="#referencias">Referencias</a></li>
      </ul>
    </li>
  </ul>
</nav><hr></div>


<p> </p>
<div class="gblog-post__anchorwrap">
    <h2 id="requisitos-de-hardware-y-software">
        Requisitos de hardware y software
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/01/mk8s-ocm/#requisitos-de-hardware-y-software" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Requisitos de hardware y software" href="#requisitos-de-hardware-y-software">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>En cuanto a <strong>hardware</strong>, los requisitos mínimos necesarios con los que se ha conseguido probar son los siguientes:</p>
<ul>
<li>CPU. Procesador con 4 cores, Intel Core i5 de sexta generación o equivalente.</li>
<li>RAM. 12 Gb.</li>
<li>Disco. Al menos 30 Gb de almacenamiento.</li>
</ul>
<p>En cuanto a <strong>software</strong>, la prueba se ha realizado con <strong>Ubuntu 20.04</strong> y <strong>Fedora 34</strong>, pero con ligeros cambios se puede ejecutar en cualquier distribución de Linux. Se utiliza la herramienta <a class="gblog-markdown__link" href="https://multipass.run/">multipass</a> para gestionar las dos máquinas virtuales necesarias. Es una herramienta que se puede instalar tanto en <strong>Windows 10</strong> como en <strong>MacOS</strong>, y por lo tanto se podría aplicar en estos sistemas operativos de forma similar casi todo lo expuesto aquí, pero no se ha comprobado realmente el funcionamiento y si los requisitos de hardware mínimos indicados son suficientes en esos casos. Además de <strong>multipass</strong> es necesario instalar:</p>
<ul>
<li><strong>MicroK8s</strong></li>
<li><strong>kubectl</strong></li>
<li><strong>clusteradm</strong></li>
</ul>
<p> 
 </p>
<div class="gblog-post__anchorwrap">
    <h2 id="conceptos-generales">
        Conceptos generales
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/01/mk8s-ocm/#conceptos-generales" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Conceptos generales" href="#conceptos-generales">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Un <strong>cluster</strong> de <strong>Kubernetes</strong> está constituido por varios <strong>nodos</strong> (equipos físicos o máquinas virtuales) que incluyen el <strong>plano de control</strong> (pods necesarios para el funcionamiento de la propia infraestructura de <strong>Kubernetes</strong>) y la capacidad de carga útil para ejecutar aplicaciones. Dependiendo de los recursos y las necesidades nos podemos encontrar con <strong>clusters</strong> en los que idealmente hay <strong>como mínimo</strong> cinco nodos:</p>
<ul>
<li>3 nodos para el <strong>plano de control</strong> denominados <strong>masters</strong>  y que por lo tanto se utilizan para la gestión de la propia plataforma <strong>Kubernetes</strong>.</li>
<li>2 nodos denominados <strong>workers</strong> que ejecutan la carga útil, es decir, las aplicaciones.</li>
</ul>
<p>Utiliza por tanto un patrón que se denomina <strong>master-kubelet</strong> o <strong>master-agent</strong>, ya que en cada <strong>worker</strong> se ejecuta un pod <strong>agente</strong>, llamado <strong>kubelet</strong>, encargado de comunicarse con los nodos <strong>master</strong> para obtener información de cuál es el estado deseado para las aplicaciones y transmitir a los componentes necesarios las tareas para conseguir ese estado (imagen, número de pods de la aplicación, cuenta de servicio, etc.).</p>
<p>El mínimo de dos nodos <strong>workers</strong> proporciona tolerancia a fallos en uno de los nodos, en cuyo caso la carga pasaría a ejecutarse en el otro. Este número de nodos es un mínimo, pero lo habitual en un cluster productivo es que haya muchos más nodos <strong>workers</strong>, incluso decenas de ellos.</p>
<p>Podemos encontrarnos también con <strong>clusters</strong> constituidos por <strong>tres nodos que desempeñan simultáneamente las funciones de master y de worker</strong>, aunque generalmente se trata de configuraciones para entornos de laboratorio con pocos recursos. No es lo recomendable para un entorno de producción.</p>
<p>El término <strong>cluster</strong> implica la existencia de varios elementos que se comportan como uno sólo. Es posible tener una instalación de <strong>Kubernetes</strong> en un sólo nodo, útil para pruebas de concepto o entornos locales, aunque en este caso se pierden muchas de las bondades de <strong>Kubernetes</strong> como el reparto de la carga de trabajo. Para este tutorial se crearán dos instalaciones de <strong>Kubernetes</strong>, cada una con un sólo nodo. Nos tomaremos la libertad de utilizar el término <strong>cluster</strong> para cada una de ellas, aunque estrictamente hablando es incorrecto. Las operaciones a realizar serían casi idénticas en un <strong>cluster</strong> de varios nodos y por lo tanto se podrían aplicar a un <strong>cluster</strong> real. De esta forma nos adaptamos a la terminología que utiliza <strong>OCM</strong>, que se refiere a la orquestación de <strong>clusters</strong>.</p>
<p><strong>OCM</strong> utiliza una arquitectura <strong>maestro-agente</strong> (<strong>master-agent</strong>) entre los clusters, que imita la idea del patrón <strong>master-kubelet</strong> que existe entre los diferentes nodos de un cluster de <strong>Kubernetes</strong>, como se ha mencionado anteriormente. Existirán por tanto dos tipos de clusters:</p>
<ul>
<li>Cluster <strong>Hub</strong>. Se trata del cluster sobre el que se ejecuta el <strong>plano de control</strong> de <strong>OCM</strong>. Se trataría de un cluster muy ligero sobre el que se ejecutan un pequeño conjunto de servicios.</li>
<li>Cluster <strong>Gestionado</strong>, <strong>Managed</strong> cluster o <strong>Klusterlet</strong>. Serían los clusters que son gestionados por el cluster <strong>hub</strong>. El servicio <strong>klusterlet</strong> está continuamente consultando las últimas especificaciones del cluster <strong>hub</strong> y ajustando de forma consistente el cluster de <strong>Kubernetes</strong> para que coincida con el estado esperado.</li>
</ul>
<p>A continuación vamos a describir la instalación de las herramientas y la realización de configuraciones necesarias para conseguir tener un cluster <strong>hub</strong> y un cluster <strong>gestionado</strong> en una configuración mínima que pueda ejecutarse en un ordenador personal sin excesivos recursos de hardware.</p>
<p> 
 </p>
<div class="gblog-post__anchorwrap">
    <h2 id="kubectl">
        Kubectl
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/01/mk8s-ocm/#kubectl" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Kubectl" href="#kubectl">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p><strong>kubectl</strong> es la herramienta de línea de comandos (<strong>CLI</strong>) necesaria para gestionar cualquier cluster de <strong>Kubernetes</strong>.
Dependiendo del sistema operativo, se puede obtener de forma sencilla de los gestores de software integrados en el sistema, pero para este ejemplo utilizaremos la descarga directa del binario.
Si queremos instalar la versión más reciente:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl"><span class="nb">cd</span> /tmp
</span></span><span class="line"><span class="cl">curl -LO <span class="s2">&#34;https://storage.googleapis.com/kubernetes-release/release/</span><span class="k">$(</span>curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt<span class="k">)</span><span class="s2">/bin/linux/amd64/kubectl&#34;</span>
</span></span></code></pre></div><p>Si queremos descargar una versión específica, en este caso la 1.21.8:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl"><span class="nb">cd</span> /tmp
</span></span><span class="line"><span class="cl">curl -LO <span class="s2">&#34;https://storage.googleapis.com/kubernetes-release/release/v1.21.8/bin/linux/amd64/kubectl&#34;</span>
</span></span></code></pre></div><p>Una vez descargado, le damos permisos de ejecución y lo movemos a una ruta que se encuentre en el <code>PATH</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">chmod +x ./kubectl
</span></span><span class="line"><span class="cl">sudo mv ./kubectl /usr/local/bin/kubectl
</span></span></code></pre></div><p>Comprobamos que la versión mostrada coincide con la que queremos:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">kubectl version --client
</span></span></code></pre></div><pre tabindex="0"><code>Client Version: version.Info{Major:&#34;1&#34;, Minor:&#34;21&#34;, GitVersion:&#34;v1.21.8&#34;, 
GitCommit:&#34;4a3b558c52eb6995b3c5c1db5e54111bd0645a64&#34;, GitTreeState:&#34;clean&#34;, 
BuildDate:&#34;2021-12-15T14:52:11Z&#34;, GoVersion:&#34;go1.16.12&#34;, Compiler:&#34;gc&#34;, Platform:&#34;linux/amd64&#34;}
</code></pre><p>Si se desea utiilizar otro método de instalación como los repositorios de paquetes, puede consultarse cómo hacerlo en <a class="gblog-markdown__link" href="https://kubernetes.io/es/docs/tasks/tools/install-kubectl/#instalar-mediante-el-gestor-de-paquetes-del-sistema">https://kubernetes.io/es/docs/tasks/tools/install-kubectl/#instalar-mediante-el-gestor-de-paquetes-del-sistema</a>.</p>
<p> 
 </p>
<div class="gblog-post__anchorwrap">
    <h2 id="instalación-de-multipass">
        Instalación de multipass
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/01/mk8s-ocm/#instalación-de-multipass" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Instalación de multipass" href="#instalaci%c3%b3n-de-multipass">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Para la creación de los clusters vamos a utilizar máquinas virtuales con <a class="gblog-markdown__link" href="https://ubuntu.com">Ubuntu</a> LTS. Por simplicidad vamos a instalar la herramienta <strong>multipass</strong>, que nos permite crear y gestionar máquinas virtuales de <strong>Ubuntu</strong> muy fácilmente. Se instala mediante un paquete snap:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">sudo snap install multipass --classic
</span></span></code></pre></div><p>Nuestro usuario debe pertenecer al grupo que otorga permisos de escritura en el directorio <code>/var/snap/multipass/common/multipass_socket</code>. En el siguiente ejemplo, realizado en un host <strong>Ubuntu</strong> se trata del grupo <em>sudo</em>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">ls -l /var/snap/multipass/common/multipass_socket
</span></span></code></pre></div><pre tabindex="0"><code>srw-rw---- 1 root sudo 0 ene  10 23:30 /var/snap/multipass/common/multipass_socket
</code></pre><p>Si hacemos la misma comprobación en <strong>Fedora</strong> veremos que el grupo que otorga el permiso es <em>wheel</em>:</p>
<pre tabindex="0"><code>srw-rw----. 1 root wheel 0 ene 10 23:40 /var/snap/multipass/common/multipass_socket
</code></pre><p>Comprobamos si nuestro usuario pertenece al grupo <em>sudo</em>  o <em>wheel</em>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">groups <span class="p">|</span> grep -E <span class="s2">&#34;sudo|wheel&#34;</span>
</span></span></code></pre></div><pre tabindex="0"><code>tty uucp dialout cdrom sudo dip video plugdev kvm syslog lpadmin lxd sambashare libvirt
</code></pre><p>En <strong>Fedora</strong> (y cualquier distribución que utilice <strong>firewalld</strong>), si está activado <strong>firewalld</strong>, es necesario añadir la interfaz bridge <em>mpqemubr0</em>, que es la que utiliza <strong>multipass</strong>, a la zona <em>trusted</em> para que puedan arrancar las máquinas virtuales:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">firewall-cmd --zone<span class="o">=</span>trusted --change-interface<span class="o">=</span>mpqemubr0
</span></span><span class="line"><span class="cl">firewall-cmd --runtime-to-permanent
</span></span></code></pre></div><p> 
 </p>
<div class="gblog-post__anchorwrap">
    <h2 id="creación-de-un-cluster-de-un-solo-nodo-para-el-hub">
        Creación de un cluster de un solo nodo para el hub
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/01/mk8s-ocm/#creación-de-un-cluster-de-un-solo-nodo-para-el-hub" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Creación de un cluster de un solo nodo para el hub" href="#creaci%c3%b3n-de-un-cluster-de-un-solo-nodo-para-el-hub">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Creamos una máquina virtual con una CPU, 4 Gb de RAM, llamada <em>ocm-hub</em>, con 10G de disco y basada en la versión <em>LTS</em> de <em>Ubuntu</em>, que al momento de escribir esto es la <em>20.04</em> :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">multipass launch -m 3Gb -c <span class="m">1</span> -n ocm-hub --disk 10G lts
</span></span></code></pre></div><p>Una vez terminada la operación, comprobamos que aparece en la lista de máquinas virtuales, con su dirección IP asignada y el estado <em>Running</em>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">multipass list
</span></span></code></pre></div><pre tabindex="0"><code>Name                    State             IPv4             Image
ocm-hub                 Running           10.89.130.31     Ubuntu 20.04 LTS
</code></pre><p>Está en ejecución, así que conectamos por shell con ella:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">multipass shell ocm-hub
</span></span></code></pre></div><p>Y comprobamos el usuario por defecto:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">whoami
</span></span></code></pre></div><pre tabindex="0"><code>ubuntu
</code></pre><p>Por defecto nos conectamos con un usuario llamado <em>ubuntu</em>, con permisos de <em>sudo</em>. Vamos a instalar <strong>MicroK8s</strong> en la máquina virtual <em>ocm-hub</em>, con una versión concreta de <strong>Kubernetes</strong> (<em>channel</em>). También asignaremos los permisos necesarios para gestionar <strong>MicroK8s</strong>, agregando el usuario al grupo <em>microk8s</em>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">sudo snap install microk8s --classic --channel<span class="o">=</span>1.21
</span></span><span class="line"><span class="cl">sudo usermod -a -G microk8s <span class="nv">$USER</span>
</span></span><span class="line"><span class="cl">sudo chown -f -R <span class="nv">$USER</span> ~/.kube
</span></span></code></pre></div><p>Salimos y volvemos a entrar en la máquina para que se aplique el grupo a nuestro usuario:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl"><span class="nb">exit</span>
</span></span><span class="line"><span class="cl">multipass shell ocm-hub
</span></span></code></pre></div><p>Comprobamos el estado del nuevo cluster, que se creará y arrancará automáticamente tras la instalación de <strong>MicroK8s</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">microk8s status
</span></span></code></pre></div><pre tabindex="0"><code>microk8s is running
high-availability: no
  datastore master nodes: 127.0.0.1:19001
  datastore standby nodes: none
addons:
  enabled:
    ha-cluster           # Configure high availability on the current node
  disabled:
    ambassador           # Ambassador API Gateway and Ingress
    cilium               # SDN, fast with full network policy
. . . 
</code></pre><p>Podemos obtener la url del plano de control:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">microk8s kubectl cluster-info
</span></span></code></pre></div><pre tabindex="0"><code>Kubernetes control plane is running at https://127.0.0.1:16443
</code></pre><p>Vamos a habilitar el addon de <em>DNS</em>, que nos hará falta más adelante:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">microk8s <span class="nb">enable</span> dns
</span></span></code></pre></div><pre tabindex="0"><code>Enabling DNS
Applying manifest
serviceaccount/coredns created
configmap/coredns created
deployment.apps/coredns created
service/kube-dns created
clusterrole.rbac.authorization.k8s.io/coredns created
clusterrolebinding.rbac.authorization.k8s.io/coredns created
Restarting kubelet
DNS is enabled
</code></pre><p>Salimos de la shell de la máquina virtual <em>ocm-hub</em> con <code>exit</code> y comprobamos de nuevo el listado de máquinas. Podemos ver que aparece una nueva dirección IP asignada a la máquina <em>ocm-hub</em>, correspondiente a una interfaz de red creada por <strong>MicroK8s</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">multipass list
</span></span></code></pre></div><pre tabindex="0"><code>Name                    State             IPv4             Image
ocm-hub                 Running           10.89.130.31     Ubuntu 20.04 LTS
                                          10.1.96.0
</code></pre><p> 
 </p>
<div class="gblog-post__anchorwrap">
    <h2 id="configuración-de-acceso-externo-por-nombre-a-través-de-kubectl-para-cluster-ocm-hub">
        Configuración de acceso externo por nombre a través de kubectl para cluster ocm-hub
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/01/mk8s-ocm/#configuración-de-acceso-externo-por-nombre-a-través-de-kubectl-para-cluster-ocm-hub" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Configuración de acceso externo por nombre a través de kubectl para cluster ocm-hub" href="#configuraci%c3%b3n-de-acceso-externo-por-nombre-a-trav%c3%a9s-de-kubectl-para-cluster-ocm-hub">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Abrimos una shell en la máquina <em>ocm-hub</em>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">multipass shell ocm-hub
</span></span></code></pre></div><p>y editamos el archivo de plantilla de los certificados</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">vi /var/snap/microk8s/current/certs/csr.conf.template
</span></span></code></pre></div><p>Agregamos en la sección <code>[ alt_names ]</code> dos entradas DNS nuevas, en este caso <code>DNS.6</code> y <code>DNS.7</code> con el nombre que queremos utilizar desde fuera del cluster para conectarnos.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vim" data-lang="vim"><span class="line"><span class="cl"><span class="nx">DNS</span>.<span class="m">6</span> <span class="p">=</span> <span class="nx">ocm</span><span class="p">-</span><span class="nx">hub</span>.<span class="nx">local</span>
</span></span><span class="line"><span class="cl"><span class="nx">DNS</span>.<span class="m">7</span> <span class="p">=</span> <span class="nx">ocm</span><span class="p">-</span><span class="nx">hub</span>
</span></span></code></pre></div><p>Quedará algo similar a lo siguiente, aunque la cantidad de entradas puede diferir y sobre todo el campo <code>IP.2</code> será diferente en cada caso y no debemos modificarlo:</p>
<pre tabindex="0"><code>[ alt_names ]
DNS.1 = kubernetes
DNS.2 = kubernetes.default
DNS.3 = kubernetes.default.svc
DNS.4 = kubernetes.default.svc.cluster
DNS.5 = kubernetes.default.svc.cluster.local
DNS.6 = ocm-hub.local
DNS.7 = ocm-hub
IP.1 = 127.0.0.1
IP.2 = 10.152.183.1
#MOREIPS
</code></pre><p>Ejecutamos la regeneración de los certificados:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo microk8s refresh-certs
</span></span></code></pre></div><pre tabindex="0"><code>Taking a backup of the current certificates under /var/snap/microk8s/2694/var/log/ca-backup/
Creating new certificates
Can&#39;t load /root/.rnd into RNG
140555660350912:error:2406F079:random number generator:RAND_load_file:Cannot open file:../crypto/rand/randfile.c:88:Filename=/root/.rnd
Can&#39;t load /root/.rnd into RNG
140458579322304:error:2406F079:random number generator:RAND_load_file:Cannot open file:../crypto/rand/randfile.c:88:Filename=/root/.rnd
Signature ok
subject=C = GB, ST = Canonical, L = Canonical, O = Canonical, OU = Canonical, CN = 127.0.0.1
Getting CA Private Key
Signature ok
subject=CN = front-proxy-client
Getting CA Private Key
1
Creating new kubeconfig file
Stopped.
Started.

The CA certificates have been replaced. Kubernetes will restart the pods of your workloads.
Any worker nodes you may have in your cluster need to be removed and re-joined to become aware of the new CA.
</code></pre><p>Salimos de la máquina <em>ocm-hub</em>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl"><span class="nb">exit</span>
</span></span></code></pre></div><p>En el equipo <em>host</em> vamos a editar el archivo <code>/etc/hosts</code> y añadir una entrada con la dirección IP de la máquina <em>ocm-hub</em> y su nombre. La IP la obtuvimos con el comando <code>multipass list</code> o de forma más directa con:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">multipass info ocm-hub <span class="p">|</span> grep IPv4
</span></span></code></pre></div><pre tabindex="0"><code>IPv4:           10.89.130.31
</code></pre><p>La entrada en este caso será:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vim" data-lang="vim"><span class="line"><span class="cl"><span class="m">10</span>.<span class="m">89</span>.<span class="m">130</span>.<span class="m">31</span> <span class="nx">ocm</span><span class="p">-</span><span class="nx">hub</span>.<span class="nx">local</span> <span class="nx">ocm</span><span class="p">-</span><span class="nx">hub</span>
</span></span></code></pre></div><p>Probamos el acceso desde el host:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">curl -kL https://ocm-hub.local:16443
</span></span></code></pre></div><pre tabindex="0"><code>{
  &#34;kind&#34;: &#34;Status&#34;,
  &#34;apiVersion&#34;: &#34;v1&#34;,
  &#34;metadata&#34;: {
    
  },
  &#34;status&#34;: &#34;Failure&#34;,
  &#34;message&#34;: &#34;Unauthorized&#34;,
  &#34;reason&#34;: &#34;Unauthorized&#34;,
  &#34;code&#34;: 401
}
</code></pre><p>Preparamos el archivo de configuración para poder utilizar <code>kubectl</code> desde nuestro host. Para ello vamos a copiar el archivo <code>/var/snap/microk8s/current/credentials/client.config</code> de la máquina <em>ocm-hub</em> al directorio <code>$HOME/.kube</code> de nuestro equipo:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl"><span class="nv">KUBECONFIG</span><span class="o">=</span><span class="nv">$HOME</span>/.kube/ocm-hub.config
</span></span><span class="line"><span class="cl">multipass <span class="nb">exec</span> ocm-hub cat /var/snap/microk8s/current/credentials/client.config &gt; <span class="nv">$KUBECONFIG</span>
</span></span></code></pre></div><p>Editamos el archivo de configuración con <code>vi $KUBECONFIG</code>, cambiando la línea:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vim" data-lang="vim"><span class="line"><span class="cl">    <span class="nx">server</span>: <span class="nx">https</span>:<span class="sr">//</span><span class="m">127</span>.<span class="m">0</span>.<span class="m">0</span>.<span class="m">1</span>:<span class="m">16443</span>
</span></span></code></pre></div><p>por</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vim" data-lang="vim"><span class="line"><span class="cl">    <span class="nx">server</span>: <span class="nx">https</span>:<span class="sr">//</span><span class="nx">ocm</span><span class="p">-</span><span class="nx">hub</span>.<span class="nx">local</span>:<span class="m">16443</span>
</span></span></code></pre></div><p>Probamos a lanzar un comando con <code>kubectl</code> especificando el archivo de configuración que hemos creado para ver los nodos en ejecución. No sería estrictamente necesario especificar el parámetro <code>--kubeconfig</code> puesto que ahora mismo hemos creado la variable la variable <code>KUBECONFIG</code> que determina la ubicación del archivo de configuración para <code>kubectl</code>. Si no existe la variable <code>KUBECONFIG</code> y no utilizamos el parámetro <code>--kubeconfig</code> intentará buscar un archivo de configuración por defecto: <code>$HOME/.kube/config</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">kubectl --kubeconfig<span class="o">=</span><span class="nv">$HOME</span>/.kube/ocm-hub.config get nodes
</span></span></code></pre></div><pre tabindex="0"><code>NAME      STATUS   ROLES    AGE   VERSION
ocm-hub   Ready    &lt;none&gt;   30m  v1.21.7-3+7700880a5c71e2
</code></pre><p>Con esto verificamos que el comando <code>kubectl</code> funciona correctamente y que podemos lanzar comandos contra el cluster <em>ocm-hub</em> desde fuera de la máquina virtual.</p>
<p>Sin utilizar <code>kubectl</code> desde la máquina host podríamos conseguir el mismo resultado lanzando un comando contra la máquina virtual utilizando <code>multipass</code> y la CLI de <strong>MicroK8s</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">multipass <span class="nb">exec</span> ocm-hub microk8s kubectl get nodes
</span></span></code></pre></div><pre tabindex="0"><code>NAME      STATUS   ROLES    AGE   VERSION
ocm-hub   Ready    &lt;none&gt;   30m   v1.21.7-3+7700880a5c71e2
</code></pre><p> 
 </p>
<div class="gblog-post__anchorwrap">
    <h2 id="creación-de-un-cluster-de-un-solo-nodo-que-será-gestionado-desde-el-cluster-hub">
        Creación de un cluster de un solo nodo que será gestionado desde el cluster hub
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/01/mk8s-ocm/#creación-de-un-cluster-de-un-solo-nodo-que-será-gestionado-desde-el-cluster-hub" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Creación de un cluster de un solo nodo que será gestionado desde el cluster hub" href="#creaci%c3%b3n-de-un-cluster-de-un-solo-nodo-que-ser%c3%a1-gestionado-desde-el-cluster-hub">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Creamos una máquina virtual con una CPU, 4 Gb de RAM, llamada <em>ocm-managed</em>, con 10G total de disco y basada en la versión <em>LTS</em> de <em>Ubuntu</em>, que al momento de escribir esto es la <em>20.04</em> :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">multipass launch -m 3Gb -c <span class="m">1</span> -n ocm-managed --disk 10G lts
</span></span></code></pre></div><p>Comprobamos que aparece en la lista de máquinas virtuales, la dirección IP asignada y su estado:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">multipass list
</span></span></code></pre></div><pre tabindex="0"><code>Name                    State             IPv4             Image
ocm-hub                 Running           10.89.130.31     Ubuntu 20.04 LTS
                                          10.1.96.0
ocm-managed             Running           10.89.130.239    Ubuntu 20.04 LTS
</code></pre><p>Está en ejecución, así que conectamos por shell con ella:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">multipass shell ocm-managed
</span></span></code></pre></div><p>De la misma forma que se ha hecho con la máquina <code>ocm-hub</code>, vamos a instalar <strong>MicroK8s</strong> y a dar los permisos necesarios:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">sudo snap install microk8s --classic --channel<span class="o">=</span>1.21
</span></span><span class="line"><span class="cl">sudo usermod -a -G microk8s <span class="nv">$USER</span>
</span></span><span class="line"><span class="cl">sudo chown -f -R <span class="nv">$USER</span> ~/.kube
</span></span><span class="line"><span class="cl"><span class="nb">exit</span>
</span></span><span class="line"><span class="cl">multipass shell ocm-managed
</span></span></code></pre></div><p>Comprobamos el estado del cluster de kubernetes:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">microk8s status
</span></span></code></pre></div><pre tabindex="0"><code>microk8s is running
high-availability: no
  datastore master nodes: 127.0.0.1:19001
  datastore standby nodes: none
addons:
  enabled:
    ha-cluster           # Configure high availability on the current node
  disabled:
    ambassador           # Ambassador API Gateway and Ingress
    cilium               # SDN, fast with full network policy
</code></pre><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">microk8s kubectl cluster-info
</span></span></code></pre></div><pre tabindex="0"><code>Kubernetes control plane is running at https://127.0.0.1:16443
</code></pre><p>Vamos a habilitar el addon de DNS, que nos hará falta más adelante:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">microk8s <span class="nb">enable</span> dns
</span></span></code></pre></div><pre tabindex="0"><code>Enabling DNS
Applying manifest
serviceaccount/coredns created
configmap/coredns created
deployment.apps/coredns created
service/kube-dns created
clusterrole.rbac.authorization.k8s.io/coredns created
clusterrolebinding.rbac.authorization.k8s.io/coredns created
Restarting kubelet
DNS is enabled
</code></pre><p>Salimos de la shell de la máquina virtual <em>ocm-managed</em> con <code>exit</code> y comprobamos de nuevo el listado de máquinas. Podemos ver que aparece una nueva dirección IP asignada, correspondiente a una interfaz de red creada por MicroK8s:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">multipass list
</span></span></code></pre></div><pre tabindex="0"><code>Name                    State             IPv4             Image
ocm-hub                 Running           10.89.130.31     Ubuntu 20.04 LTS
                                          10.1.96.0
ocm-managed             Running           10.89.130.239    Ubuntu 20.04 LTS
                                          10.1.37.0
</code></pre><p> 
 </p>
<div class="gblog-post__anchorwrap">
    <h2 id="configuración-de-acceso-externo-a-través-de-kubectl-para-el-cluster-ocm-managed">
        Configuración de acceso externo a través de kubectl para el cluster ocm-managed
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/01/mk8s-ocm/#configuración-de-acceso-externo-a-través-de-kubectl-para-el-cluster-ocm-managed" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Configuración de acceso externo a través de kubectl para el cluster ocm-managed" href="#configuraci%c3%b3n-de-acceso-externo-a-trav%c3%a9s-de-kubectl-para-el-cluster-ocm-managed">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Abrimos una shell en la máquina <em>ocm-managed</em>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">multipass shell ocm-managed
</span></span></code></pre></div><p>y editamos el archivo de plantilla de los certificados:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">vi /var/snap/microk8s/current/certs/csr.conf.template
</span></span></code></pre></div><p>Agregamos en la sección <code>[ alt_names ]</code> dos entradas DNS nuevas, en este caso <code>DNS.6</code> y <code>DNS.7</code> con el nombre que queremos utilizar desde fuera del cluster para conectarnos (<em>ocm-managed.local</em> y <em>ocm-managed</em>)
).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vim" data-lang="vim"><span class="line"><span class="cl"><span class="nx">DNS</span>.<span class="m">6</span> <span class="p">=</span> <span class="nx">ocm</span><span class="p">-</span><span class="nx">managed</span>.<span class="nx">local</span>
</span></span><span class="line"><span class="cl"><span class="nx">DNS</span>.<span class="m">7</span> <span class="p">=</span> <span class="nx">ocm</span><span class="p">-</span><span class="nx">managed</span>
</span></span></code></pre></div><p>Quedará algo similar a lo siguiente, aunque la cantidad de entradas puede diferir y sobre todo el campo <code>IP.2</code> será diferente en cada caso y no debemos modificarlo:</p>
<pre tabindex="0"><code>[ alt_names ]
DNS.1 = kubernetes
DNS.2 = kubernetes.default
DNS.3 = kubernetes.default.svc
DNS.4 = kubernetes.default.svc.cluster
DNS.5 = kubernetes.default.svc.cluster.local
DNS.6 = ocm-managed.local
DNS.7 = ocm-managed
IP.1 = 127.0.0.1
IP.2 = 10.152.183.1
#MOREIPS
</code></pre><p>Ejecutamos la regeneración de los certificados:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">sudo microk8s refresh-certs
</span></span></code></pre></div><pre tabindex="0"><code>Taking a backup of the current certificates under /var/snap/microk8s/2694/var/log/ca-backup/
Creating new certificates
Can&#39;t load /root/.rnd into RNG
140603790988736:error:2406F079:random number generator:RAND_load_file:Cannot open file:../crypto/rand/randfile.c:88:Filename=/root/.rnd
Can&#39;t load /root/.rnd into RNG
140352521098688:error:2406F079:random number generator:RAND_load_file:Cannot open file:../crypto/rand/randfile.c:88:Filename=/root/.rnd
Signature ok
subject=C = GB, ST = Canonical, L = Canonical, O = Canonical, OU = Canonical, CN = 127.0.0.1
Getting CA Private Key
Signature ok
subject=CN = front-proxy-client
Getting CA Private Key
1
Creating new kubeconfig file
Stopped.
Started.

The CA certificates have been replaced. Kubernetes will restart the pods of your workloads.
Any worker nodes you may have in your cluster need to be removed and re-joined to become aware of the new CA.
</code></pre><p>Salimos de la máquina <em>ocm-managed</em>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl"><span class="nb">exit</span>
</span></span></code></pre></div><p>En el equipo host vamos editar el archivo <code>/etc/hosts</code> y añadir una entrada con la dirección IP de la máquina <code>ocm-managed</code> y su nombre. La IP la obtuvimos con el comando <code>multipass list</code> o de forma más directa con:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">multipass info ocm-managed <span class="p">|</span> grep IPv4
</span></span></code></pre></div><pre tabindex="0"><code>IPv4:           10.89.130.239
</code></pre><p>La entrada en este caso será:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vim" data-lang="vim"><span class="line"><span class="cl"><span class="m">10</span>.<span class="m">89</span>.<span class="m">130</span>.<span class="m">239</span> <span class="nx">ocm</span><span class="p">-</span><span class="nx">managed</span>.<span class="nx">local</span> <span class="nx">ocm</span><span class="p">-</span><span class="nx">managed</span>
</span></span></code></pre></div><p>Probamos el acceso desde el host:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">curl -kL https://ocm-managed.local:16443
</span></span></code></pre></div><pre tabindex="0"><code>{
  &#34;kind&#34;: &#34;Status&#34;,
  &#34;apiVersion&#34;: &#34;v1&#34;,
  &#34;metadata&#34;: {
    
  },
  &#34;status&#34;: &#34;Failure&#34;,
  &#34;message&#34;: &#34;Unauthorized&#34;,
  &#34;reason&#34;: &#34;Unauthorized&#34;,
  &#34;code&#34;: 401
}
</code></pre><p>Preparamos el archivo de configuración para poder utilizar <code>kubectl</code> desde nuestro host:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl"><span class="nv">KUBECONFIG</span><span class="o">=</span><span class="nv">$HOME</span>/.kube/ocm-managed.config
</span></span><span class="line"><span class="cl">multipass <span class="nb">exec</span> ocm-managed cat /var/snap/microk8s/current/credentials/client.config &gt; <span class="nv">$KUBECONFIG</span>
</span></span></code></pre></div><p>Editamos el archivo de configuración con:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">vi <span class="nv">$KUBECONFIG</span>
</span></span></code></pre></div><p>cambiando la línea:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vim" data-lang="vim"><span class="line"><span class="cl">    <span class="nx">server</span>: <span class="nx">https</span>:<span class="sr">//</span><span class="m">127</span>.<span class="m">0</span>.<span class="m">0</span>.<span class="m">1</span>:<span class="m">16443</span>
</span></span></code></pre></div><p>por</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vim" data-lang="vim"><span class="line"><span class="cl">    <span class="nx">server</span>: <span class="nx">https</span>:<span class="sr">//</span><span class="nx">ocm</span><span class="p">-</span><span class="nx">managed</span>.<span class="nx">local</span>:<span class="m">16443</span>
</span></span></code></pre></div><p>Probamos a lanzar un comando con <code>kubectl</code> especificando el archivo de configuración que hemos creado para ver los nodos en ejecución:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">kubectl --kubeconfig<span class="o">=</span><span class="nv">$HOME</span>/.kube/ocm-managed.config get nodes
</span></span></code></pre></div><p>No sería estrictamente necesario especificar el parámetro <code>--kubeconfig</code> puesto que ahora mismo hemos creado la variable la variable <code>KUBECONFIG</code> que determina la ubicación del archivo de configuración para <code>kubectl</code>. Si no existe la variable <code>KUBECONFIG</code> y no utilizamos el parámetro <code>--kubeconfig</code>, el comando <code>kubectl</code> intentará buscar un archivo de configuración en <code>$HOME/.kube/config</code>. Nos aparecerá algo como lo siguiente:</p>
<pre tabindex="0"><code>NAME          STATUS   ROLES    AGE   VERSION
ocm-managed   Ready    &lt;none&gt;   34m   v1.21.7-3+7700880a5c71e2
</code></pre><p>Con esto verificamos que el comando <code>kubectl</code> funciona correctamente y que podemos lanzar comandos contra el cluster <em>ocm-managed</em> desde fuera de la máquina virtual.</p>
<p>Sin utilizar <code>kubectl</code> desde la máquina host podríamos conseguir el mismo resultado lanzando un comando contra la máquina virtual utilizando <code>multipass</code> y la CLI de <strong>MicroK8s</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">multipass <span class="nb">exec</span> ocm-managed microk8s kubectl get nodes
</span></span></code></pre></div><p> 
 </p>
<div class="gblog-post__anchorwrap">
    <h2 id="instalación-de-open-cluster-management">
        Instalación de Open Cluster Management
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/01/mk8s-ocm/#instalación-de-open-cluster-management" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Instalación de Open Cluster Management" href="#instalaci%c3%b3n-de-open-cluster-management">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><div class="gblog-post__anchorwrap">
    <h3 id="instalación-de-clusteradm">
        Instalación de clusteradm
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/01/mk8s-ocm/#instalación-de-clusteradm" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Instalación de clusteradm" href="#instalaci%c3%b3n-de-clusteradm">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h3>
</div><p>Para interactuar con <strong>Open Cluster Management</strong> (a partir de ahora <strong>OCM</strong>) existe una herramienta <em>CLI</em> llamada <code>clusteradm</code>.</p>
<p>El primer paso será instalarla. Para ello, descargamos el script de instalación:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">curl -L https://raw.githubusercontent.com/open-cluster-management-io/clusteradm/main/install.sh -o /tmp/install-clusteradm.sh
</span></span><span class="line"><span class="cl">chmod +x /tmp/install-clusteradm.sh
</span></span></code></pre></div><p>Examinamos el contenido antes de ejecutarlo. Básicamente descarga de <em>github</em> la última versión del binario adecuada para nuestra máquina y la copia en <code>/usr/local/bin</code>, aunque podemos modificar el lugar de destino modificando una variable. Si cambiamos la línea:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vim" data-lang="vim"><span class="line"><span class="cl"><span class="p">:</span> ${<span class="nx">INSTALL_DIR</span>:<span class="p">=</span><span class="s2">&#34;/usr/local/bin&#34;</span>}
</span></span></code></pre></div><p>por cualquier otra ruta que se encuentre en el <em>PATH</em> y sobre la que tengamos permisos de escritura, no será necesario ejecutar este script con <code>sudo</code>. Si queremos instalar en la ruta por defecto <code>/usr/local/bin</code> tendremos que utilizar <code>sudo</code> al ejecutar <code>/tmp/install-clusteradm.sh</code>.</p>
<p>Ejecutamos el script:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">/tmp/install-clusteradm.sh 
</span></span></code></pre></div><p>y se realizará la instalación:</p>
<pre tabindex="0"><code>Getting the latest clusteradm CLI...
Your system is linux_amd64
Installing clusteradm CLI...

Installing v0.1.0-alpha.7 OCM clusteradm CLI...
Downloading https://github.com/open-cluster-management-io/clusteradm/releases/download/v0.1.0-alpha.7/clusteradm_linux_amd64.tar.gz ...
clusteradm installed into /opt/k8stools successfully.

To get started with clusteradm, please visit https://open-cluster-management.io/getting-started/
</code></pre><p>Comprobamos que <code>clusteradm</code> funciona y puede conectar con nuestros clusters:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">clusteradm  --kubeconfig<span class="o">=</span><span class="nv">$HOME</span>/.kube/ocm-hub.config version
</span></span></code></pre></div><pre tabindex="0"><code>client		version	:0.1.0-alpha.7
server release	version	:v1.21.7-3+7700880a5c71e2
</code></pre><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">clusteradm  --kubeconfig<span class="o">=</span><span class="nv">$HOME</span>/.kube/ocm-managed.config version
</span></span></code></pre></div><pre tabindex="0"><code>client		version	:0.1.0-alpha.7
server release	version	:v1.21.7-3+7700880a5c71e2
</code></pre><p>Vamos a definir unos alias para que resulte más fácil ejecutar <code>clusteradm</code> contra nuestros clusters. Serán <code>cahub</code> para conectarnos al cluster <strong>hub</strong> y <code>camng</code> para conectarnos al cluster <strong>gestionado</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl"><span class="nb">alias</span> <span class="nv">cahub</span><span class="o">=</span><span class="s1">&#39;clusteradm --kubeconfig=$HOME/.kube/ocm-hub.config&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nb">alias</span> <span class="nv">camng</span><span class="o">=</span><span class="s1">&#39;clusteradm --kubeconfig=$HOME/.kube/ocm-managed.config&#39;</span>
</span></span></code></pre></div><p>y comprobamos que funciona ejecutando el comando <code>version</code> de <code>clusteradm</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">cahub version
</span></span></code></pre></div><pre tabindex="0"><code>client		version	:0.1.0-alpha.7
server release	version	:v1.21.7-3+7700880a5c71e2
</code></pre><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">camng version
</span></span></code></pre></div><pre tabindex="0"><code>client		version	:0.1.0-alpha.7
server release	version	:v1.21.7-3+7700880a5c71e2
</code></pre><p>y hacemos lo mismo con <code>kubectl</code>. Creamos los alias <code>khub</code> para ejecutar <code>kubectl</code> contra el cluster <strong>hub</strong> y <code>kmng</code> para ejecutar comandos contra el cluster <strong>gestionado</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl"><span class="nb">alias</span> <span class="nv">khub</span><span class="o">=</span><span class="s1">&#39;kubectl --kubeconfig=$HOME/.kube/ocm-hub.config&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nb">alias</span> <span class="nv">kmng</span><span class="o">=</span><span class="s1">&#39;kubectl --kubeconfig=$HOME/.kube/ocm-managed.config&#39;</span>
</span></span></code></pre></div><p>y comprobamos que funcionan correctamente:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">khub version
</span></span><span class="line"><span class="cl">kmng version
</span></span></code></pre></div><p>nos devolverá la versión del cliente y del servidor:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">Client Version: version.Info<span class="o">{</span>Major:<span class="s2">&#34;1&#34;</span>, Minor:<span class="s2">&#34;23&#34;</span>, GitVersion:<span class="s2">&#34;v1.23.1&#34;</span>, GitCommit:<span class="s2">&#34;86ec240af8cbd1b60bcc4c03c20da9b98005b92e&#34;</span>, GitTreeState:<span class="s2">&#34;clean&#34;</span>, BuildDate:<span class="s2">&#34;2021-12-16T11:41:01Z&#34;</span>, GoVersion:<span class="s2">&#34;go1.17.5&#34;</span>, Compiler:<span class="s2">&#34;gc&#34;</span>, Platform:<span class="s2">&#34;linux/amd64&#34;</span><span class="o">}</span>
</span></span><span class="line"><span class="cl">Server Version: version.Info<span class="o">{</span>Major:<span class="s2">&#34;1&#34;</span>, Minor:<span class="s2">&#34;21+&#34;</span>, GitVersion:<span class="s2">&#34;v1.21.7-3+7700880a5c71e2&#34;</span>, GitCommit:<span class="s2">&#34;7700880a5c71e25c44491ef5c7d7fb30527d8337&#34;</span>, GitTreeState:<span class="s2">&#34;clean&#34;</span>, BuildDate:<span class="s2">&#34;2021-11-17T22:02:47Z&#34;</span>, GoVersion:<span class="s2">&#34;go1.16.10&#34;</span>, Compiler:<span class="s2">&#34;gc&#34;</span>, Platform:<span class="s2">&#34;linux/amd64&#34;</span><span class="o">}</span>
</span></span><span class="line"><span class="cl">WARNING: version difference between client <span class="o">(</span>1.23<span class="o">)</span> and server <span class="o">(</span>1.21<span class="o">)</span> exceeds the supported minor version skew of +/-1
</span></span><span class="line"><span class="cl">Client Version: version.Info<span class="o">{</span>Major:<span class="s2">&#34;1&#34;</span>, Minor:<span class="s2">&#34;23&#34;</span>, GitVersion:<span class="s2">&#34;v1.23.1&#34;</span>, GitCommit:<span class="s2">&#34;86ec240af8cbd1b60bcc4c03c20da9b98005b92e&#34;</span>, GitTreeState:<span class="s2">&#34;clean&#34;</span>, BuildDate:<span class="s2">&#34;2021-12-16T11:41:01Z&#34;</span>, GoVersion:<span class="s2">&#34;go1.17.5&#34;</span>, Compiler:<span class="s2">&#34;gc&#34;</span>, Platform:<span class="s2">&#34;linux/amd64&#34;</span><span class="o">}</span>
</span></span><span class="line"><span class="cl">Server Version: version.Info<span class="o">{</span>Major:<span class="s2">&#34;1&#34;</span>, Minor:<span class="s2">&#34;21+&#34;</span>, GitVersion:<span class="s2">&#34;v1.21.7-3+7700880a5c71e2&#34;</span>, GitCommit:<span class="s2">&#34;7700880a5c71e25c44491ef5c7d7fb30527d8337&#34;</span>, GitTreeState:<span class="s2">&#34;clean&#34;</span>, BuildDate:<span class="s2">&#34;2021-11-17T22:02:47Z&#34;</span>, GoVersion:<span class="s2">&#34;go1.16.10&#34;</span>, Compiler:<span class="s2">&#34;gc&#34;</span>, Platform:<span class="s2">&#34;linux/amd64&#34;</span><span class="o">}</span>
</span></span></code></pre></div><p>Vamos a comprobar los <em>namespaces</em> existentes en nuestro cluster <em>hub</em>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">khub get namespaces
</span></span></code></pre></div><pre tabindex="0"><code>NAME              STATUS   AGE
kube-system       Active   40m
kube-public       Active   40m
kube-node-lease   Active   40m
default           Active   40m
</code></pre><div class="gblog-post__anchorwrap">
    <h3 id="instalación-del-control-plane-de-ocm-en-el-cluster-hub">
        Instalación del Control Plane de OCM en el cluster hub
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/01/mk8s-ocm/#instalación-del-control-plane-de-ocm-en-el-cluster-hub" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Instalación del Control Plane de OCM en el cluster hub" href="#instalaci%c3%b3n-del-control-plane-de-ocm-en-el-cluster-hub">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h3>
</div><p>Una vez instalado <code>clusteradm</code> y creados los alias, instalamos un <em>Cluster Manager</em> de <strong>OCM</strong> en el cluster de <strong>Kubernetes</strong> que utilizaremos como <strong>hub</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">cahub init
</span></span></code></pre></div><pre tabindex="0"><code>The multicluster hub control plane has been initialized successfully!

You can now register cluster(s) to the hub control plane. Log onto those cluster(s) and run the following command:

    clusteradm join \
    --hub-token eyJhbGciOiJSUzI1NiIsImtpZCI6Ik5Va2FPSXVCYTM2WDExVHBqaEFkdm92Slp0SWwxcTlkM29DQnVIczlnNTQifQ.eyJhbGciOiJSUzI1NiIsImtpZCI6Ik5Va2FPSXVCYTM2WDExVHBqaEFkdm92Slp0SWwxcTlkM29DQnVIczlnNTQifQeyJhbGciOiJSUzI1NiIsImtpZCI6Ik5Va2FPSXVCYTM2WDExVHBqaEFkdm92Slp0SWwxcTlkM29DQnVIczlnNTQifQeyJhbGciOiJSUzI1NiIsImtpZCI6Ik5Va2FPSXVCYTM2WDExVHBqaEFkdm92Slp0SWwxcTlkM29DQnVIczlnNTQifQeyJhbGciOiJSUzI1NiIsImtpZCI6Ik5Va2FPSXVCYTM2WDExVHBqaEFkdm92Slp0SWwxcTlkM29DQnVIczlnNTQifQeyJhbGciOiJSUzI1NiIsImtpZCI6Ik5Va2FPSXVCYTM2WDExVHBqaEFkdm92Slp0SWwxcTlkM29DQnVIczlnNTQifQeyJhbGciOiJSUzI1NiIsImtpZCI6Ik5Va2FPSXVCYTM2WDExVHBqaEFkdm92Slp0SWwxcTlkM29DQnVIczlnNTQifQeyJhbGciOiJSUzI1NiIsImtpZCI6Ik5Va2FPSXVCYTM2WDExVHBqaEFkdm92Slp0SWwxcTlkM29DQnVIczlnNTQifQ \
    --hub-apiserver https://ocm-hub.local:16443 \
    --cluster-name &lt;cluster_name&gt;

Replace &lt;cluster_name&gt; with a cluster name of your choice. For example, cluster1.
</code></pre><p>Con este comando, <code>clusteradm</code> instala en el cluster <strong>hub</strong> el operador <strong>registration-operator</strong> que es el responsable de instalar y actualizar de forma consistente varios componentes del entorno de <strong>OCM</strong>.</p>
<p>Al final de la ejecución nos muestra un comando con un <em>token</em> que debemos guardar, ya que nos permitirá registrar los clusters gestionados desde el <strong>hub</strong>.</p>
<p>Comprobamos los pods que están en ejecución en el cluster <strong>hub</strong> tras el comando <code>init</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">khub get pods -A
</span></span></code></pre></div><pre tabindex="0"><code>NAMESPACE                     NAME                                                      READY   STATUS    RESTARTS   AGE
kube-system                   calico-kube-controllers-f7868dd95-tm7nq                   1/1     Running   3          2d
kube-system                   calico-node-8kfnv                                         1/1     Running   3          2d
open-cluster-management       cluster-manager-6bc5bd8856-6mpm6                          1/1     Running   0          5m3s
open-cluster-management-hub   cluster-manager-registration-controller-59f487d68-dtrjt   1/1     Running   0          4m46s
open-cluster-management-hub   cluster-manager-registration-webhook-c96d56db-9bxcc       1/1     Running   0          4m46s
open-cluster-management-hub   cluster-manager-work-webhook-547d7cc954-ctxbs             1/1     Running   0          4m46s
open-cluster-management-hub   cluster-manager-placement-controller-66485c45fd-2kdkt     1/1     Running   0          4m46s
</code></pre><p>Tras la ejecución en el cluster <strong>hub</strong> del comando <code>init</code> vamos a comprobar los <em>namespaces</em> que tenemos disponibles:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">khub get namespaces
</span></span></code></pre></div><pre tabindex="0"><code>NAME                          STATUS   AGE
kube-system                   Active   40m
kube-public                   Active   40m
kube-node-lease               Active   40m
default                       Active   40m
open-cluster-management       Active   5m
open-cluster-management-hub   Active   5m
</code></pre><p>podemos ver que nos ha añadido dos nuevos <em>namespaces</em>: <em>open-cluster-management</em> y <em>open-cluster-management-hub</em>.</p>
<p>En el namespace <em>open-cluster-management</em> se encuentra el pod del <em>registration operator</em>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">khub -n open-cluster-management get pods
</span></span></code></pre></div><pre tabindex="0"><code>NAME                               READY   STATUS    RESTARTS   AGE
cluster-manager-6bc5bd8856-6mpm6   1/1     Running   0          24m
</code></pre><p>En el namespace <em>open-cluster-management-hub</em> se ejecutan los pods del <em>control plane</em> de <strong>OCM</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">khub -n open-cluster-management-hub get pods
</span></span></code></pre></div><pre tabindex="0"><code>NAME                                                      READY   STATUS    RESTARTS   AGE
cluster-manager-registration-controller-59f487d68-dtrjt   1/1     Running   0          25m
cluster-manager-registration-webhook-c96d56db-9bxcc       1/1     Running   0          25m
cluster-manager-work-webhook-547d7cc954-ctxbs             1/1     Running   0          25m
cluster-manager-placement-controller-66485c45fd-2kdkt     1/1     Running   0          25m
</code></pre><p>Se ha creado un <em>Custom Resource</em> de tipo <strong>clustermanager</strong> llamado <strong>cluster-manager</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">khub get clustermanager
</span></span></code></pre></div><pre tabindex="0"><code>NAME              AGE
cluster-manager   27m
</code></pre><p>Para examinar la información de la instalación podemos examinar el contenido de ese objeto:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">khub get clustermanager cluster-manager -o yaml
</span></span></code></pre></div><div class="gblog-post__anchorwrap">
    <h3 id="desplegar-un-agente-klusterlet-en-el-cluster-gestionado">
        Desplegar un agente klusterlet en el cluster gestionado
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/01/mk8s-ocm/#desplegar-un-agente-klusterlet-en-el-cluster-gestionado" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Desplegar un agente klusterlet en el cluster gestionado" href="#desplegar-un-agente-klusterlet-en-el-cluster-gestionado">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h3>
</div><p>Una vez que se han instalado todos los componentes del <em>control plane</em> en el cluster <strong>hub</strong> y que están en ejecución, podemos pasar a registrar un cluster en <strong>OCM</strong> para que sea <strong>gestionado</strong> por él.</p>
<p>Para ello, primero tenemos que asegurarnos de que la API del cluster <strong>hub</strong> es accesible desde el cluster que queremos que sea <strong>gestionado</strong>.</p>
<p>Obtenemos la dirección <em>IP</em> del cluster <strong>hub</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">multipass info ocm-hub <span class="p">|</span> grep IPv4 <span class="p">|</span> tr -s <span class="s1">&#39; &#39;</span> <span class="p">|</span> cut -d <span class="s1">&#39; &#39;</span> -f <span class="m">2</span>
</span></span></code></pre></div><pre tabindex="0"><code>10.89.130.31
</code></pre><p>Podemos lanzar una petición con <code>curl</code> sobre una dirección htttps en el puerto 16443 correspondiente a esa dirección IP. Si llegamos correctamente nos devolverá un mensaje <em>JSON</em> de autenticación incorrecta (código 401):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">multipass <span class="nb">exec</span> ocm-managed -- <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>curl -kL https://<span class="k">$(</span>multipass info ocm-hub <span class="p">|</span> grep IPv4 <span class="p">|</span> tr -s <span class="s1">&#39; &#39;</span> <span class="p">|</span> cut -d <span class="s1">&#39; &#39;</span> -f 2<span class="k">)</span>:16443
</span></span></code></pre></div><pre tabindex="0"><code>{
  &#34;kind&#34;: &#34;Status&#34;,
  &#34;apiVersion&#34;: &#34;v1&#34;,
  &#34;metadata&#34;: {
    
  },
  &#34;status&#34;: &#34;Failure&#34;,
  &#34;message&#34;: &#34;Unauthorized&#34;,
  &#34;reason&#34;: &#34;Unauthorized&#34;,
  &#34;code&#34;: 401
}
</code></pre><p>Para registrar el cluster <strong>gestionado</strong> utilizaremos el comando que nos generó <code>clusteradm init</code> y lo lanzaremos desde nuestro equipo. Salimos de la máquina del cluster <strong>gestionado</strong> y lanzamos el comando con <code>clusteradmin</code> (concretamente con el alias <code>camng</code>) contra nuestro cluster <strong>gestionado</strong>. El valor del parámetro <code>--cluster-name</code> no tiene por qué coincidir con el nombre de la máquina virtual, puede ser cualquier nombre que queramos asignarle.</p>
<p>Salimos de la máquina virtual:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl"><span class="nb">exit</span>
</span></span></code></pre></div><p>y desde el host lanzamos el comando <code>join</code> contra el cluster gestionado, utilizando el alias de <code>clusteradm</code> <code>camng</code> y la dirección IP de la MV <em>ocm-hub</em> en el parámetro <code>--hub-apiserver</code>, ya que desde nuestro cluster <em>gestionado</em> no resolverá el nombre <code>ocm-hub</code> u <code>ocm-hub.local</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">camng join <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--hub-token eyJhbGciOiJSUzI1NiIsImtpZCI6Ik5Va2FPSXVCYTM2WDExVHBqaEFkdm92Slp0SWwxcTlkM29DQnVIczlnNTQifQ.eyJhbGciOiJSUzI1NiIsImtpZCI6Ik5Va2FPSXVCYTM2WDExVHBqaEFkdm92Slp0SWwxcTlkM29DQnVIczlnNTQifQeyJhbGciOiJSUzI1NiIsImtpZCI6Ik5Va2FPSXVCYTM2WDExVHBqaEFkdm92Slp0SWwxcTlkM29DQnVIczlnNTQifQeyJhbGciOiJSUzI1NiIsImtpZCI6Ik5Va2FPSXVCYTM2WDExVHBqaEFkdm92Slp0SWwxcTlkM29DQnVIczlnNTQifQeyJhbGciOiJSUzI1NiIsImtpZCI6Ik5Va2FPSXVCYTM2WDExVHBqaEFkdm92Slp0SWwxcTlkM29DQnVIczlnNTQifQeyJhbGciOiJSUzI1NiIsImtpZCI6Ik5Va2FPSXVCYTM2WDExVHBqaEFkdm92Slp0SWwxcTlkM29DQnVIczlnNTQifQeyJhbGciOiJSUzI1NiIsImtpZCI6Ik5Va2FPSXVCYTM2WDExVHBqaEFkdm92Slp0SWwxcTlkM29DQnVIczlnNTQifQeyJhbGciOiJSUzI1NiIsImtpZCI6Ik5Va2FPSXVCYTM2WDExVHBqaEFkdm92Slp0SWwxcTlkM29DQnVIczlnNTQifQ <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--hub-apiserver https://10.89.130.31:16443 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--cluster-name managed1
</span></span></code></pre></div><pre tabindex="0"><code>Waiting for the management components to become ready...
Please log onto the hub cluster and run the following command:

    clusteradm accept --clusters managed1
</code></pre><p>Los <strong>CSR</strong>, <strong>Certificate Signing Request</strong> o <strong>Solicitudes de Firma de Certificados</strong>, son uno de los primeros pasos para la obtención de un certificado TLS. Cuando se realiza la operación <strong>join</strong> correctamente, el cluster gestionado genera <strong>CSRs</strong> en el cluster hub, que deben ser aceptadas. Por ello revisamos si hay recursos <strong>CSR</strong> en el cluster <strong>hub</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">khub get csr -w
</span></span></code></pre></div><pre tabindex="0"><code>NAME             AGE    SIGNERNAME                            REQUESTOR                                                         CONDITION
managed1-d479q   5m   kubernetes.io/kube-apiserver-client   system:serviceaccount:open-cluster-management:cluster-bootstrap   Pending
</code></pre><p>Aceptamos la petición de unión del cluster <strong>gestionado</strong> al cluster <strong>hub</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">cahub accept --clusters managed1
</span></span></code></pre></div><pre tabindex="0"><code>CSR managed1-d479q approved
set hubAcceptsClient to true for managed cluster managed1
</code></pre><p>Verificamos que el objeto <strong>managedcluster</strong> se ha creado correctamente:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">khub get managedcluster
</span></span></code></pre></div><pre tabindex="0"><code>NAME       HUB ACCEPTED   MANAGED CLUSTER URLS         JOINED   AVAILABLE   AGE
managed1   true           https://10.248.21.90:16443   True     True        10m
</code></pre><p>Comprobamos la creación de <em>namespaces</em> en el cluster <strong>gestionado</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">kmng get namespace
</span></span></code></pre></div><pre tabindex="0"><code>NAME                                  STATUS   AGE
kube-system                           Active   1h
kube-public                           Active   1h
kube-node-lease                       Active   1h
default                               Active   1h
open-cluster-management               Active   10m
open-cluster-management-agent-addon   Active   10m
open-cluster-management-agent         Active   10m
</code></pre><p>Verficamos la instalación de los agentes de <strong>OCM</strong> en el cluster gestionado con:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">kmng -n open-cluster-management-agent get pod
</span></span></code></pre></div><pre tabindex="0"><code>NAME                                             READY   STATUS    RESTARTS   AGE
klusterlet-registration-agent-598fd79988-jxx7n   1/1     Running   0          10m
klusterlet-work-agent-7d47f4b5c5-dnkqw           1/1     Running   0          10m
</code></pre><p>La información de instalación debe figurar en un <em>custom resource</em> llamado <strong>klusterlet</strong>  que sólo debe estar instalado en el cluster <strong>gestionado</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">kmng get klusterlet klusterlet -o yaml
</span></span></code></pre></div><p>En el cluster <strong>hub</strong> se habrá creado un <em>namespace</em> con el nombre que hamos dado al <em>managedcluster</em>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">khub get namespace
</span></span></code></pre></div><pre tabindex="0"><code>NAME                          STATUS   AGE
kube-system                   Active   2h
kube-public                   Active   2h
kube-node-lease               Active   2h
default                       Active   2h
open-cluster-management       Active   15m
open-cluster-management-hub   Active   15m
managed1                      Active   10m
</code></pre><p>Vemos que se ha creado lo que se denomina <strong>cluster namespace</strong>, un <em>namespace</em> con el mismo nombre que se ha dado al cluster <strong>gestionado</strong> durante el registro. Este <em>namespace</em> lo crea el <strong>registration controller</strong> del <strong>hub</strong> de forma automática cuando finaliza el registro y se utiliza para almacenar <strong>custom resources</strong> y configuraciones que pertenezcan al cluster <strong>gestionado</strong>.</p>
<p>Ya disponemos de dos clusters de <strong>Kubernetes</strong>, uno que sirve como <strong>hub</strong> para gestionar otros clusters y un cluster <strong>gestionado</strong>.
Lo recomendado es que el cluster <strong>hub</strong> se utilice sólamente para gestionar otros clusters, pero es posible instalar el agente en el propio cluster <strong>hub</strong> y que sea gestionado para cargas de trabajo y otros fines como cualquier otro cluster <strong>gestionado</strong>, a través de <strong>OCM</strong>.</p>
<p> 
 </p>
<div class="gblog-post__anchorwrap">
    <h2 id="despliegue-de-una-aplicación-en-el-cluster-gestionado">
        Despliegue de una aplicación en el cluster gestionado
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/01/mk8s-ocm/#despliegue-de-una-aplicación-en-el-cluster-gestionado" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Despliegue de una aplicación en el cluster gestionado" href="#despliegue-de-una-aplicaci%c3%b3n-en-el-cluster-gestionado">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Vamos a hacer un <strong>despliegue de una aplicación sencilla</strong> en el cluster <strong>gestionado</strong> para comprobar que funciona correctamente <strong>OCM</strong>.</p>
<p>Vamos a manejar dos conceptos:</p>
<ul>
<li><strong>ManifestWork</strong>: Un <strong>custom resource</strong> que se encuentra en el cluster <strong>hub</strong> y que agrupa una lista de recursos de <strong>Kubernetes</strong> que se desean aplicar en el cluster <strong>gestionado</strong>.</li>
<li><strong>AppliedManifestWork</strong>: Un <strong>custom resource</strong> del cluster <strong>gestionado</strong> que se utiliza para persistir la lista de recursos definidos en un <strong>ManifestWork</strong>.</li>
</ul>
<p>Primero debemos verificar tres puntos:</p>
<ol>
<li>Que la <em>Custom Resource Definition</em> (<em>CRD</em>) <strong>ManifestWork</strong> está instalada en el cluster <strong>hub</strong>:</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">khub get crd manifestworks.work.open-cluster-management.io
</span></span></code></pre></div><pre tabindex="0"><code>manifestworks.work.open-cluster-management.io   2022-01-14T23:30:58Z
</code></pre><ol start="2">
<li>Que la <em>CRD</em> <strong>AppliedManifestWork</strong> está instalada en el cluster <strong>gestionado</strong>:</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">kmng get crd appliedmanifestworks.work.open-cluster-management.io
</span></span></code></pre></div><pre tabindex="0"><code>NAME                                                   CREATED AT
appliedmanifestworks.work.open-cluster-management.io   2022-01-14T23:31:12Z
</code></pre><ol start="3">
<li>Que el <strong>agente</strong> está ejecutándose correctamente en el cluster <strong>gestionado</strong>:</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">kmng -n open-cluster-management-agent get pod
</span></span></code></pre></div><pre tabindex="0"><code>NAME                                             READY   STATUS    RESTARTS   AGE
klusterlet-registration-agent-69c5568fcc-98fj9   1/1     Running   1          1h
klusterlet-work-agent-54dfddf7b-78llj            1/1     Running   11         1h
</code></pre><p>Una vez comprobados los puntos anteriores, vamos a crear en el <strong>clusternamespace</strong> (en nuestro caso <em>managed1</em>) un <strong>ManifestWork</strong>. Este <strong>ManifestWork</strong> crea los siguientes objetos en el cluster:</p>
<ul>
<li>Un <em>namespace</em> llamado <em>frontend</em>.</li>
<li>Una <em>service account</em> llamada <em>nginx-sa</em> en el namespace <em>frontend</em>.</li>
<li>Un <em>deployment</em> en el namespace <em>frontend</em> llamado <em>nginx-deployment</em> que desplegará 3 <em>pods</em> de <em>nginx</em> a la escucha en el puerto 8080.</li>
<li>Un <em>servicio</em> de tipo <em>NodePort</em> que apuntará a los pods del <em>deployment</em> anterior, que permitirá acceder a los <em>nginx</em> desde fuera del cluster.</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">khub apply -f - <span class="s">&lt;&lt;EOF
</span></span></span><span class="line"><span class="cl"><span class="s">apiVersion: work.open-cluster-management.io/v1
</span></span></span><span class="line"><span class="cl"><span class="s">kind: ManifestWork
</span></span></span><span class="line"><span class="cl"><span class="s">metadata:
</span></span></span><span class="line"><span class="cl"><span class="s">  namespace: managed1
</span></span></span><span class="line"><span class="cl"><span class="s">  name: nginx-simple
</span></span></span><span class="line"><span class="cl"><span class="s">spec:
</span></span></span><span class="line"><span class="cl"><span class="s">  workload:
</span></span></span><span class="line"><span class="cl"><span class="s">    manifests:
</span></span></span><span class="line"><span class="cl"><span class="s">      - apiVersion: v1
</span></span></span><span class="line"><span class="cl"><span class="s">        kind: Namespace
</span></span></span><span class="line"><span class="cl"><span class="s">        metadata:
</span></span></span><span class="line"><span class="cl"><span class="s">          name: frontend
</span></span></span><span class="line"><span class="cl"><span class="s">      - apiVersion: v1
</span></span></span><span class="line"><span class="cl"><span class="s">        kind: ServiceAccount
</span></span></span><span class="line"><span class="cl"><span class="s">        metadata:
</span></span></span><span class="line"><span class="cl"><span class="s">          namespace: frontend
</span></span></span><span class="line"><span class="cl"><span class="s">          name: nginx-sa
</span></span></span><span class="line"><span class="cl"><span class="s">      - apiVersion: apps/v1
</span></span></span><span class="line"><span class="cl"><span class="s">        kind: Deployment
</span></span></span><span class="line"><span class="cl"><span class="s">        metadata:
</span></span></span><span class="line"><span class="cl"><span class="s">          namespace: frontend
</span></span></span><span class="line"><span class="cl"><span class="s">          name: nginx-deployment
</span></span></span><span class="line"><span class="cl"><span class="s">          labels:
</span></span></span><span class="line"><span class="cl"><span class="s">            app: nginx
</span></span></span><span class="line"><span class="cl"><span class="s">        spec:
</span></span></span><span class="line"><span class="cl"><span class="s">          replicas: 3
</span></span></span><span class="line"><span class="cl"><span class="s">          selector:
</span></span></span><span class="line"><span class="cl"><span class="s">            matchLabels:
</span></span></span><span class="line"><span class="cl"><span class="s">              app: nginx
</span></span></span><span class="line"><span class="cl"><span class="s">          template:
</span></span></span><span class="line"><span class="cl"><span class="s">            metadata:
</span></span></span><span class="line"><span class="cl"><span class="s">              labels:
</span></span></span><span class="line"><span class="cl"><span class="s">                app: nginx
</span></span></span><span class="line"><span class="cl"><span class="s">            spec:
</span></span></span><span class="line"><span class="cl"><span class="s">              serviceAccountName: nginx-sa
</span></span></span><span class="line"><span class="cl"><span class="s">              containers:
</span></span></span><span class="line"><span class="cl"><span class="s">                - name: nginx
</span></span></span><span class="line"><span class="cl"><span class="s">                  image: nginx:1.21.5
</span></span></span><span class="line"><span class="cl"><span class="s">                  ports:
</span></span></span><span class="line"><span class="cl"><span class="s">                    - containerPort: 8080
</span></span></span><span class="line"><span class="cl"><span class="s">      - apiVersion: v1
</span></span></span><span class="line"><span class="cl"><span class="s">        kind: Service
</span></span></span><span class="line"><span class="cl"><span class="s">        metadata:
</span></span></span><span class="line"><span class="cl"><span class="s">          namespace: frontend
</span></span></span><span class="line"><span class="cl"><span class="s">          name: nginx
</span></span></span><span class="line"><span class="cl"><span class="s">          labels:
</span></span></span><span class="line"><span class="cl"><span class="s">            app: nginx
</span></span></span><span class="line"><span class="cl"><span class="s">        spec:
</span></span></span><span class="line"><span class="cl"><span class="s">          type: NodePort
</span></span></span><span class="line"><span class="cl"><span class="s">          ports:
</span></span></span><span class="line"><span class="cl"><span class="s">          - nodePort: 30001
</span></span></span><span class="line"><span class="cl"><span class="s">            port: 8080
</span></span></span><span class="line"><span class="cl"><span class="s">            protocol: TCP
</span></span></span><span class="line"><span class="cl"><span class="s">            targetPort: 8080
</span></span></span><span class="line"><span class="cl"><span class="s">          selector:
</span></span></span><span class="line"><span class="cl"><span class="s">            app: nginx
</span></span></span><span class="line"><span class="cl"><span class="s">EOF</span>
</span></span></code></pre></div><pre tabindex="0"><code>manifestwork.work.open-cluster-management.io/nginx-simple created
</code></pre><p>Comprobamos el resultado con:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">khub get manifestwork nginx-simple -n managed1 -o yaml
</span></span></code></pre></div><p>En el apartado <code>.status.resourceStatus</code> del comando anterior aparecerá información de cómo se ha aplicado el recurso:</p>
<pre tabindex="0"><code>  resourceStatus:
    manifests:
    - conditions:
      - lastTransitionTime: &#34;2022-01-11T08:31:07Z&#34;
        message: Apply manifest complete
        reason: AppliedManifestComplete
        status: &#34;True&#34;
        type: Applied
      - lastTransitionTime: &#34;2022-01-11T08:31:07Z&#34;
        message: Resource is available
        reason: ResourceAvailable
        status: &#34;True&#34;
        type: Available
</code></pre><p>Comprobamos si en el cluster <strong>gestionado</strong> se han desplegado los <em>pods</em> de <em>ngnix</em> en el namespace <em>frontend</em>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">kmng get pods -n frontend
</span></span></code></pre></div><pre tabindex="0"><code>NAME                               READY   STATUS    RESTARTS   AGE
nginx-deployment-844674d9b-m2vgn   1/1     Running   0          5m
nginx-deployment-844674d9b-7z6ph   1/1     Running   0          5m
nginx-deployment-844674d9b-z9jz6   1/1     Running   0          5m
</code></pre><p>En la especificación del servicio hemos indicado el puerto 30001 en la línea:</p>
<pre tabindex="0"><code>          - nodePort: 30001
</code></pre><p>Comprobamos el servicio creado en el <em>namespace</em> <em>frontend</em>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">kmng get svc -n frontend
</span></span></code></pre></div><pre tabindex="0"><code>NAME    TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
nginx   NodePort   10.152.183.92   &lt;none&gt;        8080:30001/TCP   5m
</code></pre><p>Vemos que efectivamente el puerto expuesto al exterior del cluster es el 30001. Vamos a hacer una petición con <code>curl</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">curl http://ocm-managed:30001/
</span></span></code></pre></div><pre tabindex="0"><code>$ curl http://ocm-managed:30001/
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;title&gt;Welcome to nginx!&lt;/title&gt;
&lt;style&gt;
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;h1&gt;Welcome to nginx!&lt;/h1&gt;
&lt;p&gt;If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.&lt;/p&gt;

&lt;p&gt;For online documentation and support please refer to
&lt;a href=&#34;http://nginx.org/&#34;&gt;nginx.org&lt;/a&gt;.&lt;br/&gt;
Commercial support is available at
&lt;a href=&#34;http://nginx.com/&#34;&gt;nginx.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thank you for using nginx.&lt;/em&gt;&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre><p>También podemos introducir la url <a class="gblog-markdown__link" href="http://ocm-managed:30001/">http://ocm-managed:30001/</a> en nuestro navegador:</p>
<p><img src="/img/ocm-browser-pod-nginx.png#center" alt=""  /></p>
<p>Con esto hemos hecho una breve prueba de que <strong>OCM</strong> está funcionando. Cualquier cambio que hagamos sobre el <strong>ManifestWork</strong> se aplicará en el cluster <strong>managed</strong> en unos segundos.</p>
<p> 
 </p>
<div class="gblog-post__anchorwrap">
    <h2 id="parada-de-clusters-y-máquinas-virtuales">
        Parada de clusters y máquinas virtuales
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/01/mk8s-ocm/#parada-de-clusters-y-máquinas-virtuales" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Parada de clusters y máquinas virtuales" href="#parada-de-clusters-y-m%c3%a1quinas-virtuales">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Una vez terminada la prueba, el orden recomendado para parar el entorno que hemos creado sería detener los clusters y luego las máquinas virtuales, aunque microk8s se ejecuta como un servicio de las máquinas y por lo tanto se detendría en caso de parada ordenada de la máquina.</p>
<p>Para detener el cluster <strong>gestionado</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">multipass <span class="nb">exec</span> ocm-managed microk8s stop
</span></span></code></pre></div><p>Parada del cluster <strong>hub</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">multipass <span class="nb">exec</span> ocm-hub microk8s stop
</span></span></code></pre></div><p>Y por último parada de las <strong>máquinas virtuales</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">multipass stop ocm-managed
</span></span><span class="line"><span class="cl">multipass stop ocm-hub
</span></span></code></pre></div><p> 
 </p>
<div class="gblog-post__anchorwrap">
    <h2 id="arranque-de-máquinas-virtuales-y-clusters">
        Arranque de máquinas virtuales y clusters
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/01/mk8s-ocm/#arranque-de-máquinas-virtuales-y-clusters" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Arranque de máquinas virtuales y clusters" href="#arranque-de-m%c3%a1quinas-virtuales-y-clusters">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><p>Si en otro momento queremos arrancar de nuevo los clusters, arrancaríamos las máquinas virtuales y luego los clusters:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">multipass start ocm-hub
</span></span><span class="line"><span class="cl">multipass start ocm-managed
</span></span><span class="line"><span class="cl">multipass <span class="nb">exec</span> ocm-hub microk8s start
</span></span><span class="line"><span class="cl">multipass <span class="nb">exec</span> ocm-managed microk8s start
</span></span></code></pre></div><p> 
 </p>
<div class="gblog-post__anchorwrap">
    <h2 id="referencias">
        Referencias
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/01/mk8s-ocm/#referencias" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Referencias" href="#referencias">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><ul>
<li><a class="gblog-markdown__link" href="https://kubernetes.io/es/docs/tasks/tools/install-kubectl/">https://kubernetes.io/es/docs/tasks/tools/install-kubectl/</a></li>
<li><a class="gblog-markdown__link" href="https://multipass.run">https://multipass.run</a></li>
<li><a class="gblog-markdown__link" href="https://microk8s.io/">https://microk8s.io/</a></li>
<li><a class="gblog-markdown__link" href="https://open-cluster-management.io/">https://open-cluster-management.io/</a></li>
<li><a class="gblog-markdown__link" href="https://github.com/open-cluster-management-io">https://github.com/open-cluster-management-io</a></li>
<li><a class="gblog-markdown__link" href="https://ubuntu.com/tutorials/getting-started-with-kubernetes-ha#2-install-multipass">https://ubuntu.com/tutorials/getting-started-with-kubernetes-ha#2-install-multipass</a></li>
<li><a class="gblog-markdown__link" href="https://github.com/stolostron">https://github.com/stolostron</a></li>
</ul>]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://dvdcr.gitlab.io/tags/kubernetes" term="kubernetes" label="kubernetes" />
                            
                        
                    
                
            
        </entry>
    
        
        <entry>
            <title type="html"><![CDATA[Nivel de carga y estado de salud de la batería de un portátil en GNU/linux]]></title>
            <link href="https://dvdcr.gitlab.io/posts/2022/01/salud-bateria/?utm_source=atom_feed" rel="alternate" type="text/html"  hreflang="en" />
            <id>https://dvdcr.gitlab.io/posts/2022/01/salud-bateria/</id>
            
            <published>2022-01-09T00:00:00+01:00</published>
            <updated>2022-01-09T00:00:00+01:00</updated>
            
            
            <content type="html"><![CDATA[<p><img src="/img/batteries.svg#center" alt=""  /></p>
<p>A día de hoy, cualquier combinación de distribución GNU/Linux y entorno de escritorio, por ligera y minimalista que sea, ofrece información del nivel de carga de la batería de un portátil, mostrando un porcentaje de energía disponible con respecto al total y en muchos casos una estimación del tiempo restante en función del tipo de uso que actualmente se está haciendo del sistema. Esta información suele aparecer en un indicador en la barra de estado y además en alguna sección del panel de control. Pero ¿en qué información se basan esos indicadores? ¿Cómo podemos acceder con mayor nivel de detalle a esa información para tener más conocimiento de ella o hacer nuestras propias mediciones?</p>
<p><strong>UPower</strong> es un software que proporciona una capa de abstracción para la gestión de energía en equipos con GNU/Linux. Enumera orígenes de energía (baterías, fuentes de alimentación, &hellip;), mantiene estadísticas y datos históricos sobre ellos y notifica cuando se producen cambios de estado. Está compuesto de un demonio (<strong>upowerd</strong>), una <em>API</em> y un conjunto de de herramientas de línea de comandos. El demonio proporciona su funcionalidad a las aplicaciones a través del bus del sistema (mediante el servicio <em>org.freedesktop.UPower</em>, a través de <em>D-Bus</em>,). <em>PolicyKit</em> limita el acceso a la funcionalidad de <strong>UPower</strong> de iniciar el modo de hibernación o apagar el sistema operativo (<em>freedesktop.upower.policy</em>). El programa cliente de línea de comandos <code>upower</code> puede utilizarse para consultar y monitorizar la información sobre los dispositivos de suministro de energía del sistema. Los entornos de escritorio proporcionan herramientas gráficas para hacer uso de la funcionalidad de <strong>UPower</strong>, como <em>GNOME Power Manager</em> y <em>Xfce Power Manager</em>.</p>
<p>Vamos a centrarnos en un uso básico del comando <code>upower</code>. Podemos consultar los dispositivos de energía disponibles en el sistema con el comando <code>upower -e</code> o <code>upower --enumerate</code>. Nos mostrará la ruta de acceso a ellos, que luego utilizaremos para obtener más información de un dispositivo en concreto:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">$ upower -e
</span></span><span class="line"><span class="cl">/org/freedesktop/UPower/devices/line_power_AC
</span></span><span class="line"><span class="cl">/org/freedesktop/UPower/devices/battery_BAT0
</span></span><span class="line"><span class="cl">/org/freedesktop/UPower/devices/battery_BAT1
</span></span><span class="line"><span class="cl">/org/freedesktop/UPower/devices/line_power_ucsi_source_psy_USBC000o001
</span></span><span class="line"><span class="cl">/org/freedesktop/UPower/devices/DisplayDevice
</span></span></code></pre></div><p>La lista completa de parámetros de todos los dispositivos se puede obtener con <code>upower -d</code> o <code>upower --dump</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">$ upower -d
</span></span><span class="line"><span class="cl">Device: /org/freedesktop/UPower/devices/line_power_AC
</span></span><span class="line"><span class="cl">  native-path:          AC
</span></span><span class="line"><span class="cl">  power supply:         yes
</span></span><span class="line"><span class="cl">  updated:              mar <span class="m">04</span> ene <span class="m">2022</span> 21:03:18 <span class="o">(</span><span class="m">1451</span> seconds ago<span class="o">)</span>
</span></span><span class="line"><span class="cl">  has history:          no
</span></span><span class="line"><span class="cl">  has statistics:       no
</span></span><span class="line"><span class="cl">  line-power
</span></span><span class="line"><span class="cl">    warning-level:       none
</span></span><span class="line"><span class="cl">    online:              no
</span></span><span class="line"><span class="cl">    icon-name:          <span class="s1">&#39;ac-adapter-symbolic&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">. . . 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Device: /org/freedesktop/UPower/devices/battery_BAT1
</span></span><span class="line"><span class="cl">  native-path:          BAT1
</span></span><span class="line"><span class="cl">  vendor:               Celxpert
</span></span><span class="line"><span class="cl">  model:                01AV424
</span></span><span class="line"><span class="cl">  serial:               <span class="m">4287</span>
</span></span><span class="line"><span class="cl">  power supply:         yes
</span></span><span class="line"><span class="cl">  updated:              mar <span class="m">04</span> ene <span class="m">2022</span> 21:27:19 <span class="o">(</span><span class="m">10</span> seconds ago<span class="o">)</span>
</span></span><span class="line"><span class="cl">  has history:          yes
</span></span><span class="line"><span class="cl">  has statistics:       yes
</span></span><span class="line"><span class="cl">  battery
</span></span><span class="line"><span class="cl">    present:             yes
</span></span><span class="line"><span class="cl">    rechargeable:        yes
</span></span><span class="line"><span class="cl">    state:               discharging
</span></span><span class="line"><span class="cl">    warning-level:       none
</span></span><span class="line"><span class="cl">    energy:              12,57 Wh
</span></span><span class="line"><span class="cl">    energy-empty:        <span class="m">0</span> Wh
</span></span><span class="line"><span class="cl">    energy-full:         19,89 Wh
</span></span><span class="line"><span class="cl">    energy-full-design:  24,05 Wh
</span></span><span class="line"><span class="cl">    energy-rate:         4,424 W
</span></span><span class="line"><span class="cl">    voltage:             11,259 V
</span></span><span class="line"><span class="cl">    <span class="nb">time</span> to empty:       2,8 hours
</span></span><span class="line"><span class="cl">    percentage:          62%
</span></span><span class="line"><span class="cl">    capacity:            80,5405%
</span></span><span class="line"><span class="cl">    technology:          lithium-polymer
</span></span><span class="line"><span class="cl">    icon-name:          <span class="s1">&#39;battery-full-symbolic&#39;</span>
</span></span><span class="line"><span class="cl">  History <span class="o">(</span>charge<span class="o">)</span>:
</span></span><span class="line"><span class="cl">    1641328039	62,000	discharging
</span></span><span class="line"><span class="cl">  History <span class="o">(</span>rate<span class="o">)</span>:
</span></span><span class="line"><span class="cl">    1641328039	4,424	discharging
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  . . .   
</span></span></code></pre></div><p>Podemos ver con el comando anterior la situación de carga de la batería con respecto al total que admite actualmente (<em>percentaje: 62%</em>) y la situación aproximada de la salud de la batería, diferenciando entre <em>energy-full</em> (la cantidad de energía que actualmente puede almacenar como máximo) y <em>energy-full-design</em> (la cantidad de energía que puede almacenar por diseño, es decir cuando está nueva).</p>
<p>En este caso se trata de un portátil con dos baterías. Podemos ver información de cada una de ellas con <code>upower -i</code> o <code>upower --show-info</code> seguido de la ruta del componente, que será la devuelta por el comando <code>upower -e</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">$ upower -i /org/freedesktop/UPower/devices/battery_BAT0
</span></span><span class="line"><span class="cl">  native-path:          BAT0
</span></span><span class="line"><span class="cl">  vendor:               LGC
</span></span><span class="line"><span class="cl">  model:                01AV489
</span></span><span class="line"><span class="cl">  serial:               <span class="m">1297</span>
</span></span><span class="line"><span class="cl">  power supply:         yes
</span></span><span class="line"><span class="cl">  updated:              mar <span class="m">04</span> ene <span class="m">2022</span> 21:27:19 <span class="o">(</span><span class="m">10</span> seconds ago<span class="o">)</span>
</span></span><span class="line"><span class="cl">  has history:          yes
</span></span><span class="line"><span class="cl">  has statistics:       yes
</span></span><span class="line"><span class="cl">  battery
</span></span><span class="line"><span class="cl">    present:             yes
</span></span><span class="line"><span class="cl">    rechargeable:        yes
</span></span><span class="line"><span class="cl">    state:               fully-charged
</span></span><span class="line"><span class="cl">    warning-level:       none
</span></span><span class="line"><span class="cl">    energy:              17,24 Wh
</span></span><span class="line"><span class="cl">    energy-empty:        <span class="m">0</span> Wh
</span></span><span class="line"><span class="cl">    energy-full:         18,7 Wh
</span></span><span class="line"><span class="cl">    energy-full-design:  23,94 Wh
</span></span><span class="line"><span class="cl">    energy-rate:         1,408 W
</span></span><span class="line"><span class="cl">    voltage:             12,731 V
</span></span><span class="line"><span class="cl">    percentage:          100%
</span></span><span class="line"><span class="cl">    capacity:            72,0134%
</span></span><span class="line"><span class="cl">    technology:          lithium-polymer
</span></span><span class="line"><span class="cl">    icon-name:          <span class="s1">&#39;battery-full-charged-symbolic&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">$ $ upower -i /org/freedesktop/UPower/devices/battery_BAT1
</span></span><span class="line"><span class="cl">  native-path:          BAT1
</span></span><span class="line"><span class="cl">  vendor:               Celxpert
</span></span><span class="line"><span class="cl">  model:                01AV424
</span></span><span class="line"><span class="cl">  serial:               <span class="m">4287</span>
</span></span><span class="line"><span class="cl">  power supply:         yes
</span></span><span class="line"><span class="cl">  updated:              mar <span class="m">04</span> ene <span class="m">2022</span> 21:27:19 <span class="o">(</span><span class="m">10</span> seconds ago<span class="o">)</span>
</span></span><span class="line"><span class="cl">  has history:          yes
</span></span><span class="line"><span class="cl">  has statistics:       yes
</span></span><span class="line"><span class="cl">  battery
</span></span><span class="line"><span class="cl">    present:             yes
</span></span><span class="line"><span class="cl">    rechargeable:        yes
</span></span><span class="line"><span class="cl">    state:               discharging
</span></span><span class="line"><span class="cl">    warning-level:       none
</span></span><span class="line"><span class="cl">    energy:              14,43 Wh
</span></span><span class="line"><span class="cl">    energy-empty:        <span class="m">0</span> Wh
</span></span><span class="line"><span class="cl">    energy-full:         20,04 Wh
</span></span><span class="line"><span class="cl">    energy-full-design:  24,05 Wh
</span></span><span class="line"><span class="cl">    energy-rate:         5,544 W
</span></span><span class="line"><span class="cl">    voltage:             11,386 V
</span></span><span class="line"><span class="cl">    <span class="nb">time</span> to empty:       2,6 hours
</span></span><span class="line"><span class="cl">    percentage:          70%
</span></span><span class="line"><span class="cl">    capacity:            79,7505%
</span></span><span class="line"><span class="cl">    technology:          lithium-polymer
</span></span><span class="line"><span class="cl">    icon-name:          <span class="s1">&#39;battery-full-symbolic&#39;</span>
</span></span><span class="line"><span class="cl">  History <span class="o">(</span>rate<span class="o">)</span>:
</span></span><span class="line"><span class="cl">    1640122870	5,544	discharging
</span></span></code></pre></div><p><strong>Sysfs</strong> es un sistema de archivos virtual que proporciona el núcleo de Linux y que exporta información sobre los dispositivos y sus controladores desde el modelo de dispositivos del núcleo hacia el espacio del usuario. Genera una estructura de directorios y archivos que representan los dispositivos en una jerarquía de directorios y cada uno de sus parámetros como un archivo cuyo contenido es el valor actual de dicho parámetro. Además de permitir consultar información de estos dispositivos, también permite modificar algunos de ellos.</p>
<p>Uno de los directorios que expone es <code>/sys/class</code>, que contiene cada clase de dispositivo registrada por el kernel. Las clases
de dispositivo describen un tipo funcional de dispositivo, por ejemplo, dispositivos de entrada, dispositivos de red o dispositivos de bloques. Para el tema que nos interesa se puede conocer, por ejemplo, el estado de la batería de un portátil y el estado de la alimentación externa comprobando la información que contienen archivos almacenados dentro del directorio <code>/sys/class/power_supply</code>. Dependiendo del equipo habrá diferentes elementos, pero en el caso de un portátil existirá como mínimo el directorio <code>/sys/class/power_supply/BAT0</code> que contendrá los parámetros de la batería principal.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">$ ls  /sys/class/power_supply/
</span></span><span class="line"><span class="cl">AC  BAT0  BAT1  ucsi-source-psy-USBC000:001
</span></span></code></pre></div><p>Si mostramos el directorio correspondiente a la batería 0 (BAT0), nos aparecerá un archivo por parámetro de la batería:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">$ ls  /sys/class/power_supply/BAT0
</span></span><span class="line"><span class="cl">alarm           charge_control_end_threshold    charge_stop_threshold  energy_full         hwmon3        power      serial_number  technology  voltage_min_design
</span></span><span class="line"><span class="cl">capacity        charge_control_start_threshold  cycle_count            energy_full_design  manufacturer  power_now  status         <span class="nb">type</span>        voltage_now
</span></span><span class="line"><span class="cl">capacity_level  charge_start_threshold          device                 energy_now          model_name    present    subsystem      uevent
</span></span></code></pre></div><p>Podemos consultar el valor de la carga actual (<em>energy_now</em>), carga total cuando está llena (<em>energy_full</em>) y carga total cuando está llena según su diseño original (<em>energy_full_design</em>), entre otros parámetros, mostrando el contenido de los archivos que se encuentran dentro de ese directorio:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Shell" data-lang="Shell"><span class="line"><span class="cl">$ cat /sys/class/power_supply/BAT0/status
</span></span><span class="line"><span class="cl">Discharging
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># cat /sys/class/power_supply/BAT0/capacity</span>
</span></span><span class="line"><span class="cl"><span class="m">70</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">$ cat /sys/class/power_supply/BAT0/energy_now
</span></span><span class="line"><span class="cl"><span class="m">14320000</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">$ cat /sys/class/power_supply/BAT0/energy_full
</span></span><span class="line"><span class="cl"><span class="m">20610000</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">$ cat /sys/class/power_supply/BAT0/energy_full_design
</span></span><span class="line"><span class="cl"><span class="m">24050000</span>
</span></span></code></pre></div><p>Las aplicaciones utilizan principalmente las llamadas al bus del sistema para obtener los datos que necesitan, pero nosotros de forma muy sencilla podemos obtener la información de la batería a través de estos dos medios: el comando <code>upower</code> o el sistema de archivos <strong>sysfs</strong>, y utilizarla directamente o a través de scripts.</p>
<div class="gblog-post__anchorwrap">
    <h2 id="referencias">
        Referencias
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/01/salud-bateria/#referencias" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Referencias" href="#referencias">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><ul>
<li><a class="gblog-markdown__link" href="https://upower.freedesktop.org/">https://upower.freedesktop.org/</a></li>
<li><a class="gblog-markdown__link" href="https://man7.org/linux/man-pages/man5/sysfs.5.html">https://man7.org/linux/man-pages/man5/sysfs.5.html</a></li>
</ul>]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://dvdcr.gitlab.io/tags/linux" term="linux" label="linux" />
                            
                        
                    
                
            
        </entry>
    
        
        <entry>
            <title type="html"><![CDATA[Proteger GRUB con contraseña]]></title>
            <link href="https://dvdcr.gitlab.io/posts/2022/01/proteger-grub/?utm_source=atom_feed" rel="alternate" type="text/html"  hreflang="en" />
            <id>https://dvdcr.gitlab.io/posts/2022/01/proteger-grub/</id>
            
            <published>2022-01-02T00:00:00+01:00</published>
            <updated>2022-01-02T00:00:00+01:00</updated>
            
            
            <content type="html"><![CDATA[<p><img src="/img/grub-pass.svg#center" alt=""  /></p>
<p>Para hacer GRUB más seguro y evitar que alguien no autorizado pueda cambiar los parámetros de arranque o utilizar la línea de comandos, se puede especificar una contraseña que se solicitará en caso de querer editar las opciones del menú de GRUB en el arranque.</p>
<p>Para ello, primero debemos especificar una contraseña y almacenarla cifrada en un archivo.  Este paso lo podemos realizar con el siguiente comando:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Bash" data-lang="Bash"><span class="line"><span class="cl">sudo grub2-setpassword 
</span></span></code></pre></div><p>y especificar dos veces la contraseña que queramos establecer.</p>
<p>El comando anterior generará el archivo <code>/boot/grub2/user.cfg</code> con el hash de la contraseña.</p>
<p>Ahora es necesario recrear el archivo de configuración de grub para que incluya la contraseña especificada:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Bash" data-lang="Bash"><span class="line"><span class="cl">sudo grub2-mkconfig -o /boot/grub2/grub.cfg
</span></span></code></pre></div><p>Una vez hecho esto, al reiniciar el equipo, si se intentan modificar las opciones de arranque de GRUB pulsando <code>e</code>, se solicitará un usuario (será <em>root</em> en este caso) y una contraseña, que será la que se indicó al comando <code>grub2-setpassword</code>.</p>
<div class="gblog-post__anchorwrap">
    <h2 id="referencias">
        Referencias
        <a data-clipboard-text="https://dvdcr.gitlab.io/posts/2022/01/proteger-grub/#referencias" class="gblog-post__anchor gblog-post__anchor--right clip" aria-label="Anchor Referencias" href="#referencias">
            <svg class="icon gblog_link"><use xlink:href="#gblog_link"></use></svg>
        </a>
    </h2>
</div><ul>
<li><a class="gblog-markdown__link" href="https://www.tecmint.com/password-protect-grub-in-linux/">https://www.tecmint.com/password-protect-grub-in-linux/</a></li>
</ul>]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="https://dvdcr.gitlab.io/tags/linux" term="linux" label="linux" />
                            
                        
                    
                
            
        </entry>
    
</feed>
