Sélectionner une page
Synchroniser Obsidian entre PC et Android avec Nextcloud

Synchroniser Obsidian entre PC et Android avec Nextcloud

Tu veux consulter et modifier ton coffre Obsidian aussi bien sur ton ordinateur que sur ton téléphone, sans payer d’abonnement ni confier tes notes à un serveur tiers inconnu. Voici comment construire cette synchronisation avec un Nextcloud auto-hébergé, en évitant les pièges que j’ai moi-même rencontrés en cours de route.

Le contexte : un coffre de veille, accessible partout

Ce projet concerne un coffre Obsidian dédié à la veille professionnelle : des liens, des extraits, des notes de lecture, à consulter et enrichir aussi bien devant ton ordinateur que dans les transports avec ton téléphone. Contrairement à d’autres coffres qui restent sur PC, celui-ci a une contrainte supplémentaire : l’accès mobile est indispensable.

L’architecture retenue

Le principe : un seul mécanisme de synchronisation actif par appareil, jamais deux en parallèle sur le même dossier. C’est une règle que j’ai apprise à mes dépens en laissant tourner par erreur une synchronisation Google Drive et le plugin Remotely Save simultanément sur le même coffre, un scénario à risque de doublons et de conflits de fichiers.

AppareilMécanisme
PC (Windows)Client officiel Nextcloud, synchronisation au niveau du système de fichiers
AndroidObsidian + plugin Remotely Save, connexion WebDAV directe à Nextcloud

Sur PC, Obsidian n’ouvre qu’un dossier local classique, sans aucun plugin de synchronisation. Sur mobile, Remotely Save devient nécessaire parce qu’Android ne permet pas à Obsidian d’accéder en arrière-plan à un dossier synchronisé par une autre application.

Étape 1 : installer Nextcloud Hub chez un hébergeur

La plupart des hébergeurs web proposent un installeur applicatif en un clic pour Nextcloud. Voici la démarche générale (les noms d’écran peuvent varier légèrement selon l’hébergeur) :

  1. Dans le panneau d’administration de ton hébergement, ajoute un nouveau site
  2. Choisis un sous-domaine dédié, par exemple cloud.tondomaine.com
  3. Si ton nom de domaine est géré ailleurs que chez ton hébergeur (registrar différent), crée les enregistrements DNS nécessaires (type A et AAAA) pointant vers l’adresse fournie par l’hébergeur
  4. Dans le catalogue d’applications, sélectionne Nextcloud Hub
  5. Renseigne un nom d’utilisateur et un mot de passe administrateur robustes, à conserver dans ton gestionnaire de mots de passe
  6. Laisse la mise à jour automatique sur « mises à jour mineures et sécurité », un bon compromis entre stabilité et protection
  7. Une fois l’installation terminée, active un certificat HTTPS gratuit (Let’s Encrypt) pour sécuriser les échanges

Choisir un nom de sous-domaine discret

Plutôt qu’un nom explicite comme nextcloud.tondomaine.com, qui annonce directement la nature du service à quiconque le croise, un nom neutre (cloud., espace., docs.) apporte un léger renfort de sécurité par obscurité, en complément de l’authentification.

Préparer l’arborescence de dossiers

Avant de connecter quoi que ce soit, crée une arborescence dédiée dans Nextcloud, par exemple un dossier parent OBSIDIAN-sync contenant un sous-dossier par coffre :

/OBSIDIAN-sync/veille-2026

Ça garde tes coffres regroupés et lisibles, même si tu en ajoutes d’autres plus tard.

Étape 2 : connecter le PC via le client Nextcloud officiel

Télécharge le client officiel Nextcloud pour Windows depuis le site nextcloud.com.

À la connexion, un point de vigilance : le champ d’adresse serveur attend uniquement la racine du site, sans aucun chemin après :

https://cloud.tondomaine.com

Une fois connecté, configure la synchronisation du dossier OBSIDIAN-sync vers un dossier local dédié, par exemple D:\NextCloud\OBSIDIAN-sync. Le sous-dossier veille-2026 apparaîtra automatiquement dedans une fois la synchronisation active.

Dans Obsidian, choisis « Ouvrir un dossier existant », et pointe vers ce sous-dossier local. Répète l’opération sur chaque PC que tu utilises.

Étape 3 : connecter le téléphone avec Remotely Save

Sur Android, la logique est différente : Obsidian ne peut pas s’appuyer sur un dossier synchronisé en arrière-plan par une autre application, il faut donc un plugin de synchronisation intégré.

  1. Installe Obsidian depuis le Play Store, crée un coffre vide
  2. Va dans Paramètres > Plugins communautaires, recherche et installe Remotely Save, puis active-le
  3. Dans les réglages du plugin, choisis la méthode WebDAV

Trouver l’adresse WebDAV

L’adresse WebDAV de Nextcloud suit ce format, avec ton identifiant de connexion à la fin (pas ton adresse e-mail) :

https://cloud.tondomaine.com/remote.php/dav/files/USER

Comme ce champ ne gère qu’un seul niveau de dossier via le réglage dédié (voir plus bas), le plus simple est d’inclure le dossier parent directement dans l’adresse serveur :

https://cloud.tondomaine.com/remote.php/dav/files/USER/OBSIDIAN-sync

Renseigne ensuite ton nom d’utilisateur et ton mot de passe Nextcloud. Si une erreur 403 apparaît lors du test de connexion, deux causes fréquentes :

  • L’adresse serveur ne contient pas ton identifiant, une erreur facile à faire en copiant une adresse générique
  • Une double authentification est activée sur ton compte Nextcloud, auquel cas il faut générer un mot de passe d’application dédié plutôt que d’utiliser ton mot de passe principal

Définir le dossier distant précis

Une fois la connexion établie, utilise le champ « Change The Remote Base Directory » pour préciser le sous-dossier exact du coffre, par exemple :

veille-2026

Ce champ n’accepte qu’un nom simple, pas un chemin avec plusieurs niveaux (le / y est refusé), d’où l’astuce d’inclure le dossier parent directement dans l’adresse serveur, comme montré plus haut.

Les réserves de sécurité à connaître sur Remotely Save

Même si Remotely Save est un choix plus rassurant que le plugin écarté au départ (pas de serveur intermédiaire propriétaire pour la connexion WebDAV, tes identifiants vont directement à ton propre Nextcloud), il mérite deux réserves.

Pas de chiffrement de bout en bout par défaut. Sans mot de passe de chiffrement configuré dans le plugin, tes notes sont stockées en clair sur le serveur Nextcloud, lisibles par quiconque y aurait un accès (administrateur du serveur, faille de sécurité éventuelle). Le protocole HTTPS protège le trajet des données, pas leur stockage.

Gestion des conflits limitée en version gratuite. Sur cette version du plugin, les seules options gratuites de résolution de conflits sont « la version la plus récente écrase l’autre » ou « la version la plus volumineuse écrase l’autre ». Il n’existe pas d’option gratuite pour créer automatiquement un fichier de conflit séparé et comparer les deux versions, cette fonctionnalité relève de l’offre payante.

Ce que ça signifie concrètement

Pour un coffre de veille, avec des liens et des notes de lecture sans caractère confidentiel, ces limites sont acceptables : la perte occasionnelle d’une modification récente en cas de conflit reste un inconvénient mineur, largement compensé par le filet de sécurité de la fonction native « Historique des fichiers » (voir plus bas).

Pour un coffre contenant des documents de travail sensibles, ces mêmes limites deviennent plus problématiques. Dans ce cas, active le chiffrement de bout en bout dans les réglages du plugin (champ « Encryption Password »), avec pour conséquence que le contenu synchronisé ne sera plus lisible directement depuis l’interface web de Nextcloud, ni exploitable tel quel par un script ou une IA sans déchiffrement préalable.

Gérer les conflits et les pertes accidentelles

En complément de Remotely Save, active la fonction native d’Obsidian, indépendante de tout cloud :

  1. Va dans Paramètres > Modules principaux
  2. Active « Récupération de fichier » (File Recovery)
  3. Réduis l’intervalle de capture à environ 2 minutes

En cas de fausse manipulation ou d’écrasement par la synchronisation, retourne dans Paramètres > Historique des fichiers > Afficher pour restaurer une version locale enregistrée par Obsidian dans son propre cache, indépendamment de ce qui s’est passé côté serveur.

Pour aller plus loin

Si ce projet t’intéresse, deux articles complémentaires détaillent la suite :

  • La mise en place d’une sauvegarde périodique automatisée avec rclone, pour ne pas dépendre uniquement de la synchronisation en temps réel : Automatiser des sauvegardes cloud à cloud avec rclone
  • L’organisation d’un coffre Obsidian dédié à l’écriture longue, avec Templater, Dataview et Longform
Google Drive sur PC : comment synchroniser (sans tout mélanger)

Google Drive sur PC : comment synchroniser (sans tout mélanger)

L’application Google Drive pour ordinateur propose deux mécanismes de synchronisation complètement différents, logés dans le même menu. Ne pas les distinguer, c’est la garantie de ne plus rien comprendre à ce qui se synchronise, où, et pourquoi. Voici comment les différencier et les utiliser correctement.

Deux outils dans une seule application

Quand tu ouvres les préférences de Google Drive, la colonne de gauche affiche deux sections qui se ressemblent, mais qui n’ont rien à voir l’une avec l’autre.

A. « Google Drive / Mon Drive » (le mode cloud natif)

C’est l’espace central. Tout ce qui réside sur ton compte Google Web est répercuté sur ton ordinateur sous forme d’un dossier virtuel (par exemple D:\Google Drive\Mon Drive).

Usage idéal : synchroniser tes fichiers de travail entre plusieurs ordinateurs (PC fixe et PC portable).

Fonctionnement : tout ajout ou modification dans ce dossier est instantanément répercuté dans le cloud et sur tes autres appareils.

B. « Mon ordinateur » (le mode sauvegarde locale)

Cette option s’adresse à ceux qui veulent sauvegarder des dossiers existants sans changer leur arborescence Windows (par exemple le dossier Documents ou Images de Windows).

Usage idéal : créer une sauvegarde en arrière-plan d’un répertoire local spécifique.

Dans le cloud, ces dossiers n’apparaissent pas dans « Mon Drive », mais sous un onglet séparé nommé « Ordinateurs » sur le web.

L’erreur à éviter

N’utilise jamais la fonction « Ajouter un dossier » (dans la section Mon ordinateur) sur un dossier qui se trouve déjà dans ton dossier Mon Drive. Tu demanderais à Google Drive de synchroniser deux fois le même emplacement via deux circuits différents. Résultat : les deux mécanismes se marchent dessus, plus rien ne se synchronise comme prévu, et tu perds toute visibilité sur ce qui se passe réellement.

Streaming vs miroir : économiser l’espace de son disque dur

Dans la section Google Drive des préférences, tu dois choisir comment les fichiers de Mon Drive sont stockés physiquement sur ta machine.

ModeMise en cache et stockageAvantages et inconvénients
Accès en ligne (streaming) – recommandéLes fichiers restent hébergés sur le cloud. Seuls des raccourcis virtuels sont visibles dans l’Explorateur Windows.+ Économise l’espace disque / SSD. + Ouverture transparente des fichiers à la demande. – Nécessite une connexion Internet par défaut.
Dupliquer les fichiers (miroir)Tous les fichiers du cloud sont téléchargés et doublés en local sur ton disque dur.+ Tous les fichiers sont accessibles hors ligne. – Saturation rapide des disques durs / SSD de petite taille.

Guide pratique : rendre des dossiers disponibles hors ligne

Le mode streaming est le plus optimisé. Si tu pars en déplacement sans connexion Internet, voici comment préparer tes dossiers :

  1. Ouvre l’Explorateur de fichiers Windows et navigue dans ton dossier Mon Drive.
  2. Fais un clic droit sur le dossier ou le fichier dont tu auras besoin.
  3. Sélectionne « Accès hors connexion » puis « Disponible hors connexion ».
  4. L’application télécharge une copie locale sécurisée.

Une pastille verte avec une coche confirme la disponibilité hors ligne, tandis qu’une icône de nuage bleu indique un fichier stocké uniquement en ligne.

Dépannage : résoudre les blocages et icônes rouges

Si la synchronisation se bloque ou si un dossier affiche une croix rouge :

  1. Fermer les applications : un fichier ouvert dans Word, Excel ou un lecteur PDF verrouille la synchronisation. Ferme l’application concernée.
  2. Vérifier les caractères spéciaux : évite les caractères interdits par Windows dans les noms de fichiers (/ \ ? * : " < > |).
  3. Réinitialisation propre : si les icônes de statut ne s’affichent plus, va dans Préférences de l’application > Paramètres > Déconnecter le compte, puis reconnecte-toi. Cela re-scanne la base de données sans retélécharger tes données déjà présentes.

Checklist récapitulative

  • Un seul point d’entrée : travaille exclusivement dans le dossier Mon Drive.
  • Espace disque : active le mode streaming pour ne pas surcharger ton SSD.
  • Déplacements : utilise le clic droit « Disponible hors connexion » de manière ciblée.
  • Sécurité : laisse la section « Mon ordinateur » vide, sauf besoin explicite de sauvegarde locale.

Sauvegarde Docker vers Google Drive avec rclone

Sauvegarde Docker vers Google Drive avec rclone

Une sauvegarde nocturne qui tournait en quelques minutes s’est mise à durer plus de 7 heures, avec des erreurs de quota Google. Voici le diagnostic et la correction : remplacer des milliers de petits fichiers par une seule archive compressée.

Cet article s’inscrit dans les séries projets Ubuntu et Domotique Home Assistant, et fait suite à l’article Sauvegarder ses containers Docker automatiquement avec rclone.

Le symptôme

La sauvegarde nocturne d’un dossier Docker (une dizaine de containers, environ 1,9 Go au total) copiait chaque nuit l’intégralité du dossier vers Google Drive, avec une rétention GFS (Grand-Père / Père / Fils, une stratégie classique qui conserve des sauvegardes quotidiennes, hebdomadaires, mensuelles et annuelles). Cette sauvegarde aurait dû se faire en quelques minutes. Un jour, elle a mis plus de 7 heures à se terminer, sans plantage visible, juste un processus qui semblait ne plus avancer.

Diagnostiquer un dépassement de quota API

Premier réflexe : regarder le fichier de log. Il s’arrêtait net, sans nouvelle ligne depuis des heures. Le processus rclone tournait pourtant toujours (ps aux | grep rclone le confirmait), mais avec un temps CPU (temps de calcul processeur réellement utilisé) dérisoire par rapport à sa durée de vie, signe qu’il passait le plus clair de son temps à attendre.

Relancer une simple commande de lecture a permis de voir l’erreur clairement :

rclone size gdrive:/daily
googleapi: Error 403: Quota exceeded for quota metric 'Queries' and limit 'Queries per minute' of service 'drive.googleapis.com'

Le quota en cause n’est pas l’espace de stockage, mais le nombre de requêtes envoyées à l’API (interface de programmation, le canal par lequel un logiciel comme rclone communique avec les serveurs de Google) Google Drive en une minute. Chaque fichier transféré, vérifié ou supprimé individuellement génère plusieurs requêtes. Un compte pour voir venir le problème :

find /home/USER/docker -type f | wc -l

Résultat : plus de 10 000 fichiers, dans un seul dossier quotidien. Multiplié par une rétention de 7 jours (plus les copies hebdomadaires, mensuelles et annuelles), le script devait gérer chaque nuit plusieurs dizaines de milliers d’opérations individuelles sur l’API. Ce n’est pas le volume de données (1,9 Go, rien d’énorme) qui posait problème, c’est le nombre d’objets.

L’effet de bord du sudo sur la configuration rclone

Un deuxième problème s’est ajouté en cours de route : le fichier de configuration rclone (rclone.conf) changeait régulièrement de propriétaire, passant de l’utilisateur normal à root, ce qui bloquait toute utilisation manuelle de rclone en dehors du script.

La cause : le script utilisait sudo rclone pour contourner des permissions sur certains fichiers Docker appartenant à root. Mais lancer rclone entièrement en root a un effet de bord : le jeton d’accès Google expire régulièrement (environ toutes les heures), et rclone le renouvelle automatiquement en réécrivant le fichier de configuration. Réécrit par un processus root, ce fichier devient la propriété de root, et l’utilisateur normal perd l’accès à sa propre configuration.

La correction : ne plus jamais faire tourner rclone lui-même avec sudo. Seule la commande qui a besoin de lire les fichiers appartenant à root (tar, dans la solution ci-dessous) utilise sudo, sans aucun rapport avec la configuration rclone.

La solution : une archive compressée plutôt que des milliers de fichiers

Plutôt que de copier des milliers de fichiers un par un, le script compresse désormais l’intégralité du dossier en une seule archive avant l’envoi. Une archive à transférer, c’est une poignée de requêtes API au lieu de dizaines de milliers.

La logique GFS reste identique, mais la rotation hebdomadaire, mensuelle et annuelle se fait par copie du fichier archive directement entre deux emplacements sur Google Drive (côté serveur, sans repasser par le Mini PC), plutôt que par un nouveau balayage de milliers de fichiers.

Le nettoyage des sauvegardes obsolètes change aussi de nature : supprimer un fichier unique par jour dépassé, au lieu de parcourir et supprimer des milliers de fichiers individuellement.

Le script complet

#!/bin/bash

# ═══════════════════════════════════════════════════════════════
# backup-docker.sh — Sauvegarde Docker vers Google Drive
# v5 - archive unique compressée (fin du problème de quota API)
#      export automatique des YAML depuis l'API Portainer
# ═══════════════════════════════════════════════════════════════

# -- Configuration ────────────────────────────────────────────────
SOURCE="/home/USER/docker"
REMOTE="gdrive:"
LOG="/home/USER/rclone-logs/backup-docker.log"
RCLONE_CONF="/home/USER/.config/rclone/rclone.conf"
PORTAINER_URL="https://192.168.1.100:9443"
PORTAINER_TOKEN=$(cat /home/USER/docker/scripts/.portainer-token)
DATE=$(date +%Y-%m-%d)
DAY=$(date +%u)     # 1=lundi ... 7=dimanche
DOM=$(date +%d)     # jour du mois (01-31)
MONTH=$(date +%m)   # mois (01-12)

ARCHIVE_NAME="docker-$DATE.tar.gz"
TMP_ARCHIVE="/tmp/$ARCHIVE_NAME"

# -- Création du dossier de log si nécessaire ─────────────────────
mkdir -p /home/USER/rclone-logs

# -- Fonction de log ──────────────────────────────────────────────
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG"
}

# -- Commande rclone de base ──────────────────────────────────────
# Important : plus de sudo ici. rclone tourne toujours en tant que
# l'utilisateur normal, son fichier de config n'est donc plus
# jamais réécrit par root. Seul tar (plus bas) a besoin de sudo
# pour lire les fichiers appartenant à root dans les volumes
# Docker.
RCLONE="rclone --config $RCLONE_CONF --log-file=$LOG"

log "=== Début sauvegarde ==="

# -- Export automatique des YAML (format de fichier de configuration
# lisible, utilisé par Docker Compose) depuis l'API Portainer ─────
log "Export des YAML depuis Portainer..."

STACKS_JSON=$(curl -sk \
    -H "X-API-Key: $PORTAINER_TOKEN" \
    "$PORTAINER_URL/api/stacks")

if [ -z "$STACKS_JSON" ] || [ "$STACKS_JSON" = "null" ]; then
    log "ERREUR : impossible de contacter l'API Portainer - les YAML ne seront pas mis à jour."
else
    STACK_COUNT=$(echo "$STACKS_JSON" | python3 -c "
import sys, json
stacks = json.load(sys.stdin)
print(len(stacks))
" 2>/dev/null)

    log "$STACK_COUNT stack(s) trouvée(s) dans Portainer."

    echo "$STACKS_JSON" | python3 -c "
import sys, json
stacks = json.load(sys.stdin)
for s in stacks:
    print(s['Id'], s['Name'])
" 2>/dev/null | while read stack_id stack_name; do

        YAML_CONTENT=$(curl -sk \
            -H "X-API-Key: $PORTAINER_TOKEN" \
            "$PORTAINER_URL/api/stacks/$stack_id/file" \
            | python3 -c "
import sys, json
data = json.load(sys.stdin)
print(data.get('StackFileContent', ''))
" 2>/dev/null)

        if [ -n "$YAML_CONTENT" ]; then
            DEST="$SOURCE/$stack_name/docker-compose.yml"
            mkdir -p "$SOURCE/$stack_name"
            echo "$YAML_CONTENT" > "$DEST"
            log "YAML exporté : $stack_name/docker-compose.yml"
        else
            log "ATTENTION : YAML vide ou inaccessible pour la stack $stack_name"
        fi
    done
fi

# -- Vérification des docker-compose.yml manquants ────────────────
# Seuls les containers gérés comme des stacks Portainer ont un
# docker-compose.yml généré automatiquement ci-dessus. Certains
# dossiers ne sont pas des containers et doivent être exclus de
# cette vérification.
log "Vérification des docker-compose.yml..."
for dir in "$SOURCE"/*/; do
    logiciel=$(basename "$dir")
    # "scripts" : pas un container Docker
    # à adapter : tout dossier qui n'est pas une stack Portainer
    # (par exemple un environnement virtuel Python) doit être
    # ajouté ici pour éviter une fausse alerte à chaque exécution
    if [ "$logiciel" = "scripts" ]; then
        continue
    fi
    compose="$dir/docker-compose.yml"
    if [ ! -f "$compose" ]; then
        log "ATTENTION : docker-compose.yml manquant dans $logiciel - à créer manuellement !"
    fi
done

# -- Création de l'archive compressée ─────────────────────────────
# sudo est nécessaire ici car certains fichiers dans les volumes
# Docker appartiennent à root. tar n'a aucun rapport avec la config
# rclone, donc rclone.conf n'est jamais impacté par ce sudo.
log "Création de l'archive $ARCHIVE_NAME..."
sudo tar --exclude="*.db-shm" --exclude="*.db-wal" -czf "$TMP_ARCHIVE" -C "$SOURCE" .
sudo chown USER:USER "$TMP_ARCHIVE"
log "Archive créée : $(du -h "$TMP_ARCHIVE" | cut -f1)"

# -- Envoi de l'archive vers Google Drive (sauvegarde quotidienne) ─
log "Copie vers daily/..."
$RCLONE copy "$TMP_ARCHIVE" "$REMOTE/daily/"

# -- Copie hebdomadaire (chaque dimanche) ──────────────────────────
# Copie côté serveur, aucun ré-upload depuis le poste local
if [ "$DAY" -eq 7 ]; then
    log "Dimanche : copie hebdomadaire"
    $RCLONE copyto "$REMOTE/daily/$ARCHIVE_NAME" "$REMOTE/weekly/$ARCHIVE_NAME"
fi

# -- Copie mensuelle (le 1er de chaque mois) ───────────────────────
if [ "$DOM" -eq 01 ]; then
    log "1er du mois : copie mensuelle"
    $RCLONE copyto "$REMOTE/daily/$ARCHIVE_NAME" "$REMOTE/monthly/$ARCHIVE_NAME"
fi

# -- Copie annuelle (le 1er janvier) ───────────────────────────────
if [ "$DOM" -eq 01 ] && [ "$MONTH" -eq 01 ]; then
    log "1er janvier : copie annuelle"
    $RCLONE copyto "$REMOTE/daily/$ARCHIVE_NAME" "$REMOTE/yearly/$ARCHIVE_NAME"
fi

# -- Nettoyage du fichier temporaire local ─────────────────────────
rm -f "$TMP_ARCHIVE"

# -- Nettoyage des anciennes sauvegardes sur Drive ─────────────────
# Suppression de fichiers uniques, plus de parcours fichier par fichier
log "Nettoyage daily > 7 jours..."
$RCLONE delete "$REMOTE/daily" --min-age 7d --include "*.tar.gz"

log "Nettoyage weekly > 28 jours..."
$RCLONE delete "$REMOTE/weekly" --min-age 28d --include "*.tar.gz"

log "Nettoyage monthly > 365 jours..."
$RCLONE delete "$REMOTE/monthly" --min-age 365d --include "*.tar.gz"

# Annuelles : conservées indéfiniment, pas de nettoyage automatique

log "=== Sauvegarde terminée ==="

Résultat mesuré après cette réécriture : script complet exécuté en 8 minutes, contre plus de 7 heures avant correction.

Un point de vigilance si tu reprends ce script : les fichiers docker-compose.yml gérés par Portainer sont écrasés chaque nuit avec la version stockée dans Portainer. Toute modification doit se faire dans l’interface Portainer, jamais directement dans le fichier local, sous peine d’être effacée à la prochaine exécution.

Un point à ne pas manquer au déploiement : ce sudo tar doit être explicitement autorisé sans mot de passe, sans quoi il échoue silencieusement lorsqu’il est lancé par cron (qui n’a pas de terminal interactif pour saisir un mot de passe). L’échec ne remonte alors aucune erreur exploitable dans le log applicatif, l’archive reste simplement vide.

Plutôt que d’autoriser tar sans restriction (ce qui donnerait un accès complet en lecture et écriture à tout le système à quiconque contrôle ce compte), la règle sudoers peut être limitée à la commande exacte utilisée par le script :

sudo visudo -f /etc/sudoers.d/backup-docker

puis remplacer le contenu par :

USER ALL=(root) NOPASSWD: /usr/bin/tar --exclude=*.db-shm --exclude=*.db-wal -czf /tmp/docker-*.tar.gz -C /home/USER/docker .

Le joker (*) dans le nom de fichier autorise uniquement la variation de la date, tout le reste de la commande doit correspondre au caractère près à ce que le script envoie. Si le script évolue plus tard (nouvelle option, chemin différent), cette règle doit être mise à jour en même temps, sinon le même échec silencieux se reproduira.

Se faire alerter automatiquement avec Home Assistant

Le script écrit un journal détaillé, mais rien ne prévenait activement en cas de souci, il fallait aller consulter le fichier manuellement. Un capteur Home Assistant permet de surveiller ce journal et de déclencher une notification par email en cas d’anomalie.

Monter le dossier de logs dans le container Home Assistant, dans la section volumes du fichier de configuration de la stack (via Portainer) :

- /home/USER/rclone-logs:/rclone-logs:ro

Le :ro (lecture seule) suffit, Home Assistant n’a pas besoin d’écrire dans ce dossier.

Créer le capteur. Depuis la version 2023.8 de Home Assistant, la syntaxe sensor: - platform: command_line ne fonctionne plus, le capteur doit être déclaré sous une clé command_line: dédiée :

command_line:
  - sensor:
      name: "Sauvegarde Docker Anomalies"
      unique_id: sauvegarde_docker_anomalies
      command: >
        awk '/=== Début sauvegarde ===/{buf=""} {buf=buf $0 ORS} END{printf "%s", buf}' /rclone-logs/backup-docker.log > /tmp/dernier-run.log;
        if grep -q "=== Sauvegarde terminée ===" /tmp/dernier-run.log; then
          COUNT=$(grep -E "ATTENTION|ERROR" /tmp/dernier-run.log | grep -vc "directory not found");
          echo "$COUNT";
        else
          echo "99";
        fi
      scan_interval: 3600

Ce capteur isole la dernière exécution du script (entre le dernier === Début sauvegarde === et la fin du fichier), puis renvoie soit le nombre de lignes d’alerte ou d’erreur trouvées, soit 99 si la ligne === Sauvegarde terminée === est absente, ce qui signale un script resté bloqué ou interrompu avant la fin.

Un piège à connaître sur cette commande : grep -c compte des lignes, mais son code de sortie (au sens Unix, pas sa sortie affichée) vaut 1 dès que ce compte est à zéro, 0 seulement s’il trouve au moins une ligne. L’intégration command_line traite tout code de sortie différent de zéro comme un échec de la commande, et dans ce cas, elle n’écrit jamais la valeur dans le capteur. Résultat contre-intuitif : le capteur reste bloqué sur unknown précisément quand tout va bien (zéro anomalie trouvée). La solution, déjà intégrée ci-dessus, consiste à isoler le comptage dans une variable puis à l’afficher avec un echo séparé, qui réussit toujours quel que soit le chiffre affiché.

Créer l’automatisation qui envoie l’alerte par email :

automation:
  - alias: "Alerte anomalie sauvegarde Docker"
    id: alerte_anomalie_sauvegarde_docker
    trigger:
      - platform: time
        at: "03:30:00"
    condition:
      - condition: numeric_state
        entity_id: sensor.sauvegarde_docker_anomalies
        above: 0
    action:
      - service: notify.email_alertes
        data:
          title: "[home ALD] Anomalie sauvegarde Docker"
          message: >
            Une anomalie a été détectée dans la sauvegarde Docker de cette nuit (code {{ states('sensor.sauvegarde_docker_anomalies') }}).
            Automatisation déclenchante : automation.alerte_anomalie_sauvegarde_docker
            Consulter le log complet sur le Mini PC : /home/USER/rclone-logs/backup-docker.log

Le déclenchement à 3h30 laisse une marge confortable après le lancement du script à 3h00, largement suffisante avec un temps d’exécution ramené à quelques minutes.

Attention si tu ajoutes une automatisation dans un fichier automations.yaml existant : chaque automatisation est un élément de liste, le tiret - qui la démarre doit être collé à gauche, sans indentation, sous peine d’erreur de chargement.

Pour aller plus loin

Migrer Dropbox vers Google Drive avec rclone

Migrer Dropbox vers Google Drive avec rclone

Tu veux migrer un compte Dropbox vers Google Drive avec rclone, et le transfert stagne à quelques centaines de kilo-octets par seconde alors que ta connexion tient des centaines de mégabits. Voici ce qu’on a découvert en cherchant pourquoi, et la méthode qui a fini par fonctionner.

Précision : migration réalisée avec l’aide de Claude.ai et parfois Gemini aussi. Cet article a été préparé par Claude.ai sur la base des essais que j’ai réalisé et de ce qui a fonctionné.

Le symptôme

J’ai d’abord essayé sur un dossier Dropbox Pilote, appelé « A-TRANSFERER ». L’idée c’était d’identifier la meilleure façon de faire avec un « petit » dossier avant de transférer l’ensemble de ma Dropbox (quelques centaines de giga-octets). Et tout de suite l’essai s’est heurté à des difficultés.

le problème : un dossier Dropbox de plus de 50 000 fichiers à transférer vers Google Drive, pour une taille totale de quelques dizaines de giga-octets. La commande de départ, sur un petit serveur Linux (un mini PC sous Ubuntu) :

rclone copy dropbox:"A-TRANSFERER" gdrive:/ANCIEN-DROPBOX --progress --transfers 4 --checkers 8 --tpslimit 10 --fast-list --log-file=/home/user/rclone-migration.log

Résultat : un débit de 263 Ko/s (kilo-octets par seconde), et une estimation de temps restant (ETA, « estimated time of arrival ») de plusieurs heures pour quelques giga-octets. Un test de débit de la connexion (speedtest-cli) confirmait pourtant 397 Mbit/s en envoi (upload), soit environ 47 Mo/s. Le débit réel était donc mille fois inférieur à ce que la connexion permettait.

Les fausses pistes (et pourquoi elles n’ont pas suffi)

Augmenter le parallélisme. Première réaction logique : monter --transfers et --checkers, et supprimer la limite --tpslimit. Le débit a un peu progressé (jusqu’à 1,5 Mio/s par moments), mais restait très instable, avec des chutes à quelques kilo-octets par seconde et des ETA remontant parfois à plusieurs semaines.

--drive-chunk-size. Ce paramètre découpe les fichiers volumineux en blocs pour accélérer leur envoi vers Google Drive. Sur le papier, ça semble pertinent. En pratique, il ne s’applique qu’aux fichiers dépassant --drive-upload-cutoff (8 Mo par défaut) : en dessous, rclone envoie le fichier en un seul appel. Pour un lot de dizaines de milliers de petits fichiers (quelques centaines de kilo-octets en moyenne), ce réglage n’a strictement aucun effet.

Changer de machine pour plus de bande passante. L’hypothèse suivante : et si le goulot d’étranglement venait de la connexion du mini PC ? Un test sur Google Cloud Shell (un terminal Linux temporaire fourni gratuitement par Google, accessible depuis un navigateur) a permis de vérifier. Sur un fichier unique volumineux, le débit obtenu (1,5 Mio/s) n’était pas meilleur que ce qu’on avait déjà. La bande passante n’était donc pas non plus en cause.

Le vrai diagnostic

rclone copy entre deux services cloud distincts n’est pas un transfert serveur à serveur : chaque fichier est téléchargé depuis la source puis envoyé vers la destination, en passant par la machine qui exécute la commande. Avec des dizaines de milliers de petits fichiers, chaque aller-retour compte, et l’API de Dropbox s’est révélée nettement plus sensible à la concurrence que celle de Google Drive. Les recommandations généralement admises pour Dropbox vont d’ailleurs à l’inverse de ce qu’on avait tenté : réduire la concurrence (2 à 3 transferts, 4 vérificateurs), pas l’augmenter.

Un signal confirmait cette hypothèse : des erreurs sporadiques du type suivant, dans le journal (log) de transfert :

ERROR : chemin/vers/un/fichier: Failed to copy: failed to open source object: invalid character '&lt;' looking for beginning of value

Cette erreur signifie que Dropbox a renvoyé une page HTML d’erreur au lieu de la réponse JSON attendue, un signe classique de sursollicitation de l’API.

La méthode qui a fonctionné : passer par un disque local

Plutôt que de faire transiter chaque fichier directement entre les deux services cloud, la solution a été de synchroniser d’abord Dropbox sur un disque dur externe (via le client Dropbox classique, sur un PC Windows ou macOS), puis de transférer ce disque vers Google Drive avec rclone, sans repasser par l’API Dropbox.

Étape 1 : synchroniser Dropbox sur un disque local

Rien de spécifique à rclone ici : il s’agit d’utiliser le client Dropbox habituel (application de bureau) pour synchroniser sélectivement les dossiers voulus sur un disque, idéalement externe pour ne pas saturer le disque principal.

Point de vigilance avant de désactiver ensuite la synchronisation Dropbox : le client Dropbox traite le contenu synchronisé comme un simple reflet du cloud. Décocher un dossier dans la synchronisation sélective supprime les fichiers correspondants en local, sans avertissement particulier. Il faut donc s’assurer que la copie de sécurité (le disque externe, dans ce cas) est bien complète et vérifiée avant de toucher à quoi que ce soit côté Dropbox.

Étape 2 : monter le disque sur le serveur Linux

lsblk

Cette commande liste les périphériques de stockage connectés, pour repérer le nouveau disque (généralement /dev/sdX, où X est une lettre).

sudo blkid /dev/sda1

Vérifie le système de fichiers du disque. Pour un disque formaté sous Windows, ce sera le plus souvent ntfs.

sudo apt install ntfs-3g
sudo mkdir -p /mnt/disque-externe
sudo mount /dev/sda1 /mnt/disque-externe

Si le disque n’a pas été démonté proprement avant d’être débranché (ça arrive), un message de ce type peut apparaître au montage :

The disk contains an unclean file system (0, 0).
The file system wasn't safely closed on Windows. Fixing.

Pas d’inquiétude : c’est le mécanisme de correction automatique de ntfs-3g, qui répare les petites incohérences avant de monter le disque.

Étape 3 : exclure ce qui ne doit pas partir vers le cloud

Avant de tout copier, il vaut mieux vérifier le contenu à la racine du disque. Il n’est pas rare d’y trouver des fichiers isolés qui n’ont rien à faire dans une migration cloud : mots de passe en clair, clés privées, bases de données de gestionnaire de mots de passe. Un fichier de filtre permet d’exclure ce type d’éléments proprement :

cat > /home/user/exclude-disque-externe.txt << 'EOF'
/$RECYCLE.BIN/**
/System Volume Information/**
/.dropbox
/.dropbox.cache/**
/.dropbox.device
/desktop.ini
/cle-ssh-ancienne.pem
/mots-de-passe.kdbx
EOF

Chaque ligne commençant par / cible un élément à la racine du dossier source, ** couvrant tout le contenu d’un dossier.

Étape 4 : transférer vers Google Drive

nohup rclone copy /mnt/disque-externe gdrive:/NOUVEAU-DOSSIER --exclude-from /home/user/exclude-disque-externe.txt --transfers 12 --checkers 16 --fast-list --log-file=/home/user/rclone-disque-externe.log &

Différence notable avec la tentative initiale : la concurrence est ici volontairement élevée (12 transferts, 16 vérificateurs), parce que la destination est Google Drive, qui tolère bien mieux ce type de charge que Dropbox. Le débit obtenu avec cette méthode a dépassé 2 Mio/s en continu, contre 79 à 143 KiB/s lors des essais directs Dropbox vers Google Drive, soit un facteur supérieur à 15.

Le nohup ... & en début de commande permet de fermer la session SSH ou d’éteindre le poste depuis lequel elle a été lancée, sans interrompre le transfert sur le serveur.

Étape 5 : vérifier l’intégrité du transfert

rclone check /mnt/disque-externe gdrive:/NOUVEAU-DOSSIER --exclude-from /home/user/exclude-disque-externe.txt --one-way --fast-list

--fast-list réduit fortement le nombre de requêtes nécessaires pour parcourir l’arborescence de destination, un point important : sans cette option, la vérification d’un grand nombre de dossiers peut elle-même déclencher une limitation de l’API Google (erreur 403 Quota exceeded, avec la mention RATE_LIMIT_EXCEEDED). Ce paramètre fonctionne côté Google Drive, mais pas côté Dropbox, qui ne le prend pas en charge (rclone l’ignore silencieusement s’il est utilisé sur un remote Dropbox).

Résultat attendu en cas de succès :

NOTICE: Google drive root 'NOUVEAU-DOSSIER': 0 differences found
NOTICE: Google drive root 'NOUVEAU-DOSSIER': 55653 matching files

Deux pièges annexes, utiles à connaître

Un dossier de destination qui n’est pas à la racine attendue. Si le remote gdrive: a été configuré pour un autre usage au préalable (une sauvegarde automatisée, par exemple), il peut pointer vers un sous-dossier précis plutôt que vers la racine réelle du Drive, via un paramètre caché nommé root_folder_id. Un dossier créé avec gdrive:/MonDossier peut alors atterrir plusieurs niveaux plus bas que prévu, invisible à première vue dans l’interface web. Pour vérifier :

rclone config show gdrive

La présence d’une ligne root_folder_id confirme ce comportement.

Deux configurations rclone distinctes, deux dossiers distincts. Autoriser rclone sur deux machines différentes (un serveur et Cloud Shell, par exemple) crée deux configurations indépendantes. Si elles ne pointent pas vers le même compte Google, ou si l’une a un root_folder_id différent de l’autre, un même nom de dossier de destination peut désigner deux emplacements complètement différents. Google Drive autorisant les doublons de noms, l’interface web peut alors sembler afficher un contenu incohérent selon le moment où elle est consultée. En cas de doute, mieux vaut vérifier directement avec rclone lsd et rclone size plutôt que de se fier au rendu de l’interface, qui peut aussi simplement mettre du temps à se rafraîchir.

Ce qu’on ferait différemment en repartant de zéro

  • Mesurer la structure du contenu avant de choisir une méthode (rclone size, rclone lsd) : beaucoup de petits fichiers oriente d’emblée vers un passage par disque local, peu de fichiers volumineux se prête bien à un transfert direct.
  • Vérifier le root_folder_id du remote gdrive: avant le premier transfert.
  • Protéger systématiquement toute commande longue avec nohup dès le premier lancement, pas après une mauvaise surprise.
  • Exclure les fichiers sensibles ou indésirables avant de copier, pas après avoir découvert le mélange.
  • Toujours vérifier avec rclone check --fast-list en fin de transfert, jamais se contenter d’une comparaison de volumes ou de comptage de fichiers.

Pour aller plus loin

Cet article fait partie de la série projets Ubuntu, qui documente les projets techniques menés sur mon infrastructure Linux personnelle.

Domotique : alerte d’ouverture de porte en cas d’absence

Domotique : alerte d’ouverture de porte en cas d’absence

Tu veux recevoir une alerte si une porte s’ouvre chez toi pendant ton absence, avec une photo prise automatiquement par ta caméra ? Voici comment mettre en place ce système avec Home Assistant, des capteurs Zigbee et l’application Companion sur Android.

Cet article fait partie de la série Domotique avec Home Assistant.

Prérequis : Home Assistant installé et accessible depuis l’extérieur via une URL sécurisée (https), Zigbee2MQTT opérationnel, application Home Assistant Companion installée sur ton téléphone Android, service d’envoi d’email déjà configuré dans HA (voir Détecter une coupure de courant avec Home Assistant).


Les capteurs d’ouverture de porte

Les capteurs d’ouverture Zigbee doivent être appairés et visibles dans Home Assistant avant de commencer. Si ce n’est pas encore fait, consulte l’article Étendre son réseau Zigbee dans Home Assistant qui couvre l’appairage et le maillage Zigbee.

Une fois appairés, chaque capteur expose une entité de type binary_sensor dans HA. Pour retrouver leurs identifiants exacts, va dans Paramètres > Appareils et services > Entités et filtre sur « contact ». Tu obtiendras des identifiants de la forme :

binary_sensor.ouverture_porte_a_garage_cote_nord_contact
binary_sensor.ouverture_porte_b_garage_cote_sud_contact
binary_sensor.ouverture_porte_c_entree_nord_contact
binary_sensor.ouverture_porte_d_vers_garage_contact

Note : certains capteurs bon marché (comme les MOES ZG-1027L utilisés ici) mesurent aussi la luminosité en plus de l’ouverture. Les deux fonctions coexistent sans se gêner – l’entité contact reste bien celle qui détecte l’ouverture.


Créer le groupe de capteurs

Plutôt que de surveiller chaque capteur individuellement dans l’automatisation, on les regroupe dans un seul capteur virtuel. Ce groupe passe à « activé » dès qu’une seule porte s’ouvre, et revient à « désactivé » quand toutes sont fermées. Ajouter une nouvelle porte plus tard se résume à ajouter une ligne dans ce groupe.

Va dans Paramètres > Appareils et services > Entrées, clique sur + Créer une entrée, puis choisis Groupe > Groupe binaire.

  • Nom : Groupe ouvertures surveillées
  • Membres : ajoute tes capteurs d’ouverture
  • Option « Toutes les entités » : désactivée (logique « au moins un »)

Enregistre. HA crée automatiquement l’entité binary_sensor.groupe_ouvertures_surveillees.


Détecter la présence par téléphone

Créer l’entité Personne

Dans HA, va dans Paramètres > Personnes, crée une entrée pour chaque personne à suivre et associe-lui le téléphone correspondant. HA crée automatiquement une entité person.personne_1 qui passe à home ou not_home selon la position du téléphone.

Configurer l’application Companion

La détection de présence ne fonctionne correctement que si l’application est bien configurée. Deux points sont critiques.

L’URL du serveur : l’app doit pouvoir joindre HA aussi bien depuis le réseau local que depuis l’extérieur. Dans l’app, va dans ≡ > Paramètres > Companion app > Serveur et configure :

  • URL de Home Assistant : https://ha.ton-domaine.com (ton URL externe)
  • URL de connexion interne : http://192.168.x.x:8123 (l’IP locale de ton mini PC)

Si seule l’IP locale est renseignée, le téléphone perd le contact avec HA dès qu’il quitte le Wi-Fi maison et ne remonte plus aucune donnée – y compris sa position.

Les capteurs de localisation : dans ≡ > Paramètres > Companion app > Capteurs > Capteurs de localisation, active :

  • Localisation en arrière-plan
  • Zone de localisation

Sans ces deux options, HA ne reçoit aucune information de position.

Dans les paramètres Android, vérifie aussi que l’app Home Assistant dispose de la permission de localisation réglée sur « Toujours autoriser », et que l’optimisation de batterie est désactivée pour cette app.

Le capteur Wi-Fi comme filet de sécurité

La détection GPS peut être lente ou imprécise selon les conditions. On ajoute donc un deuxième critère : si le téléphone n’est plus connecté au Wi-Fi maison, on considère la personne comme absente.

L’entité à utiliser est sensor.telephone1_wi_fi_connection (remplace par l’identifiant correspondant à ton téléphone). Sa valeur est le nom du réseau Wi-Fi connecté (par exemple MON_RESEAU_WIFI) quand tu es à la maison, et <not connected> quand tu es dehors.

Pour activer ce capteur, va dans Paramètres > Appareils et services > Entités, recherche ton téléphone et active l’entité « Wi-Fi connection » si elle est désactivée.


L’automatisation

L’automatisation se déclenche quand le groupe de capteurs passe à « ouvert », à condition qu’au moins l’une des deux conditions suivantes soit vraie : person.personne_1 est not_home, ou le Wi-Fi du téléphone n’est pas le réseau maison.

Elle prend alors un snapshot de la caméra avec un nom horodaté, puis envoie un email avec la photo en pièce jointe.

Note sur la caméra : cette configuration fonctionne avec une caméra intégrée dans HA via l’intégration Tuya. Cette intégration cloud est limitée – elle ne permet pas l’enregistrement local continu ni la gestion avancée des flux vidéo. Le snapshot (photo statique au moment du déclenchement) est la seule action fiable disponible depuis HA avec ce type de caméra.

Le snapshot est sauvegardé dans le dossier /media du container Home Assistant. Ce dossier doit être mappé explicitement dans le docker-compose.yml de Portainer :

volumes:
  - /home/USER/docker/home-assistant/config:/config
  - /home/USER/docker/home-assistant/media:/media
  - /etc/localtime:/etc/localtime:ro
  - /run/dbus:/run/dbus:ro

Crée le dossier sur le mini PC avant de redémarrer le container :

mkdir /home/USER/docker/home-assistant/media

Va dans Paramètres > Automatisations, clique sur + Créer une automatisation, choisis Créer une automatisation vide, puis passe en mode YAML et colle le code suivant :

alias: "[home AL] Alerte ouverture en absence"
description: >-
  Alerte si une porte s'ouvre alors qu'aucune personne autorisée n'est à la
  maison. Automatisation : alerte_ouverture_absence_v2
triggers:
  - entity_id: binary_sensor.groupe_ouvertures_surveillees
    to: "on"
    trigger: state
conditions:
  - condition: or
    conditions:
      - condition: state
        entity_id: person.personne_1
        state: "not_home"
      - condition: not
        conditions:
          - condition: state
            entity_id: sensor.telephone1_wi_fi_connection
            state: "MON_RESEAU_WIFI"
actions:
  - variables:
      snapshot_file: >
        /media/alerte_ouverture_{{ now().strftime('%Y%m%d_%H%M%S') }}.jpg
  - action: camera.snapshot
    data:
      filename: "{{ snapshot_file }}"
    target:
      entity_id: camera.ma_camera
  - action: notify.email_alertes
    data:
      title: "[home AL] Ouverture détectée en absence"
      message: >
        Une porte ou fenêtre surveillée vient de s'ouvrir alors qu'aucune
        personne autorisée n'est détectée à la maison.
        Heure : {{ now().strftime('%d/%m/%Y à %H:%M:%S') }}.
        (Automatisation : alerte_ouverture_absence_v2)
      data:
        images:
          - "{{ snapshot_file }}"
mode: single

Adapte les identifiants suivants à ta configuration :

  • binary_sensor.groupe_ouvertures_surveillees : l’identifiant de ton groupe de capteurs
  • person.personne_1 : l’entité Personne de ton téléphone
  • sensor.telephone1_wi_fi_connection : le capteur Wi-Fi de ton téléphone
  • MON_RESEAU_WIFI : le nom de ton réseau Wi-Fi maison
  • camera.ma_camera : l’identifiant de ta caméra
  • notify.email_alertes : le nom de ton service d’email

Tester

Pour tester sans sortir de chez toi, désactive le Wi-Fi de ton téléphone. L’entité sensor.telephone1_wi_fi_connection passe alors à <not connected>, ce qui satisfait la condition d’absence.

Ouvre ensuite une porte surveillée. Tu dois recevoir un email avec la photo en pièce jointe dans les secondes qui suivent, et un fichier horodaté doit apparaître dans /home/USER/docker/home-assistant/media/.

Pour vérifier le détail d’une exécution, va dans Paramètres > Automatisations > [home AL] Alerte ouverture en absence > Historique des exécutions.


Ajouter un deuxième téléphone

Pour surveiller un deuxième téléphone, ajoute son entité Personne et son capteur Wi-Fi dans les conditions. L’alerte ne se déclenche que si les deux personnes sont absentes :

conditions:
  - condition: or
    conditions:
      - condition: and
        conditions:
          - condition: state
            entity_id: person.personne_1
            state: "not_home"
          - condition: state
            entity_id: person.personne_2
            state: "not_home"
      - condition: and
        conditions:
          - condition: not
            conditions:
              - condition: state
                entity_id: sensor.telephone1_wi_fi_connection
                state: "MON_RESEAU_WIFI"
          - condition: not
            conditions:
              - condition: state
                entity_id: sensor.telephone2_wi_fi_connection
                state: "MON_RESEAU_WIFI"

Pour aller plus loin

Retrouve tous les articles de la série Domotique avec Home Assistant et de la série Projets Ubuntu.