<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://erosion.es/blog/feed.xml" rel="self" type="application/atom+xml" /><link href="https://erosion.es/blog/" rel="alternate" type="text/html" /><updated>2025-06-04T14:30:40+02:00</updated><id>https://erosion.es/blog/feed.xml</id><title type="html">Erosión continua</title><subtitle>Explora la frontera entre lo real y lo digital:  coches RC, videojuegos, automatización y aventuras tecnológicas con alma. </subtitle><entry><title type="html">Indie SOLO game developer</title><link href="https://erosion.es/blog/2025/06/03/indie-solo-game-developer/" rel="alternate" type="text/html" title="Indie SOLO game developer" /><published>2025-06-03T17:34:21+02:00</published><updated>2025-06-03T17:34:21+02:00</updated><id>https://erosion.es/blog/2025/06/03/indie-solo-game-developer</id><content type="html" xml:base="https://erosion.es/blog/2025/06/03/indie-solo-game-developer/"><![CDATA[<h1 id="mundo-ordinario">Mundo ordinario</h1>
<p>Despierto, reviso la web. La bbdd permanece inmutable, como una roca que se deshace con el tiempo, pero no con la mirada. Nadie entra. Nadie sale. El circuito real espera, los coches esperan, la página espera. Yo también espero.</p>

<h1 id="llamado-a-la-aventura">Llamado a la aventura</h1>
<p>Dice ChatGPT que además de publicar 3 vídeos cortos diarios, debería hacer un blog. Seguro que mejora el SEO muchíiiiiiiiisimo. Sarcasmo nivel planeta. Me río yo solo, porque nadie más lo hará.</p>

<p>Instalo Jekyll y Bundler y lo configuro con cariño:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gem <span class="nb">install </span>jekyll bundler
</code></pre></div></div>

<h1 id="rechazo-de-la-llamada">Rechazo de la llamada</h1>
<p>El enemigo es la <a href="https://erosion.es">erosión</a>: cada día, menos ganas, menos visitas, menos fe.</p>

<p>¿Para qué escribir un blog? Nadie lo va a leer.</p>

<h1 id="encuentro-con-el-mentor">Encuentro con el mentor</h1>

<p>GoatCounter hace que solo 2 líneas te separen del contador de tu soledad. Suena fantástico.</p>

<p>Lo configuro con precisión suiza, para saber exactamente cuántos no vienen, metiendo esto en el <em>head</em> del <em>layouts/default.html</em>:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;script </span><span class="na">data-goatcounter=</span><span class="s">"https://erosion.goatcounter.com/count"</span>
        <span class="na">async</span> <span class="na">src=</span><span class="s">"//gc.zgo.at/count.js"</span><span class="nt">&gt;&lt;/script&gt;</span>
</code></pre></div></div>

<p>Y retoco el <em>config.yml</em> de mi blog:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">title</span><span class="pi">:</span> <span class="s">Erosión continua</span>
<span class="na">email</span><span class="pi">:</span> <span class="s">cctpidmdlcdtc@gmail.com</span>
<span class="na">description</span><span class="pi">:</span> <span class="pi">&gt;-</span>
  <span class="s">Explora la frontera entre lo real y lo digital: </span>
  <span class="s">coches RC, videojuegos, automatización y aventuras tecnológicas con alma. </span>
<span class="na">baseurl</span><span class="pi">:</span> <span class="s2">"</span><span class="s">/blog"</span> <span class="c1"># domain.com/blog es mejor para el seo que blog.domain.com</span>
<span class="na">url</span><span class="pi">:</span> <span class="s2">"</span><span class="s">https://erosion.es"</span>
<span class="na">permalink</span><span class="pi">:</span> <span class="s">/:year/:month/:day/:title/</span>
<span class="na">theme</span><span class="pi">:</span> <span class="s">minima</span>
<span class="na">plugins</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">jekyll-feed</span>
<span class="na">goatcounter</span><span class="pi">:</span>
  <span class="na">domain</span><span class="pi">:</span> <span class="s2">"</span><span class="s">erosion.goatcounter.com"</span>
</code></pre></div></div>

<h1 id="cruzando-el-umbral">Cruzando el umbral</h1>
<p>Lanzo el blog:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Servidor simulando producción (carga GoatCounter)</span>
<span class="nv">JEKYLL_ENV</span><span class="o">=</span>production bundle <span class="nb">exec </span>jekyll serve
</code></pre></div></div>

<p>Lo reviso desde el móvil y el ordenador, como si esperara sorprenderme. El contador sigue en cero. La única notificación es la de mi propio acceso, que ignoro para no romper la estadística perfecta de la nada.</p>

<h1 id="recompensa">Recompensa</h1>
<p>La recompensa es saber, con precisión quirúrgica, que nadie viene. Y aun así, el circuito sigue ahí. El código sigue ahí. Yo sigo ahí.</p>

<h1 id="regreso-con-elixir">Regreso con elixir</h1>
<p>Decido reemplazar la home con un haiku:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;h1&gt;</span>Error 418:<span class="nt">&lt;/h1&gt;</span>
<span class="nt">&lt;p&gt;</span>El servidor existe<span class="nt">&lt;br&gt;</span>Pero tú no<span class="nt">&lt;/p&gt;</span>

<span class="nt">&lt;script&gt;</span>
<span class="kd">let</span> <span class="nx">totalVisitors</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="c1">// Spoiler: siempre será 0</span>
<span class="nt">&lt;/script&gt;</span>
</code></pre></div></div>]]></content><author><name></name></author><category term="métricas" /><summary type="html"><![CDATA[Mundo ordinario Despierto, reviso la web. La bbdd permanece inmutable, como una roca que se deshace con el tiempo, pero no con la mirada. Nadie entra. Nadie sale. El circuito real espera, los coches esperan, la página espera. Yo también espero.]]></summary></entry><entry><title type="html">Hackeado antes de empezar</title><link href="https://erosion.es/blog/2025/06/03/hackeado-antes-de-empezar/" rel="alternate" type="text/html" title="Hackeado antes de empezar" /><published>2025-06-03T01:14:21+02:00</published><updated>2025-06-03T01:14:21+02:00</updated><id>https://erosion.es/blog/2025/06/03/hackeado-antes-de-empezar</id><content type="html" xml:base="https://erosion.es/blog/2025/06/03/hackeado-antes-de-empezar/"><![CDATA[<h1 id="mundo-ordinario">Mundo ordinario</h1>

<p>Lanzo <a href="https://erosion.es">mi web</a> al vacío digital.</p>

<p>Subo vídeos cuidadosamente editados. Los pixelo, los recorto, les inyecto fragmentos de mi alma. Son buenos. O al menos, eso quiero creer. Cada frame pulido, cada transición calculada. Horas invertidas en la ilusión de que alguien, en algún lugar, esperaba esto que he creado.</p>

<p>Así que espero.</p>

<h1 id="llamada-a-la-aventura">Llamada a la aventura</h1>
<p>Nadie entra.</p>

<p>Nadie parece saber que existo.</p>

<p>La red es vasta, pero ignora.</p>

<h1 id="rechazo-de-la-llamada">Rechazo de la llamada</h1>
<p>Quizás es normal. Es la fase inicial. Silencio como parte del proceso, dicen.</p>

<p>Mentiras reconfortantes. Me las trago, las saboreo.</p>

<p>Cada visita que no ocurre es una confirmación de que el universo permanece indiferente.</p>

<h1 id="encuentro-con-el-mentor-o-el-enemigo-que-lo-reemplaza">Encuentro con el mentor (o el enemigo que lo reemplaza)</h1>
<p>Entonces llegan ellos. No los visitantes. No los fans.</p>

<p>Primero los bots. Pero no los de los buscadores. Estos no son arañas, son hienas. Después llegan sus dueños, los hackers.</p>

<p>No les interesa lo que hago. No miran los vídeos. Solo el agujero. Para ellos, mi web no es arte: es infraestructura. Y la infraestructura existe para ser explotada.</p>

<h1 id="cruce-del-umbral">Cruce del umbral</h1>
<p>Mi servidor <em>coturn</em> abierto. Vulnerable.</p>

<p>Coturn tiene una “<a href="https://github.com/coturn/coturn/pull/1588">funcionalidad</a>” documentada desde hace 3 años. Sigue abierta: won’t fix (by design).</p>

<p>Los hackers lo ven. Lo usan. Amplificación DDoS mediante STUN/TURN reflection. Mi servidor se convierte en su arma, mi ancho de banda en su munición. Soy cómplice involuntario de batallas que no elegí.</p>

<h1 id="pruebas-aliados-y-enemigos">Pruebas, aliados y enemigos</h1>
<p>Un día, Hetzner me envía el veredicto:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Unfortunately we have had to lock the IP address(es) below due to network issues.
</code></pre></div></div>

<p>Supe del email tras comprobar que la web no funcionaba, entrar en el panel de usuario de Hetzner y ver un amenazante banner. Eso me llevó a pensar si se habrían intentado ponerse en contacto conmigo antes de desconectarme.</p>

<p>Habían pasado semanas. Yo sin embargo había decidido que ese era el momento de empezar a emitir en directo todos los días, que la plataforma era ya lo suficientemente estable.</p>

<p>Murphy sabe cómo y cuándo golpearte, eso es innegable.</p>

<h1 id="acercamiento-a-la-caverna-más-profunda">Acercamiento a la caverna más profunda</h1>
<p>Investigo. Dormir se vuelve un lujo que no puedo permitirme. Tengo que recuperar el control de mi web, tengo que volver a poner en marcha el juego.</p>

<p>Encuentro el problema. Yo soy el problema.</p>

<p>Los desarrolladores conocían el riesgo y decidieron que no era su problema. La responsabilidad, como siempre, recae en el usuario final, en el ingenuo que confía en los defaults.</p>

<h1 id="prueba-suprema">Prueba suprema</h1>
<p>Decido construir el muro. No por heroísmo, sino por terquedad. Por el absurdo placer de resolver un problema que yo mismo creé.</p>

<p>El firewall no lo activo por defecto: lo invoco dinámicamente cuando lo necesito. Una solución elegante para un problema estúpido.</p>

<ol>
  <li>
    <p>Defino una lista de ips que puedo manipular dinámicamente:</p>

    <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code> table inet filter <span class="o">{</span>
     <span class="c"># Set dinámico para IPs permitidas por Python</span>
     <span class="nb">set </span>coturn_dynamic_ips <span class="o">{</span>
         <span class="nb">type </span>ipv4_addr
         flags dynamic,timeout
         <span class="nb">timeout </span>1h
         <span class="c"># Tamaño máximo del set (ajustar según necesidades)</span>
         size 1000
     <span class="o">}</span>

     chain input <span class="o">{</span>
         <span class="nb">type </span>filter hook input priority 0<span class="p">;</span> policy drop<span class="p">;</span>
            
         <span class="c"># Permitir tráfico de loopback</span>
         iif lo accept
            
         <span class="c"># Permitir tráfico establecido y relacionado</span>
         ct state established,related accept
                        
         <span class="c"># Puerto estándar STUN/TURN TCP</span>
         ip saddr @coturn_dynamic_ips tcp dport 3478 accept
            
         <span class="c"># Puerto estándar STUN/TURN UDP  </span>
         ip saddr @coturn_dynamic_ips udp dport 3478 accept
            
         <span class="c"># Rango de puertos RTP para COTURN</span>
         ip saddr @coturn_dynamic_ips udp dport 49152-65535 accept
</code></pre></div>    </div>
  </li>
  <li>
    <p>De este modo, puedo capturar la ip en nginx y meterla en una cabecera:</p>

    <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code> proxy_set_header X-Real-IP <span class="nv">$remote_addr</span><span class="p">;</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Y esa cabecera puedo capturarla desde el backend en python para usarla para configurar el cortafuegos:</p>

    <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">ip_value</span> <span class="o">=</span> <span class="n">request</span><span class="p">.</span><span class="n">headers</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">'</span><span class="s">X-REAL-IP</span><span class="sh">'</span><span class="p">)</span>
</code></pre></div>    </div>
  </li>
</ol>

<p>Una PoC. Frágil como porcelana china. Grotesca en su simplicidad. Funciona.</p>

<h1 id="recompensa-el-elixir-amargo">Recompensa (el elixir amargo)</h1>
<p>Lo integro en la web. Todo parece sellado ahora, fortificado contra la ingenuidad propia y la malicia ajena.</p>

<p>Pruebo. Atacan. Lo bloquea.</p>

<p>Yo los oigo. Persisten. Ya no me afectan. Sus ataques rebotan inútilmente contra mi paranoia convertida en código.</p>

<p>La web resurge. Blindada. Los ataques continúan, pero ahora son mi espectáculo, mis bufones.</p>

<p>La paradoja: ahora que soy “seguro”, el silencio es más denso. He construido la fortaleza perfecta para defender un contenido que nadie quiere consumir.</p>

<h1 id="el-camino-de-regreso">El camino de regreso</h1>
<p>No hay regreso.</p>

<p>No hay gloria.</p>

<p>La web sigue ahí. Abierta al público. Segura contra atacantes. Desierta de visitantes. Un monumento a la futilidad del esfuerzo solitario.</p>

<h1 id="resurrección">Resurrección</h1>
<p>Al final, tal vez esa era la lección que necesitaba aprender. No sobre seguridad web o firewalls o protocolos de red. Sino sobre la naturaleza fundamental de crear en el vacío: que el acto de creación tiene valor independiente de la recepción, que la defensa tiene mérito independiente del valor de lo defendido.</p>

<h1 id="regreso-con-el-elixir">Regreso con el elixir</h1>
<p>Mi trofeo: métricas de una guerra invisible. Paranoia como feature, no como bug.</p>

<p><a href="https://erosion.es">Mi web</a> está sola, pero inexpugnable.</p>

<p>Y yo también estoy solo. Pero al menos ahora entiendo que esa soledad no es un bug: es una feature.</p>]]></content><author><name></name></author><category term="hacking" /><category term="coturn" /><category term="nftables" /><category term="hetzner" /><summary type="html"><![CDATA[Mundo ordinario]]></summary></entry></feed>