Time Machine est la solution de sauvegarde intégrée à macOS. Simple à utiliser, elle présente des limites : format propriétaire, chiffrement optionnel, impossible de sauvegarder vers un NAS Linux ou du stockage cloud S3, et politique de rétention peu flexible. Restic est une alternative open source : rapide, sûr, dédupliquant, chiffré de bout en bout, et compatible de nombreuses destinations (disque local, NAS, SFTP, S3, ).

Dans cet article nous mettons en place une sauvegarde automatique sur macOS avec Restic : installation, dépôt chiffré sur disque externe, script de sauvegarde, automatisation via LaunchAgent et notification Discord.

Pourquoi Restic plutôt que Time Machine ?

Fonctionnalité Time Machine Restic
Chiffrement Optionnel (APFS chiffré) Systématique (AES-256)
Déduplication Liens durs uniquement Déduplication par blocs
Destinations Disque local, Time Capsule Local, SFTP, S3, Azure, GCS…
Restauration partielle Via l’interface graphique CLI, granularité au fichier près
Rétention configurable Automatisée, peu flexible Totalement paramétrable
Vérification intégrité Limitée restic check avec hachage complet
Format Propriétaire Open source, portable

Prérequis

  • macOS 12 Monterey ou supérieur (testé sur macOS 26.5 Tahoe, Apple Silicon M2 Pro)
  • Homebrew installé (brew.sh)
  • Un disque externe USB-C ou un NAS/stockage cloud comme destination

Installation de Restic

# Installation via Homebrew
brew install restic

# Verifier la version installee
restic version

Initialisation du dépôt

Un dépôt Restic (repository) est le répertoire où toutes les sauvegardes sont stockées de façon dédupliquante et chiffrée. Il doit être initialisé une seule fois. Dans notre exemple, le disque externe est monté sous /Volumes/ExternalDrive.

# Creer le fichier de mot de passe (ne jamais commiter dans git)
echo "VotreMotDePasseTresLong" > ~/.restic_password
chmod 600 ~/.restic_password

# Initialiser le depot sur le disque externe
restic init --repo /Volumes/ExternalDrive/restic-backup --password-file ~/.restic_password

Ce mot de passe est le seul moyen de déchiffrer les données. Conservez-le précieusement dans un gestionnaire de mots de passe.

Première sauvegarde manuelle

# Sauvegarder Documents et scripts
restic backup ~/Documents ~/scripts 
  --repo /Volumes/ExternalDrive/restic-backup 
  --password-file ~/.restic_password 
  --verbose

# Lister les snapshots existants
restic snapshots 
  --repo /Volumes/ExternalDrive/restic-backup 
  --password-file ~/.restic_password

Script de sauvegarde

Créer le fichier /opt/homebrew/bin/restic_backup.sh et le rendre exécutable :

touch /opt/homebrew/bin/restic_backup.sh
chmod 755 /opt/homebrew/bin/restic_backup.sh
nano /opt/homebrew/bin/restic_backup.sh

Contenu du script (adapter DISCORD_WEBHOOK, REPO et SOURCES à votre configuration) :

#!/bin/bash
# Sauvegarde automatique restic - MacMini M2 Pro
# Dossiers : ~/Documents ~/scripts
# Repo : /Volumes/ExternalDrive/restic-backup
# Retention : 1 mois glissant

REPO="/Volumes/ExternalDrive/restic-backup"
PASSWORD_FILE="$HOME/.restic_password"
LOG="/opt/homebrew/var/log/restic_backup.log"
SOURCES=("$HOME/Documents" "$HOME/scripts")
DISCORD_WEBHOOK="https://discord.com/api/webhooks/VOTRE_WEBHOOK_ICI"

log() {
    local msg="[$(date '+%Y-%m-%d %H:%M:%S')] $*"
    echo "$msg" >> "$LOG"
    echo "$msg"
}

# Echappe une chaine pour insertion sereine dans du JSON :
# backslash et guillemets echappes, vrais retours a la ligne -> n
json_escape() {
    local s="$1"
    s=${s///\}
    s=${s//"/"}
    s=${s//$'r'/}
    s=${s//$'n'/n}
    s=${s//$'t'/t}
    printf '%s' "$s"
}

discord() {
    local message="$1"
    local payload http_code
    payload=$(printf '{"content": "%s"}' "$(json_escape "$message")")
    http_code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 15 
        -X POST "$DISCORD_WEBHOOK" 
        -H "Content-Type: application/json" 
        --data-binary "$payload")
    if [ "$http_code" = "204" ] || [ "$http_code" = "200" ]; then
        log "Discord OK (HTTP $http_code)"
    else
        log "Discord ECHEC (HTTP ${http_code:-?})"
    fi
}

if [ ! -d "$REPO" ]; then
    log "ERREUR : /Volumes/ExternalDrive non monte - backup annule"
    discord "❌ **ResticBackup MacMini** - Disque ExternalDrive non monté - backup annulé"
    exit 1
fi

log "=== Debut sauvegarde ==="
START_TIME=$(date +%s)

BACKUP_OUTPUT=$(/opt/homebrew/bin/restic backup 
    --repo "$REPO" 
    --password-file "$PASSWORD_FILE" 
    --tag macmini 
    --exclude="*.DS_Store" 
    --exclude=".git/objects" 
    --exclude="node_modules" 
    --exclude="__pycache__" 
    --exclude="*.pyc" 
    "${SOURCES[@]}" 2>&1)

BACKUP_STATUS=$?
echo "$BACKUP_OUTPUT" >> "$LOG"

if [ $BACKUP_STATUS -eq 0 ]; then
    log "Backup OK"

    # Extraction des stats restic
    FILES_NEW=$(echo "$BACKUP_OUTPUT" | grep "Files:" | awk '{print $2}')
    SIZE_ADDED=$(echo "$BACKUP_OUTPUT" | grep "Added to the repository" | grep -oE '[0-9.]+ ([KMGT]i)?B' | head -1)
    # Ne matcher QUE la ligne "snapshot <id> saved" (evite "using parent snapshot ...")
    SNAPSHOT_ID=$(echo "$BACKUP_OUTPUT" | grep -oE 'snapshot [0-9a-f]+ saved' | awk '{print $2}')

    END_TIME=$(date +%s)
    DURATION=$(( END_TIME - START_TIME ))
    DURATION_FMT=$(printf '%dm%02ds' $((DURATION/60)) $((DURATION%60)))

    MSG=$(printf '✅ **ResticBackup MacMini** - Backup terminé avec succèsn📁 Nouveaux fichiers : %sn💾 Données ajoutées : %sn🔖 Snapshot : `%s`n⏱️ Durée : %s' 
        "${FILES_NEW:-?}" "${SIZE_ADDED:-?}" "${SNAPSHOT_ID:-?}" "${DURATION_FMT}")
    discord "$MSG"
else
    log "ERREUR backup"
    ERR=$(echo "$BACKUP_OUTPUT" | tail -5)
    MSG=$(printf '❌ **ResticBackup MacMini** - Erreur lors du backupn```n%sn```' "$ERR")
    discord "$MSG"
    exit 1
fi

log "Purge snapshots > 1 mois..."

/opt/homebrew/bin/restic forget 
    --repo "$REPO" 
    --password-file "$PASSWORD_FILE" 
    --keep-within 1m 
    --prune >> "$LOG" 2>&1

log "Purge OK"
log "=== Fin sauvegarde ==="

La notification Discord de fin de sauvegarde est désormais enrichie : elle remonte le nombre de nouveaux fichiers, le volume de données ajoutées, l'identifiant du snapshot et la durée. L'envoi est robuste (échappement JSON propre via json_escape et journalisation du code HTTP de réponse de Discord).

⚠️ Le webhook Discord se récupère dans les paramètres du salon Discord : Intégrations → Webhooks → Créer un webhook.

Automatisation avec un LaunchAgent

Sur macOS, les tâches planifiées se configurent via des LaunchAgents — l’équivalent des crontabs Linux, avec une meilleure intégration système.

# Creer le repertoire LaunchAgents si necessaire
mkdir -p ~/Library/LaunchAgents

Créer le fichier ~/Library/LaunchAgents/local.restic_backup.plist :

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>local.restic_backup</string>

    <key>ProgramArguments</key>
    <array>
        <string>/opt/homebrew/bin/restic_backup.sh</string>
    </array>

    <!-- Lancement tous les jours a 02h00 -->
    <key>StartCalendarInterval</key>
    <dict>
        <key>Hour</key>
        <integer>2</integer>
        <key>Minute</key>
        <integer>0</integer>
    </dict>

    <key>StandardOutPath</key>
    <string>/opt/homebrew/var/log/restic_backup.log</string>
    <key>StandardErrorPath</key>
    <string>/opt/homebrew/var/log/restic_backup.log</string>

    <key>RunAtLoad</key>
    <false/>
</dict>
</plist>
# Charger le LaunchAgent
launchctl load ~/Library/LaunchAgents/local.restic_backup.plist

# Verifier qu'il est bien charge
launchctl list | grep restic

# Tester manuellement
launchctl start local.restic_backup

# Modifier l'heure : editer le plist puis recharger
launchctl unload ~/Library/LaunchAgents/local.restic_backup.plist
launchctl load  ~/Library/LaunchAgents/local.restic_backup.plist

Restaurer des fichiers

Lister les snapshots

restic snapshots 
  --repo /Volumes/ExternalDrive/restic-backup 
  --password-file ~/.restic_password
ID        Time                 Host        Paths
a1b2c3d4  2026-06-01 02:00:05  macmini     /Users/user/Documents, /Users/user/scripts
e5f6g7h8  2026-06-02 02:00:03  macmini     /Users/user/Documents, /Users/user/scripts

Restaurer un snapshot complet

restic restore latest 
  --repo /Volumes/ExternalDrive/restic-backup 
  --password-file ~/.restic_password 
  --target /tmp/restic-restore

Restaurer un fichier ou dossier spécifique

# Restaurer un sous-dossier precis
restic restore latest 
  --repo /Volumes/ExternalDrive/restic-backup 
  --password-file ~/.restic_password 
  --include "/Users/user/Documents/Projets" 
  --target /tmp/restic-restore

# Restaurer depuis un snapshot specifique (ID du snapshot)
restic restore a1b2c3d4 
  --repo /Volumes/ExternalDrive/restic-backup 
  --password-file ~/.restic_password 
  --include "/Users/user/Documents/important.pdf" 
  --target /tmp/restic-restore

Vérifier l’intégrité du dépôt

# Verification rapide (structure du depot)
restic check 
  --repo /Volumes/ExternalDrive/restic-backup 
  --password-file ~/.restic_password

# Verification complete avec lecture des donnees
restic check --read-data 
  --repo /Volumes/ExternalDrive/restic-backup 
  --password-file ~/.restic_password

Politique de rétention

# Garder 1 mois glissant (notre configuration)
restic forget --keep-within 1m --prune 
  --repo /Volumes/ExternalDrive/restic-backup 
  --password-file ~/.restic_password

# Politique granulaire : 7 quotidiens + 4 hebdos + 3 mensuels
restic forget 
  --keep-daily 7 --keep-weekly 4 --keep-monthly 3 
  --prune 
  --repo /Volumes/ExternalDrive/restic-backup 
  --password-file ~/.restic_password

# Dry-run : voir ce qui serait supprime sans agir
restic forget --keep-within 1m --dry-run 
  --repo /Volumes/ExternalDrive/restic-backup 
  --password-file ~/.restic_password

Commandes utiles

# Statistiques du depot (taille totale, gain deduplication)
restic stats 
  --repo /Volumes/ExternalDrive/restic-backup 
  --password-file ~/.restic_password

# Chercher un fichier dans tous les snapshots
restic find "*.pdf" 
  --repo /Volumes/ExternalDrive/restic-backup 
  --password-file ~/.restic_password

# Comparer deux snapshots
restic diff a1b2c3d4 e5f6g7h8 
  --repo /Volumes/ExternalDrive/restic-backup 
  --password-file ~/.restic_password

# Voir les logs de la derniere sauvegarde
tail -50 /opt/homebrew/var/log/restic_backup.log

Simplifier les commandes avec des variables d’environnement

Pour éviter de répéter --repo et --password-file à chaque commande, ajouter dans ~/.zshrc :

# Dans ~/.zshrc
export RESTIC_REPOSITORY="/Volumes/ExternalDrive/restic-backup"
export RESTIC_PASSWORD_FILE="$HOME/.restic_password"

# Recharger
source ~/.zshrc

# Les commandes deviennent simplement :
restic snapshots
restic backup ~/Documents ~/scripts
restic check

Aller plus loin : sauvegarder vers un NAS ou le cloud

Restic supporte nativement de nombreuses destinations distantes :

# Sauvegarde vers un NAS via SFTP
restic init --repo "sftp:user@nas.local:/backup/restic" --password-file ~/.restic_password
restic backup ~/Documents --repo "sftp:user@nas.local:/backup/restic" --password-file ~/.restic_password


# Sauvegarde vers un bucket S3
export AWS_ACCESS_KEY_ID="votre_key"
export AWS_SECRET_ACCESS_KEY="votre_secret"
restic init --repo "s3:s3.amazonaws.com/nom-bucket" --password-file ~/.restic_password

La règle 3-2-1 recommande 3 copies des données, sur 2 supports différents, dont 1 hors site. Restic s’y prête parfaitement : disque externe local ou vers un NAS distant.

Conclusion

Restic offre une solution de sauvegarde robuste, flexible et chiffrée pour macOS. Comparé à Time Machine, il donne un contrôle total sur les données, la destination et la politique de rétention, tout en restant simple en ligne de commande. Couplé à un LaunchAgent et des notifications Discord, c’est un système de sauvegarde professionnel entièrement automatisé.

La déduplication par blocs optimise l’espace : si 90 % des fichiers n’ont pas changé depuis la dernière sauvegarde, seuls 10 % sont stockés — permettant de conserver plusieurs mois d’historique sur un disque modeste.