Journal [Btrfs et openSUSE] Épisode 2 : snapper et GRUB2

Posté par  (site web personnel) . Licence CC By‑SA.
Étiquettes :
35
21
août
2017

Sommaire

Btrfs is hard

« Btrfs et openSUSE » est une série de journaux sur le système de fichiers Btrfs, basée sur ma propre expérience d'utilisateur d'openSUSE. Au menu :

  • des généralités sur Btrfs
  • des noms qui pètent : sous-volumes, snapshots, rollbacks
  • du snapper
  • du grub
  • de la mise à jour incrémentale
  • des quotas
  • de la maintenance
  • des trucs spécifiques à openSUSE
  • des tentatives désespérées pour rester applicable à d'autres distributions
  • des erreurs (pas taper)
  • des bugs
  • des cris
  • des larmes
  • et bien plus ! ou bien moins

Aujourd'hui, l'épisode 2 : snapper et GRUB2.

snapper : un gestionnaire d'instantanés

Aperçu

snapper est un outil, sous licence GPL-2.0, pour gérer des snapshots. Il a été créé par Arvin Schnell de chez SUSE.

Ses objectifs :

  • Prendre des instantanés automatiquement :
    • de façon périodique
    • avant et après chaque interaction avec des outils pouvant modifier le système. Des gestionnaires de paquets par exemple : il y a des greffons pour zypper, dnf, pacman, apt
  • Nettoyer les instantanés anciens automatiquement
  • Fournir des commandes simples pour :
    • prendre des instantanés manuellement
    • comparer des instantanés
    • faire des restaurations

Il se veut indépendant du système de fichiers : par exemple, snapper serait capable de prendre des instantanés avec LVM et ext4 – heu en fait non, pas ext4, c'est une blague

Si aujourd'hui snapper est disponible sur la plupart des distributions GNU/Linux, sur openSUSE il fait partie intégrante du système. Il y a même un module YaST permettant de réaliser certaines opérations avec une interface graphique.

image

(avec un style début année 2000 du meilleur goût ;-))

En pratique

Lister les instantanés

Pour lister les instantanés pris par snapper :

~# snapper ls
Type   | #  | Pre # | Date                            | Utilisateur | Nettoyer | Description  | Données utilisateur
-------+----+-------+---------------------------------+-------------+----------+--------------+--------------------
single | 0  |       |                                 | root        |          | current      |                    
single | 68 |       | sam. 12 août 2017 20:56:53 CEST | root        | number   |              |                    
single | 69 |       | mer. 16 août 2017 20:07:58 CEST | root        | number   |              |                    
single | 70 |       | jeu. 17 août 2017 21:17:25 CEST | root        | number   |              |                    
pre    | 71 |       | ven. 18 août 2017 22:19:46 CEST | root        | number   | zypp(zypper) | important=no       
post   | 72 | 71    | ven. 18 août 2017 22:20:32 CEST | root        | number   |              | important=no       
~#

snapper repère les instantanés en fouillant dans /.snapshots/*/ à la recherche d'un fichier info.xml. Voici par exemple le contenu de /.snapshots/70/info.xml :

<?xml version="1.0"?>
<snapshot>
  <type>single</type>
  <num>70</num>
  <date>2017-08-17 19:17:25</date>
  <cleanup>number</cleanup>
</snapshot>

Vous noterez la présence d'une colonne « type ». snapper définit en effet trois sortes d'instantanés :

  • single : un instantané classique ;
  • pre : un instantané pris avant l'utilisation d'un outil pouvant modifier le système (pris au démarrage du gestionnaire de paquets par exemple).
  • post : un instantané pris après l'utilisation de cet outil.

C'est une distinction propre à snapper, ça reste des snapshots Btrfs tout à fait similaires !

Créer et supprimer des instantanés

Voici la syntaxe, très simple, pour la création d'un instantané :

snapper create [-c empty-pre-post|number|timeline]

L'option -c permet de dire à snapper que l'instantané doit être nettoyé. Sans cette option, le snapshot ne sera jamais supprimé automatiquement. Voir plus bas la partie nettoyage.

La suppression est aussi très simple :

snapper delete [-s,--sync] <id>...|<id>-<id>

On peut spécifier un ou plusieurs instantanés à supprimer : snapper delete 68, snapper delete 68 69 70.
On peut spécifier un intervalle : snapper delete 68-70.
L'option -s permet d'attendre la fin de la suppression des données avant de rendre la main ; autrement, la suppression des données se fait de manière asynchrone.

Comparer des instantanés

Trois commandes disponibles :

  • snapper status a..b : affiche la liste des fichiers et dossiers qui ont été créés, modifiés ou supprimés entre les instantanés a et b.
  • snapper diff a..b : affiche le contenu des fichiers et dossiers qui ont été créés, modifiés ou supprimés entre les instantanés a et b.
  • snapper xadiff a..b : affiche les attributs étendus des fichiers et dossiers qui ont été… vous savez la suite.

Attention, ça peut être très très lent.

Restaurer des instantanés

Pour le système (/) :

~# snapper rollback 2
Création d'un instantané en lecture-seule du système actuel. (Instantané 4.)
Création d'un instantané en lecture-écriture de l'instantané 2. (Instantané 5.)
Définition du sous-volume par défaut sur l'instantané 5.
#

(C'est l'équivalent des commandes Btrfs qu'on a vues dans l'épisode précédent.)

Il est également possible de faire des « restaurations à chaud ». Les sous-volumes ne sont pas échangés, seulement les fichiers sont remplacés :

snapper undochange <id> <id> [fichiers]

Nettoyage automatique

Autocleanup

snapper introduit trois stratégies de nettoyage :

  • empty-pre-post : comparaison de chaque paire d'instantanés pre/post ; si aucune différence, suppression de la paire.
  • number : au delà d'un certains nombres de snapshots taggés number, les plus anciens commencent à être supprimés ; il n'y a que cette stratégie qui fasse vraiment sens pour des snapshots pris manuellement.
  • timeline : c'est la même chose que number, mais réservé aux instantanés taggés timeline, c'est-à-dire pris périodiquement par snapper.

Le nettoyage automatique des instantanés se fait grâce à une tâche cron (/etc/cron.daily/suse.de-snapper) qui appelle les commandes :

snapper cleanup empty-pre-post
snapper cleanup number
snapper cleanup timeline

Gare à la configuration !

Sur openSUSE, une configuration snapper est créée et activée à l'installation. Sinon, pour créer une configuration, la syntaxe est :

snapper [-c nom_config] <chemin_du_sous-volume>

Par exemple :

~# snapper create-config / # configuration par défaut "root"
Échec de la création de la configuration (subvolume already covered).
~# snapper --config opt create-config /opt # configuration pour un autre sous-volume
~# snapper list-configs
Configuration | Sous-volume
--------------+------------
opt           | /opt       
root          | /          
~#

Sur openSUSE, la configuration de snapper a causé bien des déboires aux utilisateurs.

Danger

~# snapper --config opt get-config
Clé                    | Valeur
-----------------------+-------
ALLOW_GROUPS           |       
ALLOW_USERS            |       
BACKGROUND_COMPARISON  | yes   # "yes" cause des crashs du démon snapper en cas de gros diffs
                               # entre 2 snapshots (typiquement après une grosse màj)
EMPTY_PRE_POST_CLEANUP | yes   
EMPTY_PRE_POST_MIN_AGE | 1800  
FSTYPE                 | btrfs 
NUMBER_CLEANUP         | yes   
NUMBER_LIMIT           | 50    # 50 snapshots associés à l'algo de nettoyage "number"
NUMBER_LIMIT_IMPORTANT | 10    # +10 snapshots avec l'attribut "important" à conserver
NUMBER_MIN_AGE         | 1800  
QGROUP                 |       
SPACE_LIMIT            | 0.5   
SUBVOLUME              | /opt  
SYNC_ACL               | no    
TIMELINE_CLEANUP       | yes   
TIMELINE_CREATE        | yes   # un nouveau snapshot toutes les heures
TIMELINE_LIMIT_DAILY   | 10    # garder 1 snapshot par jour sur 10 jours différents max
TIMELINE_LIMIT_HOURLY  | 10    # garder 1 snapshot par heure sur 10 heures différents max
TIMELINE_LIMIT_MONTHLY | 10    # garder 1 snapshot par mois sur 10 mois différents max
TIMELINE_LIMIT_WEEKLY  | 0     
TIMELINE_LIMIT_YEARLY  | 10    # …
TIMELINE_MIN_AGE       | 1800 
~#

Pour un peu qu'on soit joueur (installations/désinstallations de logiciels fréquentes), les instantanés pouvaient très vite s'accumuler avant que la tâche cron de nettoyage ne se déclenche – avec des réglages beaucoup trop laxistes.

Et l'espace disque libre de se réduire… jusqu'à rendre le système inutilisable ou presque. Surtout qu'il était et qu'il est toujours assez fréquent d'avoir de « petites » partitions racines et une partition /home séparée beaucoup plus grande !

Ce phénomène de saturation de la partition racine par les instantanés a causé beaucoup de tort – à tort – à Btrfs et explique en grande partie, à mon avis, son rejet par bien des utilisateurs d'openSUSE.

Depuis :

  • la taille de la partition racine proposée à l'installation est maintenant de 40 Go ; si inférieure, snapper est désactivé ;
  • l'introduction du sous-volume /var/cache a permis de diminuer sensiblement la taille des snapshots (voir l'épisode précédent) ;
  • depuis la version 0.3, snapper utilise les quotas Btrfs pour limiter la taille prise par les snapshots – ce sont les variables QGROUP et SPACE_LIMIT que vous pouvez voir dans la configuration. C'est bien plus efficace que les autres stratégie de nettoyage… mais pour cela il faut activer les quotas Btrfs, ce que l'on verra dans un prochain épisode.

En cas de souci, voici une configuration très safe pour du desktop, qui n'utilise pas les quotas :

~# snapper get-config
Clé                    | Valeur
-----------------------+-------
ALLOW_GROUPS           |       
ALLOW_USERS            |       
BACKGROUND_COMPARISON  | no    # évite au démon snapper de crasher à la première
                               # grosse mise à jour
EMPTY_PRE_POST_CLEANUP | yes   
EMPTY_PRE_POST_MIN_AGE | 1800  
FSTYPE                 | btrfs 
NUMBER_CLEANUP         | yes   
NUMBER_LIMIT           | 3     # c'est largement suffisant
NUMBER_LIMIT_IMPORTANT | 3     # ça aussi
NUMBER_MIN_AGE         | 1800  
SUBVOLUME              | /     
SYNC_ACL               | no    
TIMELINE_CLEANUP       | no    
TIMELINE_CREATE        | no    # pas de création périodique
TIMELINE_LIMIT_DAILY   | 0     
TIMELINE_LIMIT_HOURLY  | 0     
TIMELINE_LIMIT_MONTHLY | 0     
TIMELINE_LIMIT_WEEKLY  | 0     
TIMELINE_LIMIT_YEARLY  | 0     
TIMELINE_MIN_AGE       | 1800  
~#

C'est celle que j'utilise en ce moment. La commande snapper set-config CLÉ=VALEUR permet de modifier la configuration.

GRUB2 : booter sur du Btrfs

GRUB

Le chargeur d'amorçage d'openSUSE

Attention, danger : on rentre dans de l'openSUSE-überspecifik. Deuxième danger : je ne connais pas bien GRUB. Toujours là ? Alors allons-y !

openSUSE a une collection de patchs pour le chargeur d'amorçage GRUB2 ainsi qu'une configuration personnalisée, liée à snapper, qui permettent :

  • de faire des restaurations du système sans avoir à modifier la configuration du chargeur d'amorçage
  • d'explorer les snapshots en lecture seule du système directement depuis le menu du chargeur d'amorçage
  • de booter sur ces snapshots !

Je ne sais pas si d'autres distributions intègrent des patchs similaires et/ou une configuration de GRUB2 pour Btrfs. J'ai juste aperçu grub-btrfs, une configuration de GRUB écrite par un utilisateur d'Arch Linux. Cela pourrait être intéressant d'en discuter dans les commentaires ;-)

De jolies images

Pour vous donner envie, voici quelques captures d'écran des différents menus du GRUB d'openSUSE. La résolution est mauvaise, c'est une machine virtuelle, désolé…

  • Menu principal :

Menu principal

  • Liste des snapshots (dommage, on ne voit pas les lignes complètes avec cette faible résolution) :

Liste des snapshots

  • Menu d'un snapshot bootable

Menu d'un snapshot bootable

Comment ça marche ?

Configuration

Prenons les choses à l'envers et regardons d'abord la configuration.

Tout d'abord, le contenu du fichier /etc/grub.d/80_suse_btrfs_snapshot est ajouté à /boot/grub2/grub.cfg au moment de sa génération :

#! /bin/sh
set -e
if [ "x${SUSE_BTRFS_SNAPSHOT_BOOTING}" = "xtrue" ] &&
   [ "x${GRUB_FS}" = "xbtrfs" ] ; then
    cat << \EOF
if [ -f "/.snapshots/grub-snapshot.cfg" ]; then
  source "/.snapshots/grub-snapshot.cfg"
fi
EOF
fi

Ces quelques lignes ne font que sourcer le fichier /.snapshots/grub-snapshot.cfg qui lui-même ne fait rien d'autre que sourcer les fichiers /.snapshots/*/grub-snapshot.cfg :

~# cat /.snapshots/grub-snapshot.cfg
if [ -z "$extra_cmdline" ]; then
  submenu  "Start bootloader from a read-only snapshot" {
    if [ -f "/.snapshots/72/grub-snapshot.cfg" ]; then
      source "/.snapshots/72/grub-snapshot.cfg"
    fi
    if [ -f "/.snapshots/71/grub-snapshot.cfg" ]; then
      source "/.snapshots/71/grub-snapshot.cfg"
    fi
    if [ -f "/.snapshots/70/grub-snapshot.cfg" ]; then
      source "/.snapshots/70/grub-snapshot.cfg"
    fi
    if [ -f "/.snapshots/69/grub-snapshot.cfg" ]; then
      source "/.snapshots/69/grub-snapshot.cfg"
    fi
    if [ -f "/.snapshots/68/grub-snapshot.cfg" ]; then
      source "/.snapshots/68/grub-snapshot.cfg"
    fi
    if [ x$snapshot_found != xtrue ]; then
      submenu "Not Found" { true; }
    fi
  }
fi
~#

C'est snapper qui se charge de modifier le fichier /.snapshots/grub-snapshot.cfg et de rajouter le fichier /.snapshots/<id>/grub-snapshot.cfg à la prise de l'instantané <id> :

~# ls /.snapshots/70/
grub-snapshot.cfg  info.xml  snapshot
~# cat grub-snapshot.cfg 

  if [ -f "/.snapshots/70/snapshot/boot/grub2/grub.cfg" ]; then
    snapshot_found=true
    saved_subvol=$btrfs_subvol
    menuentry  " openSUSE Tumbleweed  (4.11.8-2,2017-08-17T19:17)" "/.snapshots/70/snapshot" "/@/.snapshots/70/snapshot" {
        btrfs_subvol="$3"
        extra_cmdline="rootflags=subvol=$3"
        export extra_cmdline
        snapshot_num=70
        export snapshot_num
        configfile "$2/boot/grub2/grub.cfg"
        btrfs_subvol=$saved_subvol
      }
  fi

~#

Ainsi, pas besoin de régénérer le grub.cfg pour rajouter des instantanés en lecture seule dans les menus de GRUB.

Concernant, le menu principal, dans /boot/grub2/grub.cfg justement :

### BEGIN /etc/grub.d/10_linux ###
menuentry 'openSUSE Tumbleweed'  --class opensuse --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-simple-4dcb59ef-2749-4017-94f4-d046a2cd7391' {
    load_video
    set gfxpayload=keep
    insmod gzio
    insmod part_msdos
    insmod btrfs
    set root='hd0,msdos2'
    if [ x$feature_platform_search_hint = xy ]; then
      search --no-floppy --fs-uuid --set=root --hint-bios=hd0,msdos2 --hint-efi=hd0,msdos2 --hint-baremetal=ahci0,msdos2 --hint='hd0,msdos2'  4dcb59ef-2749-4017-94f4-d046a2cd7391
    else
      search --no-floppy --fs-uuid --set=root 4dcb59ef-2749-4017-94f4-d046a2cd7391
    fi
    echo    'Chargement de Linux 4.11.8-2-default…'
    linux   /boot/vmlinuz-4.11.8-2-default root=UUID=4dcb59ef-2749-4017-94f4-d046a2cd7391  ${extra_cmdline} resume=/dev/sda1 quiet showopts acpi_backlight=native
    echo    'Chargement du disque mémoire initial…'
    initrd  /boot/initrd-4.11.8-2-default
}

On remarque que, pour cette entrée, les chemins vers le noyau et l'initrd ressemblent à ce que l'on peut avoir avec d'autres systèmes de fichiers : il n'y a pas de référence explicite à un sous-volume.

Pilote Btrfs : à la croisée des chemins

Au niveau de GRUB en lui-même, la série de patchs modifie le pilote Btrfs pour :

  • fournir les commandes :
    • btrfs-get-default-subvol : équivalent btrfs subvolume get-default.
    • btrfs-info : équivalent à btrfs filesystem show.
    • btrfs-list-subvols : une version simplifiée de btrfs subvolume list.
    • btrfs-mount-subvol : permet de monter des sous-volumes.
  • permettre de définir les variables :
    • btrfs_subvol : nom du sous-volume à monter ; vous avez vu que c'est utilisé dans les /.snapshots/*/grub-snapshot.cfg.
    • btrfs_subvolid : id du sous-volume à monter.
    • btrfs_relative_path : booléen indiquant à GRUB s'il doit utiliser des chemins relatifs.

Intéressons-nous à ce btrfs_relative_path et à ce qu'il implique.

Yellow Brick Road

GRUB gère les sous-volumes Btrfs comme des répertoires.

La version upstream utilise le nom complet des sous-volumes comme un chemin absolu. Par exemple : /@/.snapshots/1/snapshot/boot/grub2/grub.cfg, /@/.snapshots/1/snapshot/boot/vmlinuz-4.11.8-1-default, /@/.snapshots/1/boot/initrd-4.11.8-1-default, … Cela a l'avantage d'être clair et similaire à ce qui est fait pour les autres systèmes de fichiers qui n'ont pas de notion de sous-volume.

Le problème, c'est, si j'ai bien compris, que cela nous oblige à spécifier un sous-volume précis dans la configuration de GRUB. Or, dans le cas d'une restauration, souvenez-vous : on change de sous-volume. Du coup, avec un GRUB vanilla, on est bien obligé de régénérer sa configuration – le grub.cfg et même le core.img qui va chercher le grub.cfg – pour faire une restauration qui marche ! Du moins, une restauration telle qu'on l'a faite.

openSUSE a une autre façon de faire, du moins si btrfs_relative_path est à y, ce qui est le cas par défaut.

L'idée est de faire en sorte que GRUB n'utilise plus un chemin complet, mais un chemin relatif par rapport au sous-volume par défaut. Avec cette façon de faire, /@/.snapshots/1/snapshot/boot/grub2/grub.cfg devient /boot/grub2/grub.cfg du sous-volume par défaut.

Cela évite de devoir modifier le fichier grub.cfg et le core.img pour chaque restauration.

Et cela explique pourquoi un simple snapper rollback – ou les commandes Btrfs équivalentes – marche sur openSUSE, mais pas sur d'autres distributions. Si j'ai bien compris.

Bugs connus

bugs

Je vous en mets deux que j'ai eu l'occasion de rencontrer :

  • boo#1048088 : la commande btrfs-list-subvol ne liste que les sous-volumes descendant immédiatement de FS_TREE. Cela marche sur les anciennes installations qui n'utilisent pas @, mais pour les nouvelles… ben ça affiche juste @. Pas bien méchant. Bug spécifique à openSUSE, vu qu'il n'y a pas de commande btrfs-list-subvol dans le projet upstream.
  • boo#1049994 : l'accès au sous-volume @/.snapshots/ n'est pas très propre ; cela empêche par exemple de booter correctement sur des snapshots qui auraient été transférés avec btrfs send et btrfs receive ; on reviendra sur cela quand on parlera de sauvegarde incrémentale. Le bug est en partie upstream si j'ai bien compris.

Mais quand est-ce qu'on merge ?

git

Ma question favorite :-)

Le fait est que l'upstream reste sur sa position : pas de chemin relatif pour Btrfs, seulement un chemin absolu. Du coup status quo depuis 3 ans :-/ C'est dommage.

Après on peut aussi se retourner vers openSUSE : est-ce vraiment une bonne solution que d'utiliser des chemins relatifs dans GRUB2 ?

Bref : c'est pas hyper bien parti, mais espérons que les gourous de GRUB arrivent un jour à une solution qui satisfasse tout le monde !

Liens

Sur Snapper :

Sur GRUB :

  • # Merci

    Posté par  . Évalué à 4.

    Excellents journaux, qui montrent que la diversité du libre a du bon. Je me demande si la gourmandise de btrfs - et donc son apétence pour de gros disques - n'a pas été son plus gros frein, dans un milieu où l'on souligne souvent la frugalité des logiciels.

    Pour ma part, j'ai effectivement essayé btrfs une fois, et j'ai rempli mon disque très vite. En fouillant, j'ai compris qu'il fallait lancer manuellement une commande pour dire à btrfs de vider sa corbeille, c'était un no-go pour ma part. Revenu à ext4, je n'y ai plus touché.

    ⚓ À g'Auch TOUTE! http://afdgauch.online.fr

    • [^] # Re: Merci

      Posté par  (site web personnel) . Évalué à 4.

      En fouillant, j'ai compris qu'il fallait lancer manuellement une commande pour dire à btrfs de vider sa corbeille

      J'ai du btrfs et je n'ai jamais eu ce phénomène. En fait je ne vois pas de quelle commande tu parles. A priori, tu as du installer autre chose par dessus btrfs ou quelque chose comme ça.

      • [^] # Re: Merci

        Posté par  . Évalué à 1.

        j'ai été étonné aussi, j'ai un gros volume BTRFS RAID6 (oui, je suis un peu fou…) et je n'ai pas constaté ce problème,

        après une petite recherche sur le nain ternet, il semble qu'il y ait (eu ?) un bug qui faisait que les fichiers effacés allaient dans une corbeille qu'on ne pouvait pas vider lorsqu'elle est située à la racine d'un sous-volume, je n'ai pas lu en détail les explications et je ne sais pas si c'est toujours d'actualité

        Envoyé depuis mon Archlinux

  • # SNAPPER EXT4?

    Posté par  (site web personnel) . Évalué à 2.

    oua cette partie m'a fait réver. Des snapshot sur tout système de fichier, ou en tout cas sur ext4.
    tu écris que c'est cassé , c'est une blague ; et vu tes recherches je fais totalement confiance à ton post

    mais alors est-ce que tu pense qu'il y a espoir de le corriger?
    (la page snapper -> overview dit "Works with btrfs, ext4 and thin-provisioned LVM volumes")

    est-ce que tu pense que snapper sera un jour applicable à d'autres FS encore?

    • [^] # Re: SNAPPER EXT4?

      Posté par  (site web personnel) . Évalué à 1.

      mais alors est-ce que tu pense qu'il y a espoir de le corriger?
      (la page snapper -> overview dit "Works with btrfs, ext4 and thin-provisioned LVM volumes")

      Concernant ext4, l'implémentation faite dans snapper dépend d'une version d'ext4 patchée, réalisée dans le cadre d'un projet appelé Next4. Ce projet n'a visiblement pas abouti : wiki mort, rien de mergé, pas trouvé de trace du code. Donc à moins d'un miracle, je ne vois pas comment snapper peut marcher avec de l'ext4…

      Je doute un peu que cela soit réparé, il n'y a pas trop d'activité sur #331.

      est-ce que tu pense que snapper sera un jour applicable à d'autres FS encore?

      Je n'ai pas regardé le code de snapper pour voir comment c'est fait, ce dont il a besoin et comment cela peut évoluer. En tout cas, je n'ai pas l'impression qu'il y ait actuellement une dynamique pour rajouter le support d'autres systèmes de fichiers.

  • # Coquille

    Posté par  (site web personnel) . Évalué à 1. Dernière modification le 25 août 2017 à 07:58.

    Il y a une petite coquille au niveau du contenu du fichier /etc/grub.d/80_suse_btrfs_snapshot :

    #! /bin/sh
    set -e
    if [ "x${SUSE_BTRFS_SNAPSHOT_BOOTING}" = "xtrue" ] &&
       [ "x${GRUB_FS}" = "xbtrfs" ] ; then
    #      ici vvvv
        cat << \EOF
    if [ -f "/.snapshots/grub-snapshot.cfg" ]; then
      source "/.snapshots/grub-snapshot.cfg"
    fi
    EOF
    fi

    J'avais rajouté un \ dans mon texte car le colorateur syntaxique markdown de gedit avait un peu de mal avec cet EOF. Le vrai script est bien sûr sans \:

    #! /bin/sh
    set -e
    if [ "x${SUSE_BTRFS_SNAPSHOT_BOOTING}" = "xtrue" ] &&
       [ "x${GRUB_FS}" = "xbtrfs" ] ; then
        cat <<EOF
    if [ -f "/.snapshots/grub-snapshot.cfg" ]; then
      source "/.snapshots/grub-snapshot.cfg"
    fi
    EOF
    fi

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.