Contenu

VM Media sur KVM : migrer Sonarr, Radarr et SABnzbd dans une VM isolée

⚡ En bref

Migrer son media stack (Sonarr, Radarr, SABnzbd) dans une VM KVM dédiée sur Ubuntu 24.04 : isolation sécurité, snapshots facilités, NFS QNAP monté dans la VM, reverse proxy nginx sur l’hôte. La migration conserve intégralement les bases de données SQLite et la configuration existante.

Stack cible :

  • 🖥️ Hôte : NUC8i3BEH, Ubuntu Server, nginx reverse proxy, Plex (transcodage GPU)
  • 📦 VM media-vm : Ubuntu 24.04, 2 vCPU, 8 Go RAM, 120 Go (X5 NVMe), NFS QNAP
  • 🎬 Services migrés : Sonarr (port 8989), Radarr (port 7878), SABnzbd (port 6789)

🧠 Pourquoi isoler le media stack dans une VM

Sonarr, Radarr et SABnzbd présentent une surface d’attaque non négligeable : accès réseau vers des indexeurs externes, exécution de scripts post-download, accès au système de fichiers de la bibliothèque. Les confiner dans une VM apporte :

  • Isolation sécurité — un compromis de Sonarr ne peut pas atteindre l’hôte directement
  • Snapshots avant mise à jour — via virsh snapshot-create-as, rollback en 30 secondes si une mise à jour casse la base SQLite
  • Plex reste sur l’hôte — accès au GPU Intel (/dev/dri) pour le transcodage matériel, non virtualisable simplement

Le NFS QNAP étant déjà monté sur l’hôte, les téléchargements transitent déjà par le réseau — la VM ne change rien à ce comportement, elle monte les mêmes partages directement.

🔧 Étape 1 — Préparer le pool libvirt sur le X5

Le disque virtuel est stocké sur le SSD NVMe X5 (exFAT), via une image loop ext4. fallocate n’est pas supporté sur exFAT — utiliser dd à la place.

# Créer l'image de 150 Go
dd if=/dev/zero of=/mnt/X5/libvirt-media.img bs=1G count=150 status=progress

# Formater en ext4
mkfs.ext4 /mnt/X5/libvirt-media.img

# Monter
mkdir -p /mnt/libvirt-media
mount -o loop /mnt/X5/libvirt-media.img /mnt/libvirt-media

# Persistance au boot
echo '/mnt/X5/libvirt-media.img /mnt/libvirt-media ext4 loop 0 0' >> /etc/fstab
systemctl daemon-reload

# Déclarer le pool libvirt
virsh pool-define-as media-pool dir --target /mnt/libvirt-media
virsh pool-autostart media-pool
virsh pool-start media-pool

🔧 Étape 2 — Télécharger l’image cloud Ubuntu 24.04

On utilise une image cloud Ubuntu (pas une ISO live) pour que cloud-init applique la configuration au premier boot sans installateur interactif.

wget -P /mnt/libvirt-media \
  https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img

qemu-img resize /mnt/libvirt-media/noble-server-cloudimg-amd64.img 120G

🔧 Étape 3 — Créer l’image cloud-init

Cloud-init configure l’utilisateur, le mot de passe et les clés SSH au premier boot. Les fichiers doivent s’appeler exactement user-data et meta-data — sans préfixe — sinon cloud-init ne les détecte pas.

⚠️ Piège fréquent : toujours inclure deux clés SSH dans authorized_keys — la clé du laptop ET la clé NUC→VM. Sans la clé NUC→VM, la connexion SSH depuis l’hôte est impossible.

cat > /tmp/user-data <<'EOF'
#cloud-config
hostname: media-vm
users:
  - name: jm
    sudo: ALL=(ALL) NOPASSWD:ALL
    shell: /bin/bash
    lock_passwd: false
    passwd: $VOTRE_HASH$...
    ssh_authorized_keys:
      - ssh-ed25519 AAAA... cle-laptop
      - ssh-ed25519 AAAA... cle-nuc-to-vm
chpasswd:
  expire: false
ssh_pwauth: true
package_update: true
packages:
  - curl
  - wget
  - nfs-common
runcmd:
  - systemctl enable nfs-client.target
instance-id: media-vm-001
local-hostname: media-vm
EOF

cat > /tmp/meta-data <<'EOF'
instance-id: media-vm-001
local-hostname: media-vm
EOF

genisoimage -output /mnt/libvirt-media/media-vm-cloud-init.img \
  -volid cidata -joliet -rock \
  /tmp/user-data /tmp/meta-data

🔧 Étape 4 — Créer la VM

virt-install \
  --name media-vm \
  --ram 8192 \
  --vcpus 2 \
  --disk path=/mnt/libvirt-media/noble-server-cloudimg-amd64.img,format=qcow2 \
  --disk path=/mnt/libvirt-media/media-vm-cloud-init.img,device=cdrom \
  --os-variant ubuntu24.04 \
  --network network=default \
  --graphics none \
  --noautoconsole \
  --import

virsh autostart media-vm
virsh net-dhcp-leases default
ssh -i ~/.ssh/cle-nuc-to-vm jm@192.168.122.X

🔧 Étape 5 — Monter le NFS QNAP dans la VM

sudo mkdir -p /mnt/Animes /mnt/Series /mnt/Films

sudo tee -a /etc/fstab <<'EOF'
192.168.1.11:/Animes /mnt/Animes nfs defaults,auto,vers=4.1,rw,nofail,bg,intr,rsize=262144,wsize=262144,noatime,_netdev 0 0
192.168.1.11:/Series /mnt/Series nfs defaults,auto,vers=4.1,rw,nofail,bg,intr,rsize=262144,wsize=262144,noatime,_netdev 0 0
192.168.1.11:/Films /mnt/Films nfs defaults,auto,vers=4.1,rw,nofail,bg,intr,rsize=262144,wsize=262144,noatime,_netdev 0 0
EOF

sudo systemctl daemon-reload && sudo mount -a

🔧 Étape 6 — Installer Sonarr, Radarr, SABnzbd

🎬 Sonarr

curl -o- https://raw.githubusercontent.com/Sonarr/Sonarr/develop/distribution/debian/install.sh | sudo bash
# User: jm / Group: jm

🎬 Radarr

curl -o servarr-install-script.sh \
  https://raw.githubusercontent.com/Servarr/Wiki/master/servarr/servarr-install-script.sh
sudo bash servarr-install-script.sh
# Choisir 3 (Radarr) — User: jm / Group: jm

📥 SABnzbd

sudo apt install -y sabnzbdplus
# Éditer /etc/default/sabnzbdplus : USER=jm
sudo systemctl restart sabnzbdplus

⚙️ Écoute sur toutes les interfaces

Par défaut, Sonarr et Radarr écoutent sur 127.0.0.1 uniquement. Modifier config.xml :

sudo systemctl stop sonarr radarr
# Dans /var/lib/sonarr/config.xml et /var/lib/radarr/config.xml :
# <BindAddress>0.0.0.0</BindAddress>
sudo systemctl start sonarr radarr

Même chose pour SABnzbd dans /home/jm/.sabnzbd/sabnzbd.ini :

host = 0.0.0.0
host_whitelist = media-vm, sabnzbd.arleo.eu,

🔧 Étape 7 — Migrer les données

# Sur l'hôte
sudo systemctl stop sonarr radarr sabnzbdplus
scp -i ~/.ssh/cle-nuc-to-vm -r /mnt/X5/config/sonarr jm@192.168.122.X:/tmp/
scp -i ~/.ssh/cle-nuc-to-vm -r /mnt/X5/config/radarr jm@192.168.122.X:/tmp/
scp -i ~/.ssh/cle-nuc-to-vm -r /mnt/X5/config/sabnzbd jm@192.168.122.X:/tmp/
# Dans la VM
sudo systemctl stop sonarr radarr sabnzbdplus
sudo rm -rf /var/lib/sonarr /var/lib/radarr /home/jm/.sabnzbd
sudo cp -r /tmp/sonarr /var/lib/sonarr
sudo cp -r /tmp/radarr /var/lib/radarr
sudo cp -r /tmp/sabnzbd /home/jm/.sabnzbd
sudo chown -R jm:jm /var/lib/sonarr /var/lib/radarr /home/jm/.sabnzbd
sudo systemctl start sonarr radarr sabnzbdplus

# Sur l'hôte — désactiver les anciens services
sudo systemctl disable --now sonarr radarr sabnzbdplus

🔧 Étape 8 — Reverse proxy nginx sur l’hôte

La VM est sur le réseau NAT libvirt 192.168.122.0/24. Mettre à jour les vhosts existants :

sed -i 's/http:\/\/127\.0\.0\.1:8989/http:\/\/192.168.122.X:8989/g' \
  /etc/nginx/sites-enabled/sonarr.arleo.eu
sed -i 's/http:\/\/127\.0\.0\.1:7878/http:\/\/192.168.122.X:7878/g' \
  /etc/nginx/sites-enabled/radarr.arleo.eu
nginx -t && systemctl reload nginx

🏁 Résultat

Le media stack tourne dans une VM isolée, avec accès complet aux partages NFS QNAP. Sonarr et Radarr voient la bibliothèque intacte et SABnzbd reçoit les téléchargements sur le disque local de la VM avant import.

ServiceURLPort interne
Sonarrhttps://sonarr.arleo.eu192.168.122.X:8989
Radarrhttps://radarr.arleo.eu192.168.122.X:7878
SABnzbdhttps://sabnzbd.arleo.eu192.168.122.X:6789

Points de vigilance :

  • 💡 Inclure deux clés SSH dans cloud-init (laptop + NUC→VM)
  • 💡 Les fichiers cloud-init doivent s’appeler exactement user-data et meta-data
  • 💡 fallocate ne fonctionne pas sur exFAT — utiliser dd
  • 💡 Sonarr/Radarr écoutent sur 127.0.0.1 par défaut — modifier BindAddress dans config.xml
  • 💡 Ajouter le hostname du vhost dans host_whitelist de SABnzbd