<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Proyectos on gvisoc.com</title>
    <link>https://gvisoc.com/categories/proyectos/</link>
    <description>Recent content in Proyectos on gvisoc.com</description>
    <image>
      <title>gvisoc.com</title>
      <url>https://gvisoc.com/images/site-umina-gs.jpg</url>
      <link>https://gvisoc.com/images/site-umina-gs.jpg</link>
    </image>
    <generator>Hugo -- 0.151.0</generator>
    <language>es-ES</language>
    <managingEditor>gabriel@gvisoc.com (Gabriel Viso Carrera)</managingEditor>
    <webMaster>gabriel@gvisoc.com (Gabriel Viso Carrera)</webMaster>
    <copyright>Gabriel Viso Carrera</copyright>
    <lastBuildDate>Sun, 07 Sep 2025 07:49:35 +0000</lastBuildDate>
    <atom:link href="https://gvisoc.com/categories/proyectos/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Reenviar tráfico TCP y UDP por la red con iptables</title>
      <link>https://gvisoc.com/posts/iptables-router/</link>
      <pubDate>Sun, 07 Sep 2025 07:49:35 +0000</pubDate><author>gabriel@gvisoc.com (Gabriel Viso Carrera)</author>
      <guid>https://gvisoc.com/posts/iptables-router/</guid>
      <description>Este método permite encaminar tráfico UDP y TCP a través de la red cuando no se necesita proxy, o cuando las soluciones basadas en proxies fallan.</description>
      <content:encoded><![CDATA[<p>Este es el tercer artículo de la serie que empezó con &ldquo;<a href="https://gvisoc.com/posts/cambios-xmpp/">Cambios en mi red de la mano de XMPP</a>.&rdquo;</p>
<h2 id="el-problema">El problema</h2>
<p>En aquel artículo, casi al final de todo, comentaba que hacía falta aplicar una solución para que el tráfico XMPP del resto de puertos (5000, 5222,&hellip; todos los que no son el 80 ó el 443) fuese reenviado desde el VPS a mi servidor en casa.</p>
<p>Probé algunas cosas con la configuración de NGINX y su módulo <code>streams</code>, pero no funcionó, así que voy a describir aquí cómo acabé usando las capacidades de filtrado de paquetes de Linux (el núcleo mismo) a través de <a href="https://www.netfilter.org/projects/iptables/index.html">iptables (en inglés)</a>.</p>
<p>Mi solución está basada y adaptada a partir de lo explicado en este <a href="https://ryanwelch.me/blog/port-forward-with-tailscale/">artículo (en inglés)</a> de <a href="https://ryanwelch.me">Ryan Welch</a>.</p>
<figure class="ma0 w-75">
    <img loading="lazy" src="/images/20250910-solution.png"
         alt="El diagrama que representa lo que queremos conseguir y acabamos de detallar."/> <figcaption>
            <p>El objetivo</p>
        </figcaption>
</figure>

<p>Al final del artículo, en cualquier caso, dejaré las configuraciones que intenté aplicar en NGINX para procesar tráfico TCP y UDP en general. Quizá lo necesitemos más adelante, o a lo mejor decides probarlo y comentarme qué es lo que hice mal.</p>
<h2 id="solución">Solución</h2>
<p>Como comentaba antes, Linux (NO GNU/Linux, sino &ldquo;Linux&rdquo; sin más, el núcleo) tiene funcionalidades de filtrado de paquetes de red incluidas, que podemos configurar para nuestros propios fines.</p>
<p>Para configurar la redirección de tráfico en el VPS, entramos por SSH y habilitaremos <em>IP forwarding</em> en el núcleo:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="go">sudo sysctl -w net.ipv4.ip_forward=1
</span></span></span></code></pre></div><p>Esto puede que se aplique entre arranques, o no, dependiendo de tu distribución. Para asegurarte, edita el fichero <code>/etc/sysctl.conf</code> y descomenta la línea <code>net.ipv4.ip_forward=1</code>.</p>
<p>Tras ello, ya podrás redirigir puertos y rangos de puertos con instrucciones como la siguiente:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="go">iptables -A PREROUTING -t nat -p tcp -i eth0 --dport 5222 -j DNAT --to-destination &lt;IP Destino&gt;:5222
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A POSTROUTING -t nat -p tcp -d &lt;IP Destino&gt; --dport 5222 -j MASQUERADE
</span></span></span></code></pre></div><p>Y, para rangos de puertos:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="go">iptables -A PREROUTING -t nat -p udp -i eth0 -m multiport --dports 49152:65535 -j DNAT --to-destination &lt;IP Destino&gt;:49152-65535
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A POSTROUTING -t nat -p udp -d &lt;IP Destini&gt; -m multiport --dports 49152:65535 -j MASQUERADE
</span></span></span></code></pre></div><p>Vas a tener que cambiar los puertos del ejemplo por los tuyos propios, así como poner tu propia dirección IP de destino en lugar del texto <code>&lt;IP Destino&gt;</code>.</p>
<p>Para el caso de XMPP hay que añadir todas las reglas de renvío correspondientes a sus puertos, cambiando el texto <code>&lt;IP Destino&gt;</code> por la dirección IP del servidor de casa, <em>patata</em> en el <a href="https://gvisoc.com/posts/vps-servidor-casero/">artículo previo de la serie</a>, y teniendo en cuenta que la dirección que tenemos que configurar es la de <em>patata</em> en Tailscale.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="go">iptables -A PREROUTING -t nat -p tcp -i eth0 --dport 5222 -j DNAT --to-destination &lt;IP Destino&gt;:5222
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A POSTROUTING -t nat -p tcp -d &lt;IP Destino&gt; --dport 5222 -j MASQUERADE
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A PREROUTING -t nat -p tcp -i eth0 --dport 5269 -j DNAT --to-destination &lt;IP Destino&gt;:5269
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A POSTROUTING -t nat -p tcp -d &lt;IP Destino&gt; --dport 5269 -j MASQUERADE
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A PREROUTING -t nat -p tcp -i eth0 --dport 5000 -j DNAT --to-destination &lt;IP Destino&gt;:5000
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A POSTROUTING -t nat -p tcp -d &lt;IP Destino&gt; --dport 5000 -j MASQUERADE
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A PREROUTING -t nat -p tcp -i eth0 --dport 3478 -j DNAT --to-destination &lt;IP Destino&gt;:3478
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A POSTROUTING -t nat -p tcp -d &lt;IP Destino&gt; --dport 3478 -j MASQUERADE
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A PREROUTING -t nat -p tcp -i eth0 --dport 3479 -j DNAT --to-destination &lt;IP Destino&gt;:3479
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A POSTROUTING -t nat -p tcp -d &lt;IP Destino&gt; --dport 3479 -j MASQUERADE
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A PREROUTING -t nat -p udp -i eth0 --dport 3478 -j DNAT --to-destination &lt;IP Destino&gt;:3478
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A POSTROUTING -t nat -p udp -d &lt;IP Destino&gt; --dport 3478 -j MASQUERADE
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A PREROUTING -t nat -p udp -i eth0 --dport 3479 -j DNAT --to-destination &lt;IP Destino&gt;:3479
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A POSTROUTING -t nat -p udp -d &lt;IP Destino&gt; --dport 3479 -j MASQUERADE
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A PREROUTING -t nat -p tcp -i eth0 --dport 5349 -j DNAT --to-destination &lt;IP Destino&gt;:5349
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A POSTROUTING -t nat -p tcp -d &lt;IP Destino&gt; --dport 5349 -j MASQUERADE
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A PREROUTING -t nat -p tcp -i eth0 --dport 5350 -j DNAT --to-destination &lt;IP Destino&gt;:5350
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A POSTROUTING -t nat -p tcp -d &lt;IP Destino&gt; --dport 5350 -j MASQUERADE
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A PREROUTING -t nat -p udp -i eth0 --dport 5349 -j DNAT --to-destination &lt;IP Destino&gt;:5349
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A POSTROUTING -t nat -p udp -d &lt;IP Destino&gt; --dport 5349 -j MASQUERADE
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A PREROUTING -t nat -p udp -i eth0 --dport 5350 -j DNAT --to-destination &lt;IP Destino&gt;:5350
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A POSTROUTING -t nat -p udp -d &lt;IP Destino&gt; --dport 5350 -j MASQUERADE
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A PREROUTING -t nat -p udp -i eth0 -m multiport --dports 49152:65535 -j DNAT --to-destination &lt;IP Destino&gt;:49152-65535
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A POSTROUTING -t nat -p udp -d &lt;IP Destino&gt; -m multiport --dports 49152:65535 -j MASQUERADE
</span></span></span></code></pre></div><p>Prueba que todo funciona y graba las reglas de iptables con <code>sudo iptables-save &gt; /etc/iptables/rules.v4</code>, de tal forma que se apliquen cada vez que reinicies el VPS.</p>
<p>En <a href="https://ryanwelch.me/blog/port-forward-with-tailscale/">su artículo</a>, Ryan comentaba otras configuraciones adicionales para temas tales como control de congestión. Consulta ese artículo para saber más, y aplica todo aquello que te sea de utilidad.</p>
<h2 id="nginx-con-tráfico-tcp-y-udp-genérico">NGINX con tráfico TCP y UDP genérico</h2>
<blockquote>
<p>Esta parte del artículo no está del todo probada, porque no me acabó de funcionar para la instalación del servidor XMPP <a href="http://snikket.org">Snikket</a>. Sin embargo, de acuerdo a su documentación, ésta es la forma en la que se configura NGINX para tráfico que no es Web.</p></blockquote>
<p>Instala <code>libnginx-mod-stream</code> para habilitar el soporte a proxies inversos TCP y UDP.</p>
<p>Modifica <code>/etc/nginx/nginx.conf</code> para añadir un bloque <em>stream</em>:</p>
<pre tabindex="0"><code class="language-conf" data-lang="conf">stream {
        include /etc/nginx/streams-enabled/*;
}
</code></pre><p>Tras ello, crea los directorios correspondientes siguiendo el mismo esquema existente para las webs: <code>sudo mkdir -p /etc/nginx/streams-available /etc/nginx/streams-enabled</code></p>
<p>Crea un fichero <code>/etc/nginx/streams-available/tcpudpservice.example.com.conf</code> con reglas como las siguientes:</p>
<pre tabindex="0"><code class="language-conf" data-lang="conf"># Puerto TCP (tráfico no HTTP):
server {
        listen 5222;
        proxy_pass &lt;IP Destino&gt;:5222;
}

# ...

# Puerto UDP:
server {
        listen 3478 udp;
        proxy_pass &lt;IP Destino&gt;:3478;
}

# ...

# Se aceptan rangos, también. En este caso, fíjate
# en el uso de $server_port en la sentencia proxy_pass.

server {
        listen 49152-65535 udp;
        proxy_pass &lt;IP Destino&gt;:$server_port;
}
</code></pre><p>En caso de definir rangos de puertos muy grande, como los que propone Snikket para conversaciones de audio y vídeo por XMPP en instalaciones con muchos usuarios, NGINX devolverá un error al tener demasiados ficheros abiertos. La configuración de Snikket espera poder tener hasta 16384 conexiones UDP abiertas al mismo tiempo.</p>
<p>Si queremos resolver esto en lugar de reducir el rango de puertos UDP, hay que aumentar el parámetro <code>LimitNOFILE</code> de NGINX. Para eso, ejecuta <code>sudo systemctl edit nginx.service</code>; esto abrirá un fichero de extensión de la definición del servicio</p>
<p>Escribe lo siguiente y graba los cambios:</p>
<pre tabindex="0"><code class="language-unit" data-lang="unit">[Service]
LimitNOFILE=65535
</code></pre><p>Tras ello, en el preámbulo de <code>/etc/nginx/nginx.conf</code> (fuera de cualquier bloque <code>http</code>, <code>events</code> o <code>stream</code>), añade la línea  <code>worker_rlimit_nofile</code> con un valor menor a 65535 (30000 funciona para Snikket), e incrementa el valor de <code>worker_connections</code> dentro de <code>events</code>:</p>
<pre tabindex="0"><code class="language-conf" data-lang="conf">...
include /etc/nginx/modules-enabled/*.conf;

worker_rlimit_nofile 30000;

events {
        # worker_connections 768;
        # 3 veces 8192 será suficiente
        worker_connections 24576;
        # multi_accept on;
}

...
</code></pre><p>Como dije antes, esta configuración no me sirvió para XMPP, pero puede ser útil para otros casos de uso.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Exponer servicios alojados en casa usando un VPS y Tailscale</title>
      <link>https://gvisoc.com/posts/vps-servidor-casero/</link>
      <pubDate>Fri, 05 Sep 2025 05:40:00 +1000</pubDate><author>gabriel@gvisoc.com (Gabriel Viso Carrera)</author>
      <guid>https://gvisoc.com/posts/vps-servidor-casero/</guid>
      <description>Una forma fácil de exponer servicios en tu red casera sin necesidad de abrir puertos ni contratar direcciones IP estáticas con tu operador de acceso a internet.</description>
      <content:encoded><![CDATA[<h2 id="problema">Problema</h2>
<p>Este texto detalla los pasos a seguir para conectar un proxy inverso (<a href="https://nginx.org">NGINX</a>) instalado en un servidor A, y dar acceso a servicios web instalados en otra máquina diferente, un servidor B. Si la máquinas están en ubicaciones o redes diferentes, la conectividad la podremos resolver mediante varias alternativas, como una VPN.</p>
<p>En mi caso concreto, esto sirve para instalar NGINX en un VPS, y usarlo para exponer mis servicios alojados en un servidor que tengo en casa, &ldquo;barcas&rdquo;. Para conectar las dos máquinas y que NGINX pueda redirigir el tráfico a esos servicios, he usado <a href="https://tailscale.com">Tailscale</a>.</p>
<figure class="ma0 w-75">
    <img loading="lazy" src="/images/20250905-solution.png"
         alt="Un diagrama que representa lo que queremos conseguir y que acabamos de explicar"/> <figcaption>
            <p>El objetivo</p>
        </figcaption>
</figure>

<p>Este artículo continúa la serie que empezó con &ldquo;<a href="/posts/cambios-xmpp/">Cambios en mi red de la mano de XMPP</a>&rdquo;. Si te preguntas por qué querríamos hacer esto, o qué valor tiene esta configuración, visita ese enlace.</p>
<h2 id="solución">Solución</h2>
<p>Para llevar a cabo esta solución necesitarás:</p>
<ol>
<li>Un servidor VPS contratado con tu servicio de alojamiento de confianza, al que poder acceder por SSH.</li>
<li>Un servidor en tu casa, con al menos un servicio aceptando conexiones HTTP. Por ejemplo, en el puerto 5000 (<code>0.0.0.0:5000</code>). A este servidor lo llamaremos <em>patata</em> en el resto del artículo, por claridad.</li>
<li>Una cuenta gratuita en <a href="https://tailscale.com">Tailscale</a>. En general, valdría con cualquier otro proveedor de VPN, pero es preferible que esté basada en <a href="https://es.wikipedia.org/wiki/WireGuard">WireGuard</a>, porque ofrece un mejor rendimiento que <a href="https://es.wikipedia.org/wiki/OpenVPN">OpenVPN</a>.</li>
<li>Opcionalmente, o para que esto tenga sentido, un dominio con sus registros de DNS apuntando al VPS que mencionamos en el punto 1.</li>
</ol>
<p>En las instrucciones de este artículo supondré que ambos servidores (el VPS y <em>patata</em>) tienen instalada una Ubuntu LTS Server (por ejemplo, la 24.04).</p>
<p>Si no hay problemas, en 20 minutos deberías tenerlo todo listo.</p>
<h3 id="servidor-en-casa">Servidor en casa</h3>
<p>Nos conectaremos a <em>patata</em>, donde está nuestro servicio web esperando peticiones en el puerto 5000.</p>
<p><a href="https://tailscale.com/download">Instalamos Tailscale</a> y autenticamos a <em>patata</em> en nuestra cuenta. Esto asociará una dirección IP dentro de la VPN a <em>patata</em>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">#</span> Las órdenes para instalar tailscale no las reproduzco 
</span></span><span class="line"><span class="cl"><span class="gp">#</span> porque podrían cambiar en cualquier momento.
</span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="go">sudo tailscale up
</span></span></span><span class="line"><span class="cl"><span class="go"></span><span class="gp">#</span> Seguimos las instrucciones por pantalla
</span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="gp">#</span> Habilitamos Tailscale como servicio de sistema permanentemente
</span></span><span class="line"><span class="cl"><span class="go">sudo systemctl enable tailscaled --now
</span></span></span><span class="line"><span class="cl"><span class="go"></span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="gp">#</span> Obtenemos la IP de esta máquina en la tailnet y la anotamos.
</span></span><span class="line"><span class="cl"><span class="go">tailscale ip -4
</span></span></span><span class="line"><span class="cl"><span class="go">100.85.67.22
</span></span></span></code></pre></div><p>En el panel de control de Tailscale podremos ver su nombre local, además de la IP devuelta por la última orden.</p>
<p>También podremos configurar la máquina para que no haya que re-introducirla en la red, desactivando la expiración de la clave usada para autenticar la máquina en Tailscale.</p>
<figure class="ma0 w-10">
    <img loading="lazy" src="/images/20250905-keyexpiry.png"
         alt="Un menú contextual del panel de control de Tailscale para desactivar la expiración de las claves"/> <figcaption>
            <p>Opción relevante para desactivar la expiración de las claves</p>
        </figcaption>
</figure>

<p>Opcionalmente, podemos activar el &ldquo;MagicDNS&rdquo; para podernos referir a ambas máquinas mediante un nombre, en lugar de mediante una dirección IP, sin tener que configurar nada localmente en las máquinas.</p>
<figure class="ma0 w-75">
    <img loading="lazy" src="/images/20250905-magicdns.png"
         alt="Opciones de &#34;MagicDNS&#34; en el panel de control de Tailscale, descrita como &#34;Automatically register domain names for devices in your tailnet. This lets you to use a machine’s name instead of its IP address.&#34;. En la imagen ya se encuentra activado."/> <figcaption>
            <p>Opciones de &ldquo;MagicDNS&rdquo; en el panel de control de Tailscale. En el ejemplo ya se encuentra activado.</p>
        </figcaption>
</figure>

<p>Una vez la máquina está en Tailscale, debemos <strong>abrir el puerto 5000 en el cortafuegos de <em>patata</em></strong>, si es que no lo habíamos hecho ya, para que pueda recibir peticiones desde el VPS por dicho puerto.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="go">sudo iptables -A INPUT -p tcp -m tcp --dport 5000 -j ACCEPT
</span></span></span></code></pre></div><p>A este respecto:</p>
<ol>
<li>Si no tienes <code>iptables-persistent</code> instalado, es el momento de instalarlo para poder grabar las reglas de cortafuegos de tal forma que se apliquen con cada reinicio de la máquina.</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="go">sudo apt install iptables-persistent 
</span></span></span><span class="line"><span class="cl"><span class="go">sudo iptables-save &gt; /etc/iptables/rules.v4
</span></span></span></code></pre></div><ol start="2">
<li>Personalmente, abro los puertos en todos los interfaces y no sólo en el de Tailscale.
<ul>
<li>Al no tener que abrir ese puerto en el router, no estoy haciendo nada que me deje demasiado expuesto.</li>
<li>Al hacerlo así, no necesito acceder por Tailscale a <em>patata</em> cuando, estando en casa, necesito hacer alguna prueba.</li>
<li>Cada caso es distinto: decide qué hacer tú mismo con tu cortafuegos dependiendo de lo crítico que sea el servicio que estás configurando, y lo sensibles que sean los datos que gestiona.</li>
</ul>
</li>
</ol>
<h3 id="vps">VPS</h3>
<p>En segundo lugar, nos conectamos al VPS por medio de SSH e instalamos un proxy inverso. Aunque no vayamos a tener más de un servicio escuchando en los puertos 80 y 443, configurar y entender un proxy inverso es más fácil que configurar y entender reglas de reenvío de tráfico y DNAT en un cortafuegos como iptables.</p>
<p>En mi caso, suelo usar NGINX.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="go">sudo apt update &amp;&amp; sudo apt install nginx
</span></span></span></code></pre></div><p><a href="https://tailscale.com/download">Instalamos Tailscale</a> y repetimos los mismos pasos al respecto que en el caso de <em>patata</em>. Anotamos la dirección IP de el VPS en &ldquo;la tailnet&rdquo;, por ejemplo <code>100.85.77.36</code>.</p>
<p>Obtenemos un certificado de Let&rsquo;s Encrypt para nuestro dominio, con CertBot. Para esto:</p>
<ul>
<li>Debemos configur previamente los registros DNS de nuestro dominio, <code>ejemplo.com</code>, para que apunten a la IP pública (no a su IP en Tailscale) del VPS en el que estamos trabajando.</li>
<li>Es posible tengamos que parar NGINX para esto (<code>sudo systemctl stop nginx</code>), dependiendo de nuestros proveedores de dominio y los plugins de CertBot disponibles para interactuar con él.</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="go">sudo certbot certonly --standalone -d ejemplo.com
</span></span></span><span class="line"><span class="cl"><span class="go">...
</span></span></span><span class="line"><span class="cl"><span class="go">...
</span></span></span><span class="line"><span class="cl"><span class="go">Successfully received certificate.
</span></span></span><span class="line"><span class="cl"><span class="go">Certificate saved at: /etc/letsencrypt/live/ejemplo.com/fullchain.pem;
</span></span></span><span class="line"><span class="cl"><span class="go">Key is saved at:      /etc/letsencrypt/live/ejemplo.com/privkey.pem;
</span></span></span><span class="line"><span class="cl"><span class="go">...
</span></span></span><span class="line"><span class="cl"><span class="go">...
</span></span></span></code></pre></div><p>Configuramos el servicio en el proxy inverso, poniendo en la sentencia <code>proxy_pass</code> la IP de nuestro servidor <em>patata</em> en Tailscale. Para ello, en <code>/etc/nginx/sites-available/</code> creamos un fichero de configuración, por ejemplo <code>ejemplo.com.conf</code> para una web <code>https://ejemplo.com</code>.</p>
<pre tabindex="0"><code class="language-conf" data-lang="conf">upstream ejemploweb {
        # Sustituye [IP o nombre de máquina] por el valor de tu &#39;patata&#39;
        # **en Tailscale**.
        # 100.85.67.22 en el texto del artículo.
        server [IP o nombre de máquina]:5000;
        keepalive 64;
}

server {
        server_name ejemplo.com;
        listen 80;

        location / {
                return 301 https://$host$request_uri;
        }
}

server {
        server_name ejemplo.com;
        listen 443 ssl http2;

        # Other SSL stuff goes here
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers HIGH:!MEDIUM:!LOW:!aNULL:!NULL:!SHA;
        ssl_prefer_server_ciphers on;
        ssl_session_cache shared:SSL:10m;
        ssl_session_tickets off;

        ssl_certificate     /etc/letsencrypt/live/ejemplo.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/ejemplo.com/privkey.pem;

        location / {
                # The final `/` is important.
                proxy_pass http://ejemploweb/;
                add_header X-Frame-Options SAMEORIGIN;
                add_header X-XSS-Protection &#34;1; mode=block&#34;;
                proxy_redirect off;
                proxy_buffering off;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
                proxy_set_header X-Forwarded-Port $server_port;
                proxy_read_timeout 90;
        }
}
</code></pre><p>Creamos un enlace simbólico a este fichero en <code>/etc/nginx/sites-enabled</code>, probamos la configuración de NGINX y, si todo va bien, la recargamos.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="go">cd /etc/nginx/sites-enabled/
</span></span></span><span class="line"><span class="cl"><span class="go">sudo ln -s ../sites-available/ejemplo.com.conf
</span></span></span><span class="line"><span class="cl"><span class="go">sudo nginx -t
</span></span></span><span class="line"><span class="cl"><span class="go">nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
</span></span></span><span class="line"><span class="cl"><span class="go">nginx: configuration file /etc/nginx/nginx.conf test is successful
</span></span></span><span class="line"><span class="cl"><span class="go"></span><span class="gp">#</span> Si hemos detenido el servicio de NGINX para obtener el certificado, 
</span></span><span class="line"><span class="cl"><span class="gp">#</span> lo arrancamos<span class="p">;</span> si no:
</span></span><span class="line"><span class="cl"><span class="go">sudo systemctl reload nginx
</span></span></span></code></pre></div><p>En este punto, deberíamos poder interactuar con nuestro servicio en <em>patata</em> desde <code>https://ejemplo.com</code>.</p>
<h2 id="posibles-errores-que-te-puedes-encontrar">Posibles errores que te puedes encontrar</h2>
<ul>
<li><strong>El navegador no muestra nada, el servicio no contesta</strong>. Si hay actividad en los logs de NGINX en el VPS (<code>sudo tail -f /var/log/nginx/access.log</code>), y la petición se abandona tras un tiempo (<em>timeout</em>), hay varias posibles causas que me he encontrado con mi limitada experiencia. En <em>patata</em>:
<ul>
<li>El servicio al que quieremos acceder a través de NGINX no está funcionando, y tenemos que arrancarlo,</li>
<li>o el servicio está expuesto en el puerto 5000 de un interfaz de red específico, en lugar de <code>0.0.0.0</code> (que significa <code>todos los interfaces de red</code> de <em>patata</em>), y que no es el de Tailscale; por ejemplo, en <code>127.0.0.1</code>,</li>
<li>o hemos hecho algo mal con el cortafuegos y la petición se está rechazando de esta forma.</li>
</ul>
</li>
<li><strong>La configuración de NGINX es correcta</strong> (<code>sudo nginx -t</code> dice que 👍)<strong>, pero NGINX no arranca</strong> o se muere al recargar la configuración. La causa más frecuente es que NGINX no se puede comunicar con <em>patata</em>, al que estaríamos apuntando con la sentencia <code>proxy_pass</code>, porque no lo encuentra.
<ul>
<li>O bien hemos cometido un error al configurar el <code>proxy_pass</code>,</li>
<li>o bien NGINX ha intentado establecer contacto con <em>patata</em> antes de que Tailscale estableciese el túnel, por ejemplo si esto ocurre tras reiniciar el VPS,</li>
<li>o bien el servicio de Tailscale se ha parado en alguna de las dos máquinas.</li>
<li>Esta causa se confirma con el mensaje <code>host not found in upstream</code> en la salida de <code>sudo systemctl status nginx</code>:</li>
</ul>
</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="go">nginx.service - A high performance web server and a reverse proxy server
</span></span></span><span class="line"><span class="cl"><span class="go">     Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled)
</span></span></span><span class="line"><span class="cl"><span class="go">    Drop-In: /etc/systemd/system/nginx.service.d
</span></span></span><span class="line"><span class="cl"><span class="go">             └─override.conf
</span></span></span><span class="line"><span class="cl"><span class="go">     Active: active (running) since Fri 2025-08-29 11:05:51 UTC; 3 days ago
</span></span></span><span class="line"><span class="cl"><span class="go">       Docs: man:nginx(8)
</span></span></span><span class="line"><span class="cl"><span class="go">    Process: 1154 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
</span></span></span><span class="line"><span class="cl"><span class="go">    Process: 1156 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
</span></span></span><span class="line"><span class="cl"><span class="go">    Process: 58676 ExecReload=/usr/sbin/nginx -g daemon on; master_process on; -s reload (code=exited, status=1/FAILURE)
</span></span></span><span class="line"><span class="cl"><span class="go">   Main PID: 1157 (nginx)
</span></span></span><span class="line"><span class="cl"><span class="go">      Tasks: 3 (limit: 1110)
</span></span></span><span class="line"><span class="cl"><span class="go">     Memory: 46.9M (peak: 200.2M)
</span></span></span><span class="line"><span class="cl"><span class="go">        CPU: 29min 57.349s
</span></span></span><span class="line"><span class="cl"><span class="go">     CGroup: /system.slice/nginx.service
</span></span></span><span class="line"><span class="cl"><span class="go">             ├─1157 &#34;nginx: master process /usr/sbin/nginx -g daemon on; master_process on;&#34;
</span></span></span><span class="line"><span class="cl"><span class="go">             ├─1158 &#34;nginx: worker process&#34;
</span></span></span><span class="line"><span class="cl"><span class="go">             └─1159 &#34;nginx: cache manager process&#34;
</span></span></span><span class="line"><span class="cl"><span class="go"></span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="go">...
</span></span></span><span class="line"><span class="cl"><span class="go">Sep 02 05:32:49 vps-hostingprovider nginx[58676]: 2025/09/02 05:32:49 [emerg] 58676#58676: host not found in upstream &#34;100.85.77.22&#34; in /etc/nginx/sites-enabled/ejemlo.com.conf&gt;
</span></span></span><span class="line"><span class="cl"><span class="go">Sep 02 05:32:49 vps-hostingprovider systemd[1]: nginx.service: Control process exited, code=exited, status=1/FAILURE
</span></span></span><span class="line"><span class="cl"><span class="go">Sep 02 05:32:49 vps-hostingprovider systemd[1]: Reload failed for nginx.service - A high performance web server and a reverse proxy server.
</span></span></span><span class="line"><span class="cl"><span class="go">lines 1-28/28 (END)
</span></span></span></code></pre></div><ul>
<li><strong>Recibimos un código de estado HTTP 502</strong>. Esto quiere decir que, si bien NGINX está funcionando correctamente, el servicio al que nos conectamos (expuesto en el puerto 5000 de <em>patata</em>), está devolviendo errores.
<ul>
<li>Si haces una prueba local en <em>patata</em> y el servicio funciona, casi seguro que se trata de un tema de <strong>proxies de confianza</strong>. Describir el problema en detalle excede los objetivos de este artículo, pero en resumen se trata de que tienes que especificar en la configuración de ese servicio, en <em>patata</em>, que NGINX está instalado en una máquina diferente, el VPS, y configurar la dirección del VPS en Tailscale como proxy de confianza. <strong>Consulta los logs del servicio</strong> para confirmar esta hipótesis.</li>
<li>Esto no es un problema, es una característica de seguridad bastante interesante.</li>
<li>Por ejemplo, esto me pasó con mis instancias de Mastodon y de Nextcloud. En ambos casos, la documentación contenía las soluciones: <a href="https://docs.joinmastodon.org/admin/config/#trusted_proxy_ip">Mastodon &ndash; trusted proxies</a>, <a href="https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/reverse_proxy_configuration.html#defining-trusted-proxies">Nextcloud &ndash; trusted proxies</a></li>
<li>Depende de cómo esté implementado el servicio que estamos intentando arreglar, puede ser que acepte un proxy de confianza tanto como nombre de máquina o como direccion IP, o sólo como IP. Ármate de paciencia y si no te funciona con el nombre de la máquina no te desesperes.</li>
</ul>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Cambios en mi red de la mano de XMPP</title>
      <link>https://gvisoc.com/posts/cambios-xmpp/</link>
      <pubDate>Sun, 31 Aug 2025 00:49:35 +0000</pubDate><author>gabriel@gvisoc.com (Gabriel Viso Carrera)</author>
      <guid>https://gvisoc.com/posts/cambios-xmpp/</guid>
      <description>Hace unos días me recomendaron Snikket como servidor XMPP sencillo de instalar y mantener, y a raíz de ello he cambiado la forma de exponer mis servicios a internet.</description>
      <content:encoded><![CDATA[<p><strong>Hace unos días me recomendaron <a href="https://snikket.org">Snikket</a> como una opción sencilla para montar un servidor XMPP<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>, y me lancé por el terraplén</strong> de cabeza. A mitad del descenso, me encontré un montón de problemas interesantísimos que resolver, que iré desgranando paso a paso en los próximos artículos.</p>
<p>En éste os voy a contar cuáles son esos problemas, por qué surgen, por qué los quiero resolver y cómo los he resuelto, pero desde un punto de vista de diseño por el momento. No vas a encontrar instrucciones de terminal para resolver todo esto, sino el contexto necesario para saber qué estoy intentando resolver; las instrucciones detalladas vendrán en los siguientes artículos.</p>
<p>Hasta ahora, todos mis servicios alojados bajo mi impresora eran servicios Web, es decir, basados en comunicaciones a través de los puertos 80 y 443 (HTTP y HTTPs). Estos puertos eran suficientes para comunicar todos mis servicios con mis dispositivos o con mi navegador, o con otros servidores en el caso de mi instancia de Mastodon. Para poder atender a tantos servicios tras los mismos puertos, uso <a href="https://nginx.org/">NGINX</a> como <a href="https://es.wikipedia.org/wiki/Proxy_inverso">proxy inverso</a>:</p>
<figure class="ma0 w-75">
    <img loading="lazy" src="/images/20250901-before.png"
         alt="Un diagrama donde se ve la conectividad entre cloudflare y un servidor llamado barcas, que contiene NGINX y una serie de servicios ejecutados en Docker"/> <figcaption>
            <p>&ldquo;Barcas&rdquo; es el nombre de mi servidor.</p>
        </figcaption>
</figure>

<p>A diferencia de mis otros servicios, <strong>XMPP usa más puertos TCP y UDP que el 80 y el 443</strong>: 5000, 5222, 5269,&hellip; En el caso de Snikket como, supongo, toda otra serie de alternativas, los puertos 80 y 443 también se usan para proporcionar un panel de control Web y poder administrar usuarios y salas de chat.</p>
<p>Sobre el papel, la configuración para instalar Snikket sería la siguiente:</p>
<ul>
<li>Configurar los puertos 80 y 443 en NGINX para poder usar el panel de control de usuarios y conversaciones de Snikket.</li>
<li>Configurar acceso directo a todo el resto de los puertos de Snikket: 5222, 5269,&hellip; ésta es la <a href="https://snikket.org/service/help/advanced/firewall/">lista completa</a> (en inglés). Como no son puertos compartidos entre varios servicios, no hace falta configurarlos en un proxy inverso.</li>
<li>Configurar las DNS de mi servicio de XMPP en mi proveedor de dominios, que es <a href="https://www.cloudflare.com/">Cloudflare</a>.</li>
</ul>
<p>Esta configuración, que a priori tendría buena pinta, se resume en esta figura:</p>
<figure class="ma0 w-75">
    <img loading="lazy" src="/images/20250902-snikketsimple.png"
         alt="Un diagrama donde se ve la conectividad entre cloudflare y un servidor llamado barcas, que contiene NGINX y una serie de servicios ejecutados en Docker."/> <figcaption>
            <p>Esta configuración, aunque parece que tiene sentido, no funciona. Cloudflare sólo ofrece funciones de proxy para los puertos 80 y 443 en su cuenta gratuita, por lo que el tráfico del resto de puertos de Snikket es descartado.</p>
        </figcaption>
</figure>

<p><strong>Sin embargo, esto tiene un problema que no es evidente, y la configuración de arriba no funciona</strong> sin más. Las características de seguridad de Cloudflare que están disponibles para las cuentas gratuitas en forma de &ldquo;el proxy de Cloudflare&rdquo; sólo soportan los puertos 80 y 443. Con el proxy de Cloudflare activado, todo el tráfico que no es web (80 y 443) se descartaba, por lo que ninguno de los puertos que entran en juego en las conversaciones de XMPP llegaba a mi servidor.</p>
<p>Desactivando el proxy de Cloudflare para la configuración de los subdominios de Snikket todo funciona, pero queda así:</p>
<figure class="ma0 w-75">
    <img loading="lazy" src="/images/20250902-snikketactual.png"
         alt="Un diagrama donde se ve la conectividad entre cloudflare y un servidor llamado barcas, que contiene NGINX y una serie de servicios ejecutados en Docker."/> <figcaption>
            <p>Esta configuración, aunque funciona, supone un riesgo. Al desactivar la función de proxy, la dirección IP de mi casa (por donde se entra a &ldquo;barcas&rdquo;) queda expuesta a todo internet. Un ataque de denegación de servicio distribuido que afecte a mi proveedor de servicios de acceso a internet puede dejar sin internet a toda la casa, cosa que no nos gusta.</p>
        </figcaption>
</figure>

<p><strong>Desactivar el proxy de Cloudflare no es ideal, porque expone la IP del servidor</strong> al resto de internet. Mi servidor está en casa, por lo que un ataque de denegación de servicio que, por puro azar, incluyese la dirección IP que mi operador de acceso a internet me asigna, tiraría por tierra toda mi red doméstica. Eso nos impediría trabajar o estudiar desde casa y, probablemente, me traería problemas con mi proveedor de servicio.</p>
<p>Una alternativa a esto es contratar un servidor barato en cualquier proveedor de alojamiento en la nube (un <a href="https://es.wikipedia.org/wiki/Servidor_virtual_privado">VPS</a>) y mover toda mi configuración de NGINX a él, conectando este servidor en la nube con el servidor de mi casa mediante una VPN (la cuenta gratuita de <a href="https://tailscale.com">Tailscale</a> funciona muy bien y es más que suficiente).</p>
<ul>
<li>El proveedor de alojamiento en la nube protegería mi servidor porque la IP expuesta sería la suya, no la de mi casa, y este tipo de empresas protegen su infraestructura ante ataques, por lo menos a un nivel de red. Tras los puertos XMPP no hay contenido, sino un servicio de mensajería que además cifra el tráfico, así que no hay problema por perder la capa de seguridad y anti-<em>bots</em> que proporciona Cloudflare para estos puertos.</li>
<li>Conectar el VPS a mi servidor a través de una VPN es perfecto para este caso, porque oculta la dirección IP de mi red doméstica ahorrándome configurarla en el panel de control de DNS, y también el tener que abrir puertos en mi router. Además, para todo el tráfico que iría entre la VPS y mi servidor, se aplicaría una capa de cifrado que lo protegería en caso de que, de por sí, no estuviese ya cifrado.</li>
</ul>
<figure class="ma0 w-75">
    <img loading="lazy" src="/images/20250902-snikketvps.png"
         alt="Un diagrama donde se ve la conectividad entre cloudflare, un VPS que contiene NGINX, y entre éste y un servidor llamado barcas usando Tailscale. Barcas que contiene una serie de servicios ejecutados en Docker."/> <figcaption>
            <p>Esta configuración funciona y resuelve el problema de la configuración anterior, pero requiere encontrar una solución para redirigir el tráfico de todos los puertos diferentes a 80 y 443 a &ldquo;barcas&rdquo;.</p>
        </figcaption>
</figure>

<p>¿Por qué mover sólo la configuración de NGINX y no todos los servicios? Por una cuestión de recursos y precio: para ejecutar NGINX y una serie de reglas de cortafuegos, los requisitos que necesitamos son mínimos. Con un servidor que tenga 1 GB de RAM y con una CPU es más que suficiente, y eso cuesta entre 3 y 6€ al mes. Si muevo todo<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> necesitaría entre 8 y 16 GB de RAM, mucho más almacenamiento potencia de CPU, una solución de copias de seguridad,&hellip; Con todo ello me estaría moviendo en un rango de unos 40€, o más, al mes. Teniendo un servidor en casa donde ejecutar los servicios, moverlo todo a la VPS acarrea un coste innecesario en mi caso.</p>
<p>Así pues, parece que contratar una VPS sencilla para tener NGINX configurado allí, y conectarla al servidor de casa mediante Tailscale, resolvería mis problemas a un coste muy bajo (3 a 6€ al mes).</p>
<p>La única complicación adicional de este método es que tenemos que tener algún mecanismo para redirigir el tráfico de los puertos de XMPP (varios puertos como el 5222, 5269,&hellip; en TCP y UDP) desde el VPS hacia nuestro servidor (en casa o donde fuere). Al final lo he resuelto configurando el VPS para que haga <em>IP forwarding</em> y luego he configurado una serie de reglas DNAT<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> en su cortafuegos.</p>
<p>En próximos artículos veremos más en detalle la configuración de todos los elementos.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><em><a href="https://es.wikipedia.org/wiki/Extensible_Messaging_and_Presence_Protocol">Extensible Messaging and Presence Protocol</a></em> &ndash; Protocolo Extensible de Mensajería y Presencia. XMPP es un protocolo que comunica comunidades de usuarios a través de sus servidores, creando conversaciones entre pares de usuarios, o salas para conversaciones en grupo. Google Talks fue el ejemplo más popular de aplicación de XMPP. Una ventaja de XMPP es que es un protocolo federado, es decir: aunque yo me conecte a un servidor donde sólo esté yo, puedo establecer contacto con cualquier otro usuario de cualquier otro servicio que utilice XMPP en el planeta. Los nombres de usuario en XMPP tienen el mismo formato que un correo electrónico, y los servidores se comunican entre ellos cuando sus usuarios lo necesitan.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Aunque en este artículo hablo casi exclusivamente de Snikket, en mi servidor de casa tengo muchos otros servicios: Mastodon, Castopod, algún que otro blog, Linkwarden, FreshRSS, Actual Budget, Forgejo y Nextcloud.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><em>Destination <a href="https://es.wikipedia.org/wiki/Traducci%C3%B3n_de_direcciones_de_red">NAT</a></em>. Traducción de direcciones de destino de red.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>🚨 gvisoc.com en Hugo 🚨</title>
      <link>https://gvisoc.com/posts/gvisoc-com-en-hugo/</link>
      <pubDate>Sun, 03 Aug 2025 14:17:00 +1000</pubDate><author>gabriel@gvisoc.com (Gabriel Viso Carrera)</author>
      <guid>https://gvisoc.com/posts/gvisoc-com-en-hugo/</guid>
      <description>Salvo un sistema de comentarios que me falta por poner, todo terminado y muy satisfecho.</description>
      <content:encoded><![CDATA[<p>He cambiado el motor que da vida a este blog a <a href="https://gohugo.io">Hugo</a>. El anterior era <a href="https://ghost.org">Ghost</a>.</p>
<p>Entre otras cosas, lo hice porque la filosofía de Ghost de obligaros a daros de alta en un perfil determinado de suscriptor para poder comentar me parecía demasiado agresiva. Es cierto, la membresía para ello era gratis, pero no dejaba de ser una barrera artificial y confusa. Por otro lado, Ghost parece más orientado a boletines de noticias (newsletter, en inglés, para los despistados) que a blogs, por varias cosas pero también por el rollo de las membresías.</p>
<p>Por otro lado, Hugo tiene ciertas ventajas para mí, como por ejemplo que me permite crear un blog en dos (o más) idiomas, si es que me decido a traducir todos los artículos. Así, por ejemplo, si pulsáis en la banderita australiana (🇦🇺) que está en la cabecera de la web, podréis acceder a todo el contenido en inglés. En aquellos artículos o páginas con una traducción al inglés, veréis una banderita australiana (o, si estáis en una página en inglés, veréis una banderita española si está traducida al español).</p>
<p>Vais a notar alguna diferencia, más allá de cambios de aspecto. Mientras Ghost es una aplicación web con un servidor y con una base de datos que genera páginas al vuelo, Hugo es un programa que yo ejecuto en mi PC para crear un sitio web estático que luego subo a un servidor web, mucho más sencillo que un servidor de aplicaciones. Eso tiene ventajas e inconvenientes. Sin entrar en demasiados detalles:</p>
<ul>
<li>Como ventaja inmediata, no hay ningún software del que preocuparse que se ejecute en otro sitio diferente a tu PC. Tu navegador se descarga mi web entera y te la representa.</li>
<li>Como inconvemiente inmediato, en Hugo no tendremos comentarios. Algo pensaré.</li>
</ul>
<p>Aunque a lo mejor hay algún bache por el camino, como por ejemplo que el feed RSS cambie y recibas artículos antiguos como nuevos, no hay tantos artículos como para que esto sea un gran problema; no deberías tener que preocuparte de nada. En cualquier caso, si te preocupa algo, deja un comentario mientras puedas o escríbeme un correo a <code>gabriel arroba</code> el dominio de este blog.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Linux en directo</title>
      <link>https://gvisoc.com/posts/linux-en-directo/</link>
      <pubDate>Sat, 30 Nov 2024 21:47:45 +0000</pubDate><author>gabriel@gvisoc.com (Gabriel Viso Carrera)</author>
      <guid>https://gvisoc.com/posts/linux-en-directo/</guid>
      <description>&lt;p&gt;Empezar con Linux no es un paso &lt;em&gt;trivial&lt;/em&gt; para todo el mundo. Pese a que las cosas son mucho más sencillas que en 1998, o, sin irnos tan allá, que en 2010, hay muchas preguntas que hacerse al respecto.&lt;/p&gt;
&lt;p&gt;El pasado viernes 29 de noviembre estuve instalando Linux en arranque dual con Windows 11, en través de &lt;a href=&#34;https://www.twitch.tv/gvis0c&#34;&gt;twitch&lt;/a&gt;. Y me ha gustado el resultado, tanto que es muy posible que haga muchos más vídeos de ahora en adelante, tanto en directo como &amp;ldquo;producidos&amp;rdquo;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Empezar con Linux no es un paso <em>trivial</em> para todo el mundo. Pese a que las cosas son mucho más sencillas que en 1998, o, sin irnos tan allá, que en 2010, hay muchas preguntas que hacerse al respecto.</p>
<p>El pasado viernes 29 de noviembre estuve instalando Linux en arranque dual con Windows 11, en través de <a href="https://www.twitch.tv/gvis0c">twitch</a>. Y me ha gustado el resultado, tanto que es muy posible que haga muchos más vídeos de ahora en adelante, tanto en directo como &ldquo;producidos&rdquo;.</p>
<p>El resultado está disponible en PeerTube, en mi cuenta de <a href="https://veedeo.org/a/gvisoc/">veedeo.org</a>, próximamente en YouTube, y lo puedes ver aquí mismo:</p>
<div style="position: relative; padding-bottom: 56.25%; margin-bottom: 1rem; height: 0; overflow: hidden;">
<iframe sandbox="allow-scripts allow-popups" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;"
    src="https://veedeo.org/videos/embed/h5hYmcWPtyDXPmydDu8kFZ?title=0&warningTitle=0&peertubeLink=0" allowfullscreen>
</iframe>
</div>

<p>También he creado, por fin, <a href="https://www.patreon.com/gabrielviso">una cuenta de Patreon</a> para que, quien quiera, pueda apoyar este blog, <a href="https://podcast.gvisoc.com/@sobrelamarcha">el podcast</a> y <a href="https://veedeo.org/a/gvisoc/">los vídeos que iré creando</a> a medida que el tiempo y el material disponible me lo permitan.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Migrar un Podcast de Spotify a Castopod</title>
      <link>https://gvisoc.com/posts/migrar-un-podcast-a-castopod/</link>
      <pubDate>Fri, 24 May 2024 12:00:05 +0000</pubDate><author>gabriel@gvisoc.com (Gabriel Viso Carrera)</author>
      <guid>https://gvisoc.com/posts/migrar-un-podcast-a-castopod/</guid>
      <description>Recuperando el control sobre las cosas que siempre debieron pertenecernos.</description>
      <content:encoded><![CDATA[<p>Empecé <a href="https://podcast.gvisoc.com/@sobrelamarcha">sobre la marcha</a> en 2017, al poco de llegar a Australia. Por aquél entonces, una red social de segmentos de 2 minutos llamada Anchor viraba su rumbo y abrazaba el podcasting como siguiente paso lógico en su estrategia. El funcionamiento era original e innovador: durante un espacio de 24 horas, los segmentos de 2 minutos y las contestaciones de tus contactos, de 1 minuto, se iban añadiendo a tu <em>emisora</em> de Anchor. Cuando terminaba el día, podías optar por publicar todo el contenido del día en un feed RSS: ya tenías tu podcast.</p>
<p>Era genial.</p>
<p>Entonces, llegó Spotify, primero como inversor, y luego comprando Anchor. Al poco tiempo, todas las características sociales y el funcionamiento que comentaba arriba, como la <em>emisora</em>, desaparecieron del producto y nos quedamos con una plataforma de podcasting solvente, y nada más.</p>
<p>No voy a dar cuenta en detalle del proceso de <a href="https://gvisoc.com/posts/enmierdificacion/">enmierdificación</a> de Spotify en este artículo, sino que me voy a limitar a decir que no estoy cómodo en esa plataforma desde hace tiempo, y he decidido <a href="https://gvisoc.com/posts/personal-y-rapido/">autoalojar en casa</a> mi propio podcast. El soporte a mi podcast me lo da ahora <a href="https://castopod.org/">Castopod</a>, una plataforma de podcasting libre y de código abierto que, además, ofrece integración con el fediverso mediante Activity Pub y soporte a múltiples características del <a href="https://podcasting2.org/">podcasting 2.0</a>, como la posibilidad de dejar comentarios desde tu propia aplicación de escucha de podcasts entre otras cosas.</p>
<p>Vamos a ver cómo se migra un podcast a Castopod. En este artículo se trata de Spotify, pero el proceso va a ser muy similar en cualquier otro caso.</p>
<h2 id="obtén-castopod">Obtén Castopod</h2>
<p>Castopod está disponible para instalar en tu propio servidor, que puede ser una VPS o una máquina sencilla conectada a internet, o puedes contratarlo en varios proveedores especializados. En <a href="https://castopod.org/">castopod.org</a> tienes acceso a información acerca de todas las opciones. Yo lo he instalado en un mini PC, en mi casa, usando Docker y NGINX como proxy inverso. Publicaré algo al respecto en lo sucesivo, pero hoy me voy a centrar en el <strong>proceso de salida de Spotify</strong>.</p>
<h3 id="opcional-obtén-tu-propio-dominio-y-si-es-posible-una-cdn">Opcional: obtén tu propio dominio y, si es posible, una CDN</h3>
<p>Si lo vas a instalar en tu casa, vas a necesitar un dominio. Si lo usas ofrecido por alguno de los proveedores, probablemente puedas usar un subdominio proporcionado por ellos.</p>
<p>En cualquier caso, como un podcast sirve archivos de cierto peso, es buena idea contratar un servicio que te ofrezca una red de distribución de contenidos (CDN) con presencia en múltiples geografías: actuará de caché, relajando los requisitos de ancho de banda de tu proveedor, y además acelerará el acceso a tu podcast desde geografías lejanas. Personalmente, uso la cuenta gratuita de Cloudflare para los dos asuntos: dominio y CDN.</p>
<h2 id="importa-tu-podcast-en-castopod">Importa tu Podcast en Castopod</h2>
<p>Una vez Castopod está funcionando en tu servidor, es hora de importar tu podcast en Castopod. Esto se hace indicando la dirección del feed RSS a Castopod en su panel de control, así como el nombre de usuario en el fediverso, la categoría y el idioma del contenido.</p>
<figure class="ma0 w-75">
    <img loading="lazy" src="/images/20240524-castopodimport.png"
         alt="Formulario de importación de un podcast en Castopod. Permite introducir la dirección del feed, indicar el idioma, la categoría y el nombre de usuario de la cuenta en el fediverso."/> <figcaption>
            <p>Formulario de importación de un podcast en Castopod.</p>
        </figcaption>
</figure>

<p>![)Formulario de importación de un podcast en Castopod
Es importante tener en cuenta que Castopod va a reinterpretar todo el feed y a validarlo. Uno de los problemas que tuve derivados de este aspecto es que, si a alguno de tus episodios le faltan las notas (el contenido del campo <code>&lt;description /&gt;</code>), el proceso va a fallar, tendrás que corregir el episodio en concreto, esperar a que Spotify republique tu feed, y reanudar el proceso donde se interrumpió. En mi caso, llevó unos cuantos intentos:</p>
<figure class="ma0 w-75">
    <img loading="lazy" src="/images/20240524-castopodimportlog.png"
         alt="Pantalla de Castopod que muestra diferentes intentos de importar sobre la marcha en mi nuevo servidor. Podemos contar 8 intentos fallidos, seguidos de tres exitosos para sincronizar metadatos editados y publicaciones posteriores al proceso original de importación"/> <figcaption>
            <p>Distintos intentos fallidos antes de completar la importación de sobre la marcha.</p>
        </figcaption>
</figure>

<h3 id="opcional-activa-las-estadísticas-op3">Opcional: activa las estadísticas OP3</h3>
<p>Las estadísticas que ofrece Castopod por defecto son bastante buenas, pero si te interesa la iniciativa <em>Open Podcast Prefix Project</em>, <a href="https://op3.dev/">OP3</a>, es un buen momento para unirte a ella activando las estadísticas con un sólo click en el formulario de edición de tu podcast.</p>
<figure class="ma0 w-75">
    <img loading="lazy" src="/images/20240524-castopodeditpod.png"
         alt="Formulario de edición del podcast recién importado. De interés es el hecho de que para activar las estadísticas OP3, en Castopod se hace con un click."/> <figcaption>
            <p>Formulario de edición del podcast recién importado.</p>
        </figcaption>
</figure>

<h2 id="redirige-tu-antiguo-feed-al-nuevo-permanentemente">Redirige tu antiguo feed al nuevo, permanentemente</h2>
<p>Éste es el paso más sencillo, y más crítico. En Spotify para Podcasters, en el panel de edición de tu podcast, encontrarás un campo de texto para introducir un nuevo feed al que redirigir el antiguo. Esta redirección se implementa mediante un código de estado del protocolo HTTP, 301, que indica a los clientes, índices, buscadores y directorios de podcasting que el feed se ha mudado y nunca volverá a Spotify. En el cuerpo de la respuesta que acompaña a ese estado de redirección permanente 301 está la dirección del nuevo feed. El comportamiento esperado para clientes HTTP (cualquier aplicación o servicio que quiera acceder al feed) cuando reciben una respuesta con estado 301 es actualizar sus registros a la nueva dirección y no volver a intentar acceder a la antigua nunca más.</p>
<p>A partir de este momento, y en un tiempo indeterminado, todos tus suscriptores empezarán a recibir contenido por el feed nuevo, y si tienes la misma experiencia que yo, de una forma totalmente transparente.</p>
<blockquote>
<p>⚠️ <strong>Cuando esto pase, perderás el control de tu podcast en Spotify para Podcasters</strong></p></blockquote>
<p>Esto es algo de lo que Spotify no te avisa claramente, pero cuando Spotify perciba que el podcast ya no está bajo su control, perderás acceso a funciones de edición del mismo, e incluso a la posibilidad de borrar los datos del mismo en Spotify.</p>
<p>Aún cuando veas que esto está pasando, no borres aún tu cuenta de Spotify para Podcasters: si algún rezagado no ha recibido la instrucción de redirección y borras tu cuenta, se borra la redirección, con lo cual podrías perder oyentes. Personalmente, yo voy a borrarla pasado un mes.</p>
<blockquote>
<p>⚠️ Escoge cuidadosamente el momento de hacer esto si alojas tu nuevo podcast en tu casa.</p>
<p>En el momento en el que actives la redirección en Spotify, servicios de directorio que probablemente no sepas que estaban indexando tu feed recibirán las coordenadas del feed nuevo. Estos servicios de directorio usan procesos tipo <em>arañas</em> (mala traducción de <em>web crawlers</em>) para recorrer el feed y reindexarlo todo.</p>
<p>El cambio desde Spotify para Podcasters a Castopod es muy profundo. En términos de la propia estructura del feed, de dónde están alojados los ficheros, y del nombre de los mismos, el efecto es que todos estos procesos perciben tu podcast como una <em>reedición</em> completa. Como decimos en España, <em>a este feed no lo reconoce ni su padre</em>. Esto hace que todos estos actores invaliden todos los datos previos y lo recorran todo de nuevo, descargando todos y cada uno de tus episodios, metadatos, imágenes&hellip; todo. Y en este momento, aunque tengas CDN, su caché todavía está vacía, por lo que todas las peticiones van a ir directas a tu casa.</p>
<p>Estos servicios tienen una capacidad de cómputo y de red inmensamente mayor que la de tu casa, por lo que todos* ataquen tu feed a la vez*, tu conexión a internet va a experimentar un pico de tráfico de subida muy significativo, que puede afectar a tu conectividad, especialmente si hay alguien trabajando desde casa.</p>
<p>Por eso, mejor hazlo por la noche. No seas como yo 😅</p></blockquote>
<h2 id="solicita-el-traspaso-de-tu-podcast-a-tu-propiedad-en-los-distintos-directorios-que-te-interesen">Solicita el traspaso de tu Podcast a tu propiedad en los distintos directorios (que te interesen)</h2>
<p>Esto es importante.</p>
<p>Me voy a centrar en Apple Podcasts, pero es fácil entender que esto deberías hacerlo allá en donde te interese tener presencia, en tu caso particular.</p>
<p>Acabas de redirigir tu feed a Castopod, tu conexión a internet en casa las ha pasado canutas, y has tenido que dar explicaciones a tu mujer e hijos de por qué internet iba como una patata, pero todo ha vuelto a su cauce normal, todo funciona y los primeros oyentes que sabían que ibas a cambiar esto te felicitan, te dicen que las descargas van más lentas&hellip;</p>
<p>Sin embargo, los directorios de podcasting como Apple Podcasts no te tienen registrado como el autor o el responsable del mismo a la hora de enviarte alertas y avisos con respecto a tu podcast. Situaciones en las que te podrías ver involucrado y que te interesaría resolver con Apple, como una potencial violación de los Términos y Condiciones de su servicios de directorio, no te serán notificadas.</p>
<p>Por otro lado, tampoco tienes control sobre tu podcast en Spotify para Podcasters, que es a donde Apple enviaría este tipo de mensajes en caso de conflicto. Spotify te ha dado la patada en cuanto la redirección se ha vuelto efectiva.</p>
<p>Estás en tierra de nadie.</p>
<p>Es hora de ponerse en contacto con Apple Podcasts Connect y solicitar que te den control sobre tu podcast.</p>
<p>Para eso, ármate con tu fluidez en el idioma de Shakespeare, porque creo que el formulario no se ofrece en Español, y haz lo siguiente (efectivo en Mayo de 2024):</p>
<ol>
<li>
<p>Entra en <a href="https://podcastsconnect.apple.com">Apple Podcasts Connect</a>, o regístrate si no tienes cuenta.</p>
</li>
<li>
<p>Abajo de todo, selecciona &ldquo;<em>Contact Us</em>&rdquo;</p>
</li>
<li>
<p>En el formulario que aparece, rellena tus datos y selecciona como tema de consulta &ldquo;<em>Missing Podcast(s)</em>&rdquo; dentro de &ldquo;<em>Content Management</em>&rdquo;. Tendrás que introducir los siguientes datos:</p>
</li>
<li>
<p>El título del podcast (&quot;<em><strong>Podamigos del cocido</strong></em>&quot;)</p>
</li>
<li>
<p>El <em>id</em> del podcast. Entiendo que se trata de los números al final en la URL del podcast en Apple Podcasts: &ldquo;<strong>id817487481</strong>&rdquo;.</p>
</li>
<li>
<p>La URL completa a la página del podcast en Apple Podcasts.</p>
</li>
<li>
<p>La URL del nuevo feed.</p>
</li>
<li>
<p>En el cuerpo del mensaje, indica que has migrado tu podcast desde Spotify a otro hosting, indica también la del anterior por si acaso el operador que te atienda todavía tiene el antiguo feed en sus cachés, y pide instrucciones como te gustaría que te las pidiesen a ti.</p>
</li>
</ol>
<p>Este fue el mensaje enviado, nada fuera de lo común:</p>
<blockquote>
<p>Good afternoon.</p>
<p>I am writing as the author of the show &ldquo;sobre la marcha&rdquo;, which was previously hosted on Spotify for Podcasters. I recently re-hosted the show on my own hosting server, and I would like to claim ownership and be able to manage it under my Apple Podcasts Connect dashboard. I hope this is the right method / channel to request so.</p>
<p>Looking forward to hearing from you,</p>
<p>~Gabriel Viso Carrera</p>
<p>PS – before today, the show&rsquo;s feed was &ldquo;<a href="https://anchor.fm/s/dd4f78/podcast/rss%22">https://anchor.fm/s/dd4f78/podcast/rss"</a>. As part of the migration, it has been placed a 301 permanent redirection to the feed I filled in this form; please take it into consideration of you see that the redirection hasn&rsquo;t been followed in your systems yet. Please note that no further episodes will be appearing in such feed.</p></blockquote>
<p>Te enviarán un correo en un plazo de un par de días, pidiéndote más datos (como el ID de tu cuenta en Apple Podcasts Connect), y dándote un código numérico que deberás introducir en algún campo del feed. Por ejemplo, mí me dieron como opciones el campo de <code>&lt;copyright /&gt;</code>, <code>&lt;keyword /&gt;</code> o <code>&lt;verification /&gt;</code>.</p>
<p>En negrita los aspectos clave que nos piden en esta respuesta de ejemplo, que es lo que me contestaron a mí con algunas cosas cambiadas.</p>
<blockquote>
<p>Hello Gabriel,</p>
<p>Thanks for reaching out to us. I hope you&rsquo;re doing well.</p>
<p>To get access to the show in Apple Podcasts Connect, we can transfer the ownership of it to your account.</p>
<p>To transfer ownership, <strong>enter the six-digit authorization code “381234” in the verification, keyword, or copyright fields of your RSS feed</strong>.</p>
<p>You can update the six-digit authorization code on your hosting provider’s platform or edit the metadata tags in your RSS feed. If you need assistance with this update, contact your hosting provider. We’ll review your request after we receive your reply.</p>
<p>When you reply, <strong>tell us where you entered the authorization code so we can find it <strong>and</strong> include the Account ID of your Apple Podcasts Connect account</strong>.</p>
<p>You can locate the Account ID on the <a href="https://podcastsconnect.apple.com/account/details">Details pane</a> in the Account section in Apple Podcasts Connect.</p>
<p>Example Account ID: 12a345bc6-d7ef-8901-2ghi-j3k4lm56n7o8</p>
<p>If you have additional questions related to this request, please reference case number 384791872432.</p>
<p>Best regards,</p>
<p>Mary Jane
Apple Inc.</p></blockquote>
<p>El feed que genera Castopod no tiene campo <code>&lt;verification /&gt;</code>, pero como añadir campos a medida es sencillísimo, es la solución ideal porque nadie recibirá ese cambio de forma visible. Ve al panel de control de edición de tu podcast, y abajo de todo encontrarás un campo donde poder poner el campo extra:</p>
<figure class="ma0 w-75">
    <img loading="lazy" src="/images/20240524-castopodeditpodverification.png"
         alt="Captura de pantalla del panel de edición del podcast, mostrando el campo donde podemos introducir el código de verificación. Este lugar es &#34;Advanced Parameters&#34;; y debemos introducir el campo con las etiquetas XML incluidas, por ejemplo &amp;lt;verification&amp;gt;381234&amp;lt;/verification&amp;gt;"/> <figcaption>
            <p>Captura de pantalla del panel de edición del podcast, mostrando el campo donde podemos introducir el código de verificación.</p>
        </figcaption>
</figure>

<p>Salva los cambios en tu podcast, verifica que en unos segundos, el feed tiene el mencionado campo dentro del objeto <code>&lt;channel /&gt;</code>, y contesta a Apple con los datos que te han solicitado, que incluyen dónde pueden encontrar el valor en cuestión.</p>
<blockquote>
<p>Good morning, Mary Jane.</p>
<p>Thanks for your prompt reply. As requested:My Podcasts Connect Account ID is <strong>12a345bc6-d7ef-8901-2ghi-j3k4lm56n7o8</strong>.I&rsquo;ve just included the code you provided in the tag of my feed, inside the &lt;channel /&gt; element:</p>
<p>&lt;rss version=&ldquo;2.0&rdquo;&gt;
&lt;channel&gt;
&hellip;
**        &lt;verification&gt;381234&lt;/verification&gt;**
&hellip;
&lt;/channel&gt;
&lt;/rss&gt;</p>
<p>Thank you for your help and kind regards,
~Gabriel</p></blockquote>
<p>Cuando recibas la confirmación y al cabo de un tiempo prudencial que te indicarán, deberías poder controlar tu relación con el directorio de Apple Podcasts tú mismo.</p>
<figure class="ma0 w-75">
    <img loading="lazy" src="/images/20240524-applepodconnect.png"
         alt="Captura de pantalla de mi panel de control de Apple Podcasts Connect mostrando mi podcast &#34;sobre la marcha&#34;"/> <figcaption>
            <p>Captura de pantalla de mi panel de control de Apple Podcasts Connect mostrando mi podcast &ldquo;sobre la marcha&rdquo;</p>
        </figcaption>
</figure>

<h2 id="no-tengas-prisa-en-borrar-cosas">No tengas prisa en borrar cosas</h2>
<p>En realidad ya está, pero es importante que no corras a Spotify y a tu feed a borrar cosas y deshacer cambios, porque puedes revertir el proceso sin querer.</p>
<ol>
<li>Como comentaba en el apartado de la redirección permamente, no tengas demasiada prisa en borrar tu cuenta de Spotify, porque podrías perder oyentes.</li>
<li>Aún cuando ya tengas tu podcast en Apple Podcasts Connect, no corras a editar tu podcast en Castopod para retirar el campo de verificación. Ten en cuenta que el directorio de podcasts de Apple tienen páginas en todo el mundo, y no nos interesa que, por alguna razón, alguna versión antigua del feed se haya quedado en algún sitio y reasignen tu podcast a Spotify. Deja ese campo de verificación en tu feed el tiempo que te parezca oportuno.</li>
</ol>
<h2 id="pues-ya-estaría">Pues ya estaría</h2>
<p>Hasta aquí mi experiencia. Espero que te resulte útil e interesante.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Igual de personal, pero mucho más rápido</title>
      <link>https://gvisoc.com/posts/personal-y-rapido/</link>
      <pubDate>Fri, 19 Apr 2024 11:49:04 +0000</pubDate><author>gabriel@gvisoc.com (Gabriel Viso Carrera)</author>
      <guid>https://gvisoc.com/posts/personal-y-rapido/</guid>
      <description>Cuando las barbas del vecino veas pelar, no alojes cosas en tu NAS.</description>
      <content:encoded><![CDATA[<p>Esta semana no he publicado ningún artículo más allá del mero eco de <a href="https://gvisoc.com/posts/publicidad-en-windows-11-otra-vez/">la falta de vergüenza de Microsoft</a>, que está probando publicidad en Windows 11. Un producto de 145€.</p>
<p>En lugar de escribir, me he dedicado a mover <a href="https://fedi.gvisoc.com/@gabriel">mi instancia personal de mastodon</a>, este blog, y <a href="https://micromaquina.com">micromáquina</a>, desde una Raspberry Pi 4B de 4GB a un Intel NUC. Ya tenía pensado mover todo a mi NAS utilizando Docker, pero finalmente decidí separar las cosas: en vez de seguir apilando Raspberrys, esta semana consolidé todo en una nueva máquina de mayor potencia pero consumo relativamente contenido.</p>
<p><img alt="Una Raspberry Pi con una caja disipadora. A su lado hay un disco duro conectado por USB." loading="lazy" src="/images/20240419-rpi.jpg">Esta Raspberry Pi 4, de 4 GB, llevaba encendida dando servicio a mi instancia de Mastodon y a Micromáquina durante más de 1 año y medio, ininterrumpidamente.</p>
<p>La motivación principal para este movimiento ha sido la experiencia de <a href="https://fedi.gvisoc.com/@vidalpascual@mas.to">Jacobo Vidal Pascual</a>, del podcast Desde El Reloj, con una serie de fallos en su NAS.</p>
<p>Podéis escucharle aquí: &ldquo;<a href="https://www.desdeelreloj.com/e0822/">E0822: 32 horas sin NAS, sin Docker y sin página web</a>&rdquo;</p>

<style>
.toot {
    --toot-bg: var(--entry);
    --toot-border: rgba(0,0,0,0.1);
    --toot-radius: 12px;
    --toot-shadow: 0 1px 3px rgba(0,0,0,0.1);

    background: var(--toot-bg);
    border: 1px solid var(--toot-border);
    border-radius: var(--toot-radius);
    box-shadow: var(--toot-shadow);
    padding: 16px;
    margin: 1.5rem auto;
    max-width: 600px;
    font-family: -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
}

.dark .toot {
    --toot-border: rgba(255,255,255,0.1);
    --toot-shadow: 0 1px 3px rgba(0,0,0,0.2);
}

.toot-header {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    margin-bottom: 12px;
}

.toot-profile img {
    width: 48px;
    height: 48px;
    border-radius: 4px;
    display: block;
}

.toot-author {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.toot-author-name {
    color: var(--primary);
    font-weight: 700;
    text-decoration: none;
    font-size: 15px;
    line-height: 20px;
}

.toot-author-handle {
    color: var(--secondary);
    text-decoration: none;
    font-size: 14px;
    line-height: 18px;
}

.toot-content {
    color: var(--content);
    font-size: 15px;
    line-height: 1.5;
    margin: 12px 0;
}

.toot-content p {
    margin: 0 0 12px;
}

.toot-content p:last-child {
    margin-bottom: 0;
}

.toot-content a {
    color: #2b90d9;
    text-decoration: none;
}

.toot-content a:hover {
    text-decoration: underline;
}

.toot-media-grid {
    display: grid;
    grid-gap: 8px;
    margin: 12px 0;
    border-radius: 12px;
    overflow: hidden;
}

.toot-media-grid[data-count="1"] {
    grid-template-columns: 1fr;
}

.toot-media-grid[data-count="2"] {
    grid-template-columns: repeat(2, 1fr);
}

.toot-media-grid[data-count="3"] {
    grid-template-columns: repeat(2, 1fr);
}

.toot-media-grid[data-count="3"] > :first-child {
    grid-column: 1 / -1;
}

.toot-media-grid[data-count="4"] {
    grid-template-columns: repeat(2, 1fr);
}

.toot-media-item img {
    width: 100%;
    height: auto;
    display: block;
    border-radius: 4px;
}

.toot-footer {
    margin-top: 12px;
    padding-top: 12px;
    border-top: 1px solid var(--toot-border);
    color: var(--secondary);
    font-size: 14px;
}

.toot-date {
    color: inherit;
    text-decoration: none;
}

.toot-date:hover {
    text-decoration: underline;
}
</style>








    
    
        
        
    

    
        <div class="toot">
            <div class="toot-header">
                <a class="toot-profile" href="https://mas.to/@desdeelreloj" rel="noopener">
                    <img src="https://media.mas.to/accounts/avatars/109/694/690/202/287/596/original/05c31515605728cb.jpg"
                         alt="Avatar for @desdeelreloj@mas.to"
                         loading="lazy">
                </a>
                <div class="toot-author">
                    <a class="toot-author-name"
                       href="https://mas.to/@desdeelreloj"
                       rel="noopener">Desde El Reloj</a>
                    <a class="toot-author-handle"
                       href="https://mas.to/@desdeelreloj"
                       rel="noopener">@desdeelreloj@mas.to</a>
                </div>
            </div>

            <div class="toot-content"><p>Sigo con la web caída, disculpad las molestias… 😩</p></div>

            

            <div class="toot-footer">
                <a href="https://mas.to@desdeelreloj/status/112240632440123033"
                   class="toot-date"
                   rel="noopener">9:48 AM · abr. 9, 2024</a>
            </div>
        </div>
    


<p>El relato de los problemas causados por componentes moribundos, el precio de reemplazarlos y el riesgo que corrieron sus archivos personales, me dejaban claro el mensaje: tener varios servidores en mi NAS, constantemente accedidos por todos vosotros y por Activity Pub en el caso de Mastodon, elevaría el acceso a los discos a un ritmo fuera de mi control, reduciendo su vida útil. Los componentes de un NAS son *bastante *más caros que los componentes de un equipo tradicional, porque están pensados para evitar errores que no afectan el tipo de servicios que quiero alojar: para mi instancia de Mastodon y mis webs no necesito ni memoria ECC, ni discos en RAID; con una buena estrategia de copias de seguridad me basta. La perspectiva de poder llegar a ver todos mis datos en riesgo por el hecho de alojar webs públicas y los costes asociados no me hacían ninguna gracia.</p>
<p>Por otro lado, mi NAS es de Synology, lo cual tiene una serie de inconvenientes derivados de la licencia de su software y su política de soporte. No ya por cuestiones éticas de licencias del software y potencial <a href="https://gvisoc.com/posts/enmierdificacion/"><em>enmierdificación</em></a> futura a varios nieveles, sino también porque todo esto nos sujeta a políticas de soporte y fin de actualizaciones por las que podemos llegar a vernos obligados a renovar un equipo que, de otra manera, tendría una vida útil mucho mayor.</p>
<p>Así que nada: cuanto, antes mejor, y me puse manos a la obra.</p>
<p>Los beneficios de este cambio son varios:</p>
<ul>
<li>A corto plazo, el aprendizaje. Estoy aprendiendo un montón acerca de <em>proxies inversos</em> y de Docker, puesto que mover una instancia de Mastodon desde una máquina física a contenedores Docker, sin perder datos, no es trivial.</li>
<li>A medio plazo tiene los beneficios ya comentados en el área de la vida útil del <em>hardware</em> donde tengo nuestros datos personales y los de la familia.</li>
<li>A largo plazo, estos cambios transforman la forma de relacionarnos con internet y a través de internet, ya que nos devuelven un control que perdimos hace tiempo porque hacen de la tecnología algo mucho más personal.</li>
</ul>
<p>Por el momento, ya se va notando una mejoría de rendimiento importante.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
