/images/avatar.png

This site runs on an Intel NUC hosted at home, behind a standard fiber connection. Its main purpose is to serve as an experimentation ground for testing server configurations, automation scripts, and open source security tools.

Not a professional website — a homelab: we break things, fix them, and learn.

🌟 Don’t miss

Three articles that capture the spirit of this homelab:

📚 Full documentation is in Documentation and automation scripts in Scripts.

🛠️ Tech stack

ToolRole
OpenRestyReverse proxy · nginx + LuaJIT · TLS 1.3
🔒 CrowdSecCommunity IDS/IPS
🧱 CrowdSec AppSecInline WAF · OWASP CRS 4.x
☁️ CloudflareCDN · WAF · DNS · DDoS
🌐 HugoStatic site generator
🤖 MCP Hugo ServerMCP server to drive this site from an AI
📊 BetterStackMonitoring · Alerts · Logs

🐛 Found a vulnerability?

If you discover a bug, misconfiguration, or security vulnerability on this server, please report it. This homelab is public and I learn from my mistakes.

📨 Responsible disclosure: www.arleo.eu/security.txt

Reports are publicly credited in the Hall of Fame, or kept anonymous on request.

Any contribution to improving security is welcome.

Migration in progress
The site is gradually migrating from Grav CMS to Hugo. Old URLs are preserved, but the visual rendering is evolving. If you spot a bug, report it.

Post-mortem: 3 MCP Timeouts — Cloudflare, NFS, OAuth

What happened
  • Date: May 9, 2026
  • Severity: P2
  • Status: Resolved
  • Impact: create_page/update_page intermittently timing out (~1 in 3 calls), caused by 3 independent bugs — OAuth expiry, IPAddressDeny blocking the Cloudflare call, QNAP NFS latency during backup

In short

I deployed a Hugo MCP Server (FastAPI, 7 tools) that lets me edit arleo.eu from Claude.ai. Architecture: claude.ai → mcp-oauth-proxy NUC → hugo-mcp-proxy NUC → MCP server VM.

Strategy 4: separating content (MCP) from structure (Git)

In short

You have a Hugo site. You want to:

  1. Edit content via Claude.ai (publish an article, fix a typo, update a draft) without touching an SSH terminal.
  2. Version the structure (layouts, themes, hugo.toml, deploy scripts) in Git, like a serious dev.

First instinct: “everything in Git”. Articles too. The MCP commits, pushes, GitHub webhook triggers a rebuild. Clean, GitOps-philosophy.

Except it doesn’t work that well. Here’s why, and the simple solution I call Strategy 4.

Why “everything in Git” breaks in practice

Imagine your MCP git commits on every create_page. Naive strategy, often suggested. Here are the problems:

Problem 1 — MCP ↔ Git conflict

You push a new layout from your laptop (layouts/index.html modified). At the same time, the MCP is committing a new article version. Race condition: the MCP might git pull --rebase and fail, or worse, overwrite your local commit.

Problem 2 — MCP’s Git identity

Whose commits is the MCP making? With which GPG key? If you have a “signed commits required” policy, the MCP needs to manage a GPG key, which has to be secured, rotated, etc.

Problem 3 — Unwanted auto-commits

You’re testing, you create a draft article to experiment, you delete it. But the MCP already committed. Now you have a “wip test” commit in history, to rebase or squash manually.

Problem 4 — Asymmetric reversibility

A git revert repo-side has no effect on files the MCP already created. You end up with a desynchronized repo and filesystem state.

Strategy 4: separate the zones

The idea: MCP and Git never write to the same files.

ZoneWho editsVersioned in Git?
content/**/*.mdMCP only❌ NO (.gitignore)
layouts/, themes/, static/, hugo.toml, deploy.shGit push only✅ YES

Repo-side .gitignore:

content/
public/
resources/

Implications:

  • The MCP can write to content/ whenever. No Git conflict possible.
  • You can git reset --hard repo-side with confidence — content/ stays intact.
  • No need for the MCP to manage a Git identity.
  • No “commit pollution”.

Trade-off: no content versioning

You lose Git versioning of content. That’s a real loss:

  • No git blame on an article to see who wrote what.
  • No git log content/csp-nonce/index.fr.md for history.
  • No PR review for articles.

Mitigation: VM snapshots + daily encrypted content/ backup to QNAP. That covers disaster recovery, but not fine-grained versioning (who-changed-what-when).

For my personal homelab (single author, technical articles, no editorial validation workflow), it’s an acceptable trade-off. For a 10-contributor team blog, I’d reconsider.

Final architecture

systemd hardening: taking a Python service from 9.6 to 1.7

In short

systemd-analyze security is an underused tool. It scans your unit files and computes an exposure score from 0.0 (UNSAFE) to 10.0 (PERFECT). Custom Python services often score around 9.6 by default — that’s bad.

I took my hugo-mcp service (FastAPI exposing 7 MCP tools) from 9.6 → 1.7 without breaking a single feature. Here are the directives that actually matter, and the ones that are traps.

Initial score

Migrating Grav → Hugo: 32 files, 0 regression

TL;DR

arleo.eu had been running on Grav CMS for ~3 years: flat-file, PHP-FPM, ModSecurity, Cloudflare. Everything worked. But operational debt was piling up: PHP upgrades, Grav plugins to patch, TTFB creeping past 800ms on some pages.

I migrated to Hugo (Go-based static site generator) in two weeks, keeping the same URLs, the same content, and both languages FR/EN. Zero regression on SEO, Cloudflare indexing, or internal links. Here’s how.

Why Hugo

I had 3 criteria:

  1. Performance — Pure static. No PHP, no DB. nginx serves pre-generated .html directly.
  2. Security — Attack surface divided by 10. No more PHP-FPM, no server-side execution on public pages.
  3. Clean multilingual — Hugo natively supports i18n via the index.{lang}.md convention (page bundles).

Hugo checks all the boxes. Alternatives evaluated: Eleventy (JS, but less mature i18n), Zola (Rust, awesome, but fewer themes), Gatsby (too heavy for a homelab).

Hugo MCP Server: Connecting Claude.ai to a Static Hugo Site

In short

Connect Claude.ai to a Hugo site hosted in a KVM VM in 30 minutes: a FastAPI server exposes 6 MCP tools (read, create, modify, delete pages, rebuild the site) via JSON-RPC 2.0, an OAuth proxy reuses existing infrastructure, and every modification automatically triggers a Hugo rebuild + Cloudflare cache purge.

The code is available on GitHub:

🧠 Why

Anthropic’s MCP (Model Context Protocol) allows Claude.ai to connect to external data sources via standardized tools. Unlike Grav CMS which is dynamic (PHP), Hugo generates pure static HTML — making content management via MCP even more powerful: every modification is compiled and deployed instantly.

KVM Media VM: Isolating Sonarr, Radarr and SABnzbd

In short

Migrating the media stack (Sonarr, Radarr, SABnzbd) into a dedicated KVM VM running Ubuntu 24.04: security isolation, easy snapshots, QNAP NFS mounted inside the VM, nginx reverse proxy on the host. The migration fully preserves SQLite databases and existing configuration.

Target stack:

  • Host: NUC8i3BEH, Ubuntu Server, nginx reverse proxy, Plex (GPU transcoding)
  • VM media-vm: Ubuntu 24.04, 2 vCPU, 8 GB RAM, 120 GB (X5 NVMe), QNAP NFS
  • Migrated services: Sonarr (port 8989), Radarr (port 7878), SABnzbd (port 6789)

Why isolate the media stack in a VM

Sonarr, Radarr and SABnzbd present a significant attack surface: network calls to external indexers, post-download script execution, filesystem access to the media library. Confining them in a VM provides:

Hugo