<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
        <title>Infrastructure - Catégorie - arleo.eu</title>
        <link>https://www.arleo.eu/categories/infrastructure/</link>
        <description>Infrastructure - Catégorie - arleo.eu</description>
        <generator>Hugo -- gohugo.io</generator><language>fr</language><lastBuildDate>Mon, 29 Jun 2026 07:00:00 &#43;0200</lastBuildDate><atom:link href="https://www.arleo.eu/categories/infrastructure/" rel="self" type="application/rss+xml" /><item>
    <title>Hugo SEO : noindex sur les taxonomies pour corriger l&#39;alerte Bing &#34;too many thin pages&#34;</title>
    <link>https://www.arleo.eu/posts/noindex-taxonomies-hugo-bing-seo/</link>
    <pubDate>Mon, 29 Jun 2026 07:00:00 &#43;0200</pubDate>
    <author>Jmr</author>
    <guid>https://www.arleo.eu/posts/noindex-taxonomies-hugo-bing-seo/</guid>
    <description><![CDATA[<div class="featured-image">
                <img src="/images/noindex-taxonomies-hugo-bing-seo-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="contexte">Contexte</h2>
<p>Bing Webmaster Tools remontait une recommandation <strong>&ldquo;There are too many pages with insufficient content&rdquo;</strong> (sévérité : modérée) — 15 pages en erreur, toutes du même type :</p>
<ul>
<li><code>https://www.arleo.eu/en/tags/svg/</code></li>
<li><code>https://www.arleo.eu/en/tags/sonarr/</code></li>
<li><code>https://www.arleo.eu/en/categories/incidents/</code></li>
<li>etc.</li>
</ul>
<p>Ces pages sont les <strong>taxonomies Hugo</strong> — les listes par tag et par catégorie. Elles ne contiennent pas d&rsquo;article en elles-mêmes, juste une liste de liens. Pour Bing, c&rsquo;est du <em>thin content</em>.</p>
<hr>
<h2 id="stratégie-choisie">Stratégie choisie</h2>
<p>Trois options s&rsquo;offraient :</p>
<ol>
<li><strong>Bloquer via <code>robots.txt</code></strong> → déconseillé : empêche le crawl, masque les liens internes</li>
<li><strong>Enrichir le contenu des pages de taxonomie</strong> → trop coûteux pour un blog technique</li>
<li><strong><code>noindex,follow</code></strong> → la bonne réponse : on dit aux robots de ne pas indexer la page, mais de suivre les liens vers les articles</li>
</ol>
<p>L&rsquo;option 3 est retenue. Le <code>follow</code> préserve le maillage interne — les robots continuent de découvrir les articles via ces pages.</p>]]></description>
</item>
<item>
    <title>SEO Hugo : 404 Googlebot, aliases noindex et normalisation des sitemaps</title>
    <link>https://www.arleo.eu/posts/debug-seo-404-broken-links/</link>
    <pubDate>Fri, 29 May 2026 20:30:00 &#43;0200</pubDate>
    <author>Jmr</author>
    <guid>https://www.arleo.eu/posts/debug-seo-404-broken-links/</guid>
    <description><![CDATA[<div class="featured-image">
                <img src="/images/debug-seo-404-broken-links-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="contexte">Contexte</h2>
<p>Google Search Console remontait quatre catégories de problèmes sur arleo.eu :</p>
<ul>
<li><strong>404 Googlebot</strong> : <code>/fr/tag/cloudflare</code>, <code>/en/tag/nginx</code>, <code>/fr/tag/javascript</code>… URLs avec préfixe <code>/fr/</code> ou singulier <code>/tag/</code> jamais servi par nginx</li>
<li><strong>16 pages &ldquo;Exclue par la balise noindex&rdquo;</strong> : toutes des pages de redirection générées par <code>aliases:</code> dans le frontmatter Hugo</li>
<li><strong>Balise robots</strong> : <code>noodp</code> hardcodée dans le thème LoveIt</li>
<li><strong>Sitemap FR/EN</strong> : 104 vs 105 URLs — un tag doublon FR et deux tags manquants</li>
</ul>
<hr>
<h2 id="acte-1--aliases-hugo--redirections-nginx-301">Acte 1 : aliases Hugo → redirections nginx 301</h2>
<h3 id="pourquoi-hugo-génère-des-pages-noindex">Pourquoi Hugo génère des pages noindex</h3>
<p>Hugo génère les entrées <code>aliases:</code> du frontmatter comme des fichiers HTML statiques :</p>]]></description>
</item>
<item>
    <title>Hugo : gel des diagrammes Mermaid en SVG statiques dark/light</title>
    <link>https://www.arleo.eu/posts/debug-mermaid-svg-freeze/</link>
    <pubDate>Fri, 29 May 2026 20:00:00 &#43;0200</pubDate>
    <author>Jmr</author>
    <guid>https://www.arleo.eu/posts/debug-mermaid-svg-freeze/</guid>
    <description><![CDATA[<div class="featured-image">
                <img src="/images/debug-mermaid-svg-freeze-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="problème">Problème</h2>
<p>Les diagrammes Mermaid sur arleo.eu rendaient côté client via <code>cdn.jsdelivr.net</code>. Trois conséquences concrètes :</p>
<ol>
<li><strong>CSP contrainte</strong> — <code>script-src cdn.jsdelivr.net</code> et <code>worker-src cdn.jsdelivr.net</code> obligatoires (Mermaid v11 utilise des Web Workers pour ses parsers).</li>
<li><strong>Flash de rendu</strong> — le diagramme apparaît après l&rsquo;exécution JS, créant un décalage visible.</li>
<li><strong>Thème dark ignoré</strong> — Mermaid initialisait le SVG en mode clair même quand le thème du site était dark.</li>
</ol>
<p>La solution : générer les SVG <strong>au build</strong> avec <code>mmdc</code>, en dehors de tout contexte navigateur.</p>]]></description>
</item>
<item>
    <title>CSP A&#43; sur Hugo &#43; Cloudflare : de hash-based à origin allowlist, monitoring auto et durcissement</title>
    <link>https://www.arleo.eu/posts/csp-a-plus-hugo-cloudflare-origin-allowlist/</link>
    <pubDate>Thu, 28 May 2026 23:40:12 &#43;0200</pubDate>
    <author>Jmr</author>
    <guid>https://www.arleo.eu/posts/csp-a-plus-hugo-cloudflare-origin-allowlist/</guid>
    <description><![CDATA[<div class="featured-image">
                <img src="/images/csp-a-plus-hugo-cloudflare-origin-allowlist-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="contexte">Contexte</h2>
<p>arleo.eu tourne sur Hugo (VM KVM) → OpenResty (NUC) → Cloudflare (CDN/WAF). Objectif : score <strong>A+ sur Mozilla Observatory</strong> avec une CSP stricte qui résiste aux injections côté edge.</p>
<p>Le parcours a traversé trois stratégies en quelques semaines — nonces, hashes, puis origin allowlist — avant d&rsquo;aboutir à une solution stable et automatisée.</p>
<hr>
<h2 id="acte-1--pourquoi-hash-based-a-échoué">Acte 1 : pourquoi hash-based a échoué</h2>
<p>L&rsquo;idée de départ semblait solide : Hugo Pipes externalise tout le JS avec SRI, on liste les hashes dans la CSP, résultat propre. En pratique, deux problèmes ont rendu l&rsquo;approche impossible.</p>]]></description>
</item>
<item>
    <title>Plugin Cloudflare hugo-mcp : purge ciblée plutôt que totale</title>
    <link>https://www.arleo.eu/posts/hugo-mcp-plugin-cloudflare/</link>
    <pubDate>Thu, 14 May 2026 09:42:22 &#43;0200</pubDate>
    <author>Jmr</author>
    <guid>https://www.arleo.eu/posts/hugo-mcp-plugin-cloudflare/</guid>
    <description><![CDATA[<div class="featured-image">
                <img src="/images/hugo-mcp-plugin-cloudflare-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="tldr">TL;DR</h2>
<p>Le plugin Cloudflare de <a href="https://github.com/jmrGrav/hugo-mcp/releases/tag/v2.0.0" target="_blank" rel="noopener noreffer ">hugo-mcp v2.0</a> implémente 3 modes de purge cache (<code>full</code>, <code>partial</code>, <code>smart</code>). Le mode <code>partial</code> calcule les URLs liées à invalider (canonique + sitemap + RSS + listing + home) pour préserver 95% du cache CDN à chaque modification. Concrètement : 6 URLs purgées au lieu de tout vider. Ce post détaille le calcul, les pièges, et pourquoi <code>smart</code> est devenu le défaut.</p>]]></description>
</item>
<item>
    <title>Audit sécurité NUC : ModSecurity supprimé, 500 MB récupérés</title>
    <link>https://www.arleo.eu/posts/audit-securite-modsecurity-crowdsec/</link>
    <pubDate>Thu, 14 May 2026 05:31:23 &#43;0200</pubDate>
    <author>Jmr</author>
    <guid>https://www.arleo.eu/posts/audit-securite-modsecurity-crowdsec/</guid>
    <description><![CDATA[<div class="featured-image">
                <img src="/images/audit-securite-modsecurity-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="-en-bref">⚡ En bref</h2>
<p>Un audit de la stack sécurité du NUC révèle une <strong>double inspection WAF redondante</strong> : ModSecurity + OWASP CRS chargent 11 872 règles en mémoire malgré <code>SecRuleEngine Off</code>, en parallèle d&rsquo;un CrowdSec AppSec déjà actif et couvrant le même périmètre. Après suppression du module nginx ModSecurity et 5 autres correctifs ciblés, nginx passe de <strong>~520 MB à ~27 MB PSS</strong>. Sécurité identique, empreinte divisée par 20.</p>
<hr>
<h2 id="-architecture-avant-audit">🏗️ Architecture avant audit</h2>
<p>La stack sécurité reposait sur six couches empilées :</p>]]></description>
</item>
<item>
    <title>hugo-mcp v2.0 : un plugin-system Python en 200 lignes</title>
    <link>https://www.arleo.eu/posts/hugo-mcp-plugins-architecture/</link>
    <pubDate>Sat, 09 May 2026 23:23:49 &#43;0200</pubDate>
    <author>Jmr</author>
    <guid>https://www.arleo.eu/posts/hugo-mcp-plugins-architecture/</guid>
    <description><![CDATA[<div class="featured-image">
                <img src="/images/hugo-mcp-plugins-architecture-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="tldr">TL;DR</h2>
<p><a href="https://github.com/jmrGrav/hugo-mcp/releases/tag/v2.0.0" target="_blank" rel="noopener noreffer ">hugo-mcp v2.0.0</a> introduit un <strong>plugin-system Python</strong> qui permet à n&rsquo;importe qui d&rsquo;ajouter des hooks après chaque <code>create_page</code> / <code>update_page</code> / <code>delete_page</code>. 200 lignes de code pour le coeur, 3 plugins de production fournis (IndexNow, Google Indexing, Cloudflare). Ce post explique le design, les arbitrages, la sécurité, et montre comment écrire son propre plugin en 5 minutes.</p>]]></description>
</item>
<item>
    <title>Migration Grav → Hugo : 2 ans de blog basculés en une journée</title>
    <link>https://www.arleo.eu/posts/migration-grav-hugo/</link>
    <pubDate>Sat, 09 May 2026 22:29:45 &#43;0200</pubDate>
    <author>Jmr</author>
    <guid>https://www.arleo.eu/posts/migration-grav-hugo/</guid>
    <description><![CDATA[<div class="featured-image">
                <img src="/images/migration-grav-hugo-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="tldr">TL;DR</h2>
<p>Le 9 mai 2026, j&rsquo;ai basculé <code>arleo.eu</code> de <strong>Grav</strong> (CMS PHP) vers <strong>Hugo</strong> (générateur de site statique Go) en une session. Bascule <strong>atomique</strong> (≈ 0 seconde de downtime), 22 articles legacy migrés sous <code>/posts/</code> avec <strong>aliases SEO</strong> pour préserver les URLs Google indexées, monitoring BetterStack <code>/ping</code> intact pendant toute l&rsquo;opération.</p>
<p>Le code et le script de migration sont open source : <a href="https://github.com/jmrGrav/grav-to-hugo-migration" target="_blank" rel="noopener noreffer ">github.com/jmrGrav/grav-to-hugo-migration</a>.</p>]]></description>
</item>
<item>
    <title>Sprint sécurité MCP livré : v1.9.0, 10 chantiers, écosystème durci</title>
    <link>https://www.arleo.eu/posts/sprint-securite-mcp-livre/</link>
    <pubDate>Sat, 09 May 2026 18:42:54 &#43;0200</pubDate>
    <author>Jmr</author>
    <guid>https://www.arleo.eu/posts/sprint-securite-mcp-livre/</guid>
    <description><![CDATA[<div class="featured-image">
                <img src="/images/sprint-securite-mcp-livre-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="tldr">TL;DR</h2>
<p>Le 9 mai 2026, j&rsquo;ai livré en une session marathon les 10 chantiers du sprint sécurité MCP que <a href="/posts/roadmap-sprint-securite-mcp/" rel="">j&rsquo;avais annoncé en début de journée</a>. <code>hugo-mcp</code> est désormais en <strong>v1.9.0</strong> (<a href="https://github.com/jmrGrav/hugo-mcp/releases/tag/v1.9.0" target="_blank" rel="noopener noreffer ">GitHub Release</a>), commit <code>1404f83</code> GPG-signé.</p>
<p>Voici le recap haute-volée + un zoom pédagogique sur 2 chantiers qui ont une vraie valeur en dehors de mon contexte précis : <strong>C2 token rotation</strong> et <strong>C6 TLS interne</strong>.</p>
<h2 id="recap-des-10-chantiers">Recap des 10 chantiers</h2>
<table>
  <thead>
      <tr>
          <th>#</th>
          <th>Chantier</th>
          <th>Implémentation</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>C1</td>
          <td>Rate limiting</td>
          <td><code>slowapi</code>, 60 req/min par IP</td>
      </tr>
      <tr>
          <td>C2</td>
          <td>Token rotation</td>
          <td><code>tokens.json</code> + <code>token_mgr.py</code> CLI</td>
      </tr>
      <tr>
          <td>C3</td>
          <td>Audit logs JSON</td>
          <td><code>structlog</code>, événements machine-readable</td>
      </tr>
      <tr>
          <td>C4</td>
          <td>Pydantic v2 stricte</td>
          <td><code>CreatePageArgs</code> / <code>UpdatePageArgs</code> avec contraintes</td>
      </tr>
      <tr>
          <td>C5</td>
          <td>bcrypt cost-12</td>
          <td>Tokens hashés en stockage</td>
      </tr>
      <tr>
          <td>C6</td>
          <td>TLS NUC ↔ VM</td>
          <td>Cert EC P-256, uvicorn SSL, proxy vérifie le cert</td>
      </tr>
      <tr>
          <td>C7</td>
          <td>requirements.lock</td>
          <td>SHA-256 hashes via <code>pip-compile --generate-hashes</code></td>
      </tr>
      <tr>
          <td>C8</td>
          <td>Info disclosure</td>
          <td>Docs off, exception handler générique, <code>proxy_hide_header</code></td>
      </tr>
      <tr>
          <td>C9</td>
          <td>nginx WAF</td>
          <td>Enforcement POST + <code>application/json</code> sur <code>/mcp</code>, OWASP CRS actif</td>
      </tr>
      <tr>
          <td>C10</td>
          <td>Backup DR</td>
          <td><code>backup.sh</code> GPG-chiffré, rétention 30 jours</td>
      </tr>
  </tbody>
</table>
<p>Détails complets dans le <a href="https://github.com/jmrGrav/hugo-mcp/blob/main/CHANGELOG.md" target="_blank" rel="noopener noreffer ">CHANGELOG v1.9.0</a> et le commit <a href="https://github.com/jmrGrav/hugo-mcp/commit/1404f83" target="_blank" rel="noopener noreffer ">1404f83</a>.</p>]]></description>
</item>
<item>
    <title>Stratégie 4 : séparer le contenu (MCP) de la structure (Git)</title>
    <link>https://www.arleo.eu/posts/strategie-4-mcp-vs-git/</link>
    <pubDate>Sat, 09 May 2026 12:40:58 &#43;0200</pubDate>
    <author>Jmr</author>
    <guid>https://www.arleo.eu/posts/strategie-4-mcp-vs-git/</guid>
    <description><![CDATA[<div class="featured-image">
                <img src="/images/strategie-4-mcp-vs-git-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="le-problème">Le problème</h2>
<p>Tu as un site Hugo. Tu veux pouvoir :</p>
<ol>
<li><strong>Éditer le contenu via Claude.ai</strong> (publier un article, corriger une coquille, mettre à jour un draft) sans toucher à un terminal SSH.</li>
<li><strong>Versionner la structure</strong> (layouts, themes, hugo.toml, scripts de déploiement) dans Git, comme un dev sérieux.</li>
</ol>
<p>Premier réflexe : « tout dans Git ». Les articles aussi. Le MCP commit, push, le webhook GitHub fait un rebuild. Propre, dans la philosophie GitOps.</p>
<p>Sauf que ça ne marche pas si bien. Voici pourquoi, et la solution simple que j&rsquo;appelle la <strong>Stratégie 4</strong>.</p>
<h2 id="pourquoi--tout-dans-git--casse-en-pratique">Pourquoi « tout dans Git » casse en pratique</h2>
<p>Imaginons que ton MCP fait un <code>git commit</code> à chaque <code>create_page</code>. Stratégie naïve, mais souvent proposée. Voici les problèmes :</p>
<h3 id="problème-1--conflit-mcp--git">Problème 1 — Conflit MCP ↔ Git</h3>
<p>Tu push depuis ton laptop un nouveau layout (<code>layouts/index.html</code> modifié). Au même moment, le MCP est en train de commiter une nouvelle version d&rsquo;un article. Race condition, le MCP peut faire un <code>git pull --rebase</code> qui échoue, ou pire, écraser ton commit local.</p>
<h3 id="problème-2--identité-git-du-mcp">Problème 2 — Identité Git du MCP</h3>
<p>Le MCP commit en tant que qui ? Avec quelle clé GPG ? Si tu as une politique « commits signés obligatoires », le MCP doit gérer une clé GPG, qu&rsquo;il faut sécuriser, faire tourner, etc.</p>
<h3 id="problème-3--auto-commit-indésirable">Problème 3 — Auto-commit indésirable</h3>
<p>Tu fais un test, tu crées un article draft pour expérimenter, tu le supprimes. Mais le MCP a déjà commité. Maintenant tu as un commit &ldquo;wip test&rdquo; dans l&rsquo;historique, à rebaser ou squasher manuellement.</p>
<h3 id="problème-4--réversibilité-asymétrique">Problème 4 — Réversibilité asymétrique</h3>
<p>Un <code>git revert</code> côté repo n&rsquo;a aucun effet sur les fichiers que le MCP a déjà créés. Tu te retrouves avec un repo et un état filesystem désynchronisés.</p>
<h2 id="la-stratégie-4--séparer-les-zones">La Stratégie 4 : séparer les zones</h2>
<p>L&rsquo;idée : <strong>MCP et Git n&rsquo;écrivent jamais sur les mêmes fichiers</strong>.</p>
<table>
  <thead>
      <tr>
          <th>Zone</th>
          <th>Qui édite</th>
          <th>Versionné dans Git ?</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>content/**/*.md</code></td>
          <td><strong>MCP exclusivement</strong></td>
          <td>❌ NON (<code>.gitignore</code>)</td>
      </tr>
      <tr>
          <td><code>layouts/</code>, <code>themes/</code>, <code>static/</code>, <code>hugo.toml</code>, <code>deploy.sh</code></td>
          <td><strong>Git push exclusivement</strong></td>
          <td>✅ OUI</td>
      </tr>
  </tbody>
</table>
<p>Le <code>.gitignore</code> côté repo :</p>
<div class="code-block code-line-numbers open" data-start="0">
    <div class="code-header language-">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="Copier dans le presse-papiers"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><pre tabindex="0"><code>content/
public/
resources/</code></pre></div>
<p>Implications :</p>
<ul>
<li>Le MCP peut écrire dans <code>content/</code> quand il veut. Aucun conflit Git possible.</li>
<li>Tu peux faire <code>git reset --hard</code> côté repo en confiance — <code>content/</code> reste intact.</li>
<li>Pas besoin que le MCP gère une identité Git.</li>
<li>Pas de &ldquo;commit pollution&rdquo;.</li>
</ul>
<h2 id="trade-off--pas-de-versioning-du-contenu">Trade-off : pas de versioning du contenu</h2>
<p>Tu perds le versioning Git du contenu. C&rsquo;est une perte réelle :</p>
<ul>
<li>Pas de <code>git blame</code> sur un article pour voir qui a écrit quoi.</li>
<li>Pas de <code>git log content/csp-nonce/index.fr.md</code> pour voir l&rsquo;historique.</li>
<li>Pas de PR review pour les articles.</li>
</ul>
<p><strong>Mitigation</strong> : snapshots VM + backup <code>content/</code> chiffré quotidien sur QNAP. Ça couvre la <strong>récupération en cas de désastre</strong>, mais pas le versioning fin (qui-a-changé-quoi-quand).</p>
<p>Pour mon homelab perso (un seul auteur, articles techniques, pas de workflow de validation éditoriale), c&rsquo;est un trade-off acceptable. Pour un blog d&rsquo;équipe avec 10 contributeurs, je reconsidérerais.</p>
<h2 id="architecture-finale">Architecture finale</h2>]]></description>
</item>
</channel>
</rss>
