PROJET AUTOBLOG


®om's blog

Site original : ®om's blog

⇐ retour index

Mise à jour

Mise à jour de la base de données, veuillez patienter...

Lecture différée de la webcam d’un Rasberry Pi

lundi 20 janvier 2014 à 11:20

raspi

L’objectif de ce billet est de parvenir à lire le flux provenant de la caméra d’un Raspberry Pi avec un décalage de quelques secondes (plutôt qu’en direct), avec les outils dédiés que sont raspivid et omxplayer.

Contexte

Là où je travaille, il y a un babyfoot. Nous avons récemment décidé de l’informatiser un peu pour avoir la détection et le ralenti des buts. Entre autres, un Raspberry Pi a été installé avec sa caméra au-dessus du terrain de manière à fournir une vue aérienne.

raspivid permet d’afficher en direct ce que la caméra filme. Mais l’intérêt est faible dans notre cas : le direct, nous l’avons déjà sous les yeux.

Il est bien plus utile d’avoir un "direct" différé de quelques secondes : lors d’un but ou d’une action litigieuse, il suffit de tourner la tête pour revoir ce qu’il vient de se passer (à vitesse réelle).

Je me suis intéressé à faire fonctionner ce cas d’usage. Je vais détailler ici les principes et les problèmes rencontrés.

Un simple tube

La première idée fut de brancher le flux H.264 que produit raspivid sur l’entrée de omxplayer, qui serait démarré quelques secondes plus tard.

Premier problème, omxplayer ne semblait pas savoir lire sur son entrée standard. Ce n’est pas très gênant, il suffit d’utiliser un tube nommé grâce à mkfifo. En effet :

printf 'a\nb\nc\n' | grep b

peut être remplacé par :

# terminal 1
mkfifo /tmp/fifo
printf 'a\nb\nc\n' > /tmp/fifo
# terminal 2
< /tmp/fifo grep b

Mais en fait, il y a plus direct : omxplayer n’est qu’un script wrapper pour le vrai omxplayer.bin, c’est lui qui empêchait la lecture sur l’entrée standard. Il suffit juste d’exporter la variable qui-va-bien et d’appeler omxplayer.bin directement :

export LD_LIBRARY_PATH="/opt/vc/lib:/usr/lib/omxplayer"
omxplayer.bin 

Cependant, contrairement à ce que proposent beaucoup de commandes shell, omxplayer.bin ne prévoit pas explicitement de lire sur son entrée standard, il attend obligatoirement un fichier en paramètre. Donnons-lui donc comme fichier /dev/stdin !

raspivid -t 0 -w 1280 -h 720 -fps 25 -n -o - | omxplayer.bin /dev/stdin

Vu la durée de démarrage d’omxplayer.bin, pas besoin de retarder son lancement, la vidéo sera bien décalée de quelques secondes.

Le problème, c’est que le buffer lié au tube est très limité (man 7 pipe) : il sera très vite plein, bloquant totalement l’enregistrement et la lecture de la vidéo.

Avec un buffer

Nous avons besoin d’un buffer plus important. Pour cela, nous pouvons utiliser mbuffer, ici avec une taille de 10Mo :

raspivid  | mbuffer -m 10m | omxplayer.bin /dev/stdin

Et là, cela semble fonctionner.

Pour décaler un peu plus la lecture par omxplayer.bin, il est possible d’utiliser les commandes groupées (man bash) pour ajouter un appel à sleep avant le démarrage :

raspivid  | mbuffer -m 10m | { sleep 3; omxplayer.bin /dev/stdin; }

raspivid est censé enregistrer à 25 fps (-fps 25) et omxplayer nous indique dans la console qu’il lit à 25 fps.

Cependant, en réalité, le décalage n’est pas constant : il augmente petit à petit au fil des minutes, et le buffer se remplit légèrement plus vite qu’il ne se vide. La lecture consomme moins d’images que n’en produit l’enregistrement, comme si le débit d’images de l’enregistrement était supérieur à celui de lecture.

Il y a donc un manque d’exactitude (à ne pas confondre avec un manque de précision) dans le nombre d’images enregistrées et/ou lues par seconde.

Si nous tentons d’enregistrer à un débit légèrement inférieur (24 fps), c’est le contraire : le retard est rattrapé progressivement jusqu’à fournir une lecture en direct.

Comme le débit d’images est la seule information temporelle disponible et qu’elle est inexacte, il semble impossible de contrecarrer cette variation de délai.

Information temporelle

Mais en réalité, ce n’est pas la seule information temporelle dont nous disposons : nous savons que le flux est en direct.

Comment exploiter cette information ? Pour le comprendre, il suffit d’enregistrer à un débit d’images très faible (-fps 5) et de le lire toujours à 25 fps.

Si la lecture provient d’un fichier, alors la vidéo passe en accéléré. Par contre, si la lecture sort de la webcam en direct, alors la vidéo passe à vitesse normale mais à 5 fps : le lecteur a beau vouloir lire 25 images par seconde, s’il n’en reçoit que 5 chaque seconde, il n’a pas d’autre choix que de lire à 5 fps.

Ainsi, sans même connaître sa valeur réelle exacte, nous parvenons à obtenir le même débit d’images à l’enregistrement qu’à la lecture.

Mais comme nous l’avons vu, avec un débit d’images d’enregistrement inférieur, le délai introduit se réduira inexorablement (le retard sera rattrapé). Ce que nous voulons éviter : nous voulons un délai constant.

Delay

Nous avons cependant avancé, car maintenant, si nous disposions d’une commande qui retarde ce qui sort de raspivid pour le donner à omxplayer x secondes plus tard, et que nous enregistrons à un débit d’images légèrement inférieur à celui de la lecture, alors omxplayer rattrapera le retard pour parvenir au direct… décalé de x secondes. Exactement ce que nous voulons !

J’ai donc demandé sur stackoverflow si une telle commande existait, ce qui ne semblait pas être le cas.

Je l’ai donc implémentée (sous licence GPLv3) :

git clone http://git.rom1v.com/delay.git
cd delay
make && sudo make install

(ou sur github)

Elle permet de décaler tout ce qui arrive sur stdin d’un délai constant pour le sortir sur stdout :

delay [-b <dtbufsize>] <delay>

Elle est donc très générique, et n’a aucun lien avec le fait que le flux soit une vidéo.

Elle fonctionne aussi très bien pour différer la lecture de la webcam dans VLC sur un pc classique :

ffmpeg -an -s 320x240 -f video4linux2 -i /dev/video0 -f mpeg2video -b 1M - |
  delay 2s | vlc -

Nous pourrions penser qu’il suffit de faire la même chose avec raspivid et omxplayer, avec un débit d’images légèrement inférieur pour l’enregistrement (24 fps) :

raspivid -t 0 -w 1280 -h 720 -fps 24 -n -o - |
  delay -b10m 4s |
  omxplayer.bin /dev/stdin

Malheureusement, avec omxplayer, ce n’est pas si simple.

Initialisation immédiate

En effet, l’initialisation d’omxplayer pour une lecture vidéo est très longue (plusieurs secondes), et surtout, elle ne débute que lorsque une partie suffisamment importante de la vidéo à lire est reçue (les headers ne suffisent pas). Décaler la vidéo de x secondes décale également l’initialisation de x secondes, ajoutant d’autant plus de décalage.

Certes, le retard supplémentaire sera rattrapé progressivement, mais cela prendra du temps (environ 1 image chaque seconde, soit 1 seconde toutes les 25 secondes). Pour obtenir le délai désiré dès le départ, ce problème doit être évité.

Une solution de contournement consiste à passer les premiers (méga-)octets sortis de raspivid directement à omxplayer.bin, et de ne différer que le reste avec delay. De cette manière, les premières images seront lues immédiatement, permettant au lecteur de s’initialiser, alors que la suite sera différée.

Grâce aux commandes groupées de bash (encore elles), c’est très simple :

raspivid -t 0 -w 1280 -h 720 -fps 24 -n -o - |
  { head -c10M; delay -b10m 4s; } |
  omxplayer.bin /dev/stdin

La commande head va passer immédiatement les 10 premiers méga-octets à omxplayer.bin, puis la commande delay prendra le relai. Ainsi, l’initialisation aura déjà eu lieu quand les premiers octets sortiront de delay.

À part les premières secondes un peu chaotiques, le flux vidéo sera alors bien diffusé en différé avec un délai constant (testé sur 24 heures).

Conclusion

Nous avons donc bricolé une solution qui permet un replay différé en continu sur un Raspberry Pi.

Lecture différée de la webcam d'un Raspberry Pi

lundi 20 janvier 2014 à 10:20

L’objectif de ce billet est de parvenir à lire le flux provenant de la caméra d’un Raspberry Pi avec un décalage de quelques secondes (plutôt qu’en direct), avec les outils dédiés que sont raspivid et omxplayer.

raspi

Contexte

Là où je travaille, il y a un babyfoot. Nous avons récemment décidé de l’informatiser un peu pour avoir la détection et le ralenti des buts. Entre autres, un Raspberry Pi a été installé avec sa caméra au-dessus du terrain de manière à fournir une vue aérienne.

raspivid permet d’afficher en direct ce que la caméra filme. Mais l’intérêt est faible dans notre cas : le direct, nous l’avons déjà sous les yeux.

Il est bien plus utile d’avoir un “direct” différé de quelques secondes : lors d’un but ou d’une action litigieuse, il suffit de tourner la tête pour revoir ce qu’il vient de se passer (à vitesse réelle).

Je me suis intéressé à faire fonctionner ce cas d’usage. Je vais détailler ici les principes et les problèmes rencontrés.

Un simple tube

La première idée fut de brancher le flux H.264 que produit raspivid sur l’entrée de omxplayer, qui serait démarré quelques secondes plus tard.

Premier problème, omxplayer ne semblait pas savoir lire sur son entrée standard. Ce n’est pas très gênant, il suffit d’utiliser un tube nommé grâce à mkfifo. En effet :

printf 'a\nb\nc\n' | grep b

peut être remplacé par :

# terminal 1
mkfifo /tmp/fifo
printf 'a\nb\nc\n' > /tmp/fifo

# terminal 2
< /tmp/fifo grep b

Mais en fait, il y a plus direct : omxplayer n’est qu’un script wrapper pour le vrai omxplayer.bin, c’est lui qui empêchait la lecture sur l’entrée standard. Il suffit juste d’exporter la variable qui-va-bien et d’appeler omxplayer.bin directement :

export LD_LIBRARY_PATH="/opt/vc/lib:/usr/lib/omxplayer"
omxplayer.bin …

Cependant, contrairement à ce que proposent beaucoup de commandes shell, omxplayer.bin ne prévoit pas explicitement de lire sur son entrée standard, il attend obligatoirement un fichier en paramètre. Donnons-lui donc comme fichier /dev/stdin !

raspivid -t 0 -w 1280 -h 720 -fps 25 -n -o - | omxplayer.bin /dev/stdin

Vu la durée de démarrage d’omxplayer.bin, pas besoin de retarder son lancement, la vidéo sera bien décalée de quelques secondes.

Le problème, c’est que le buffer lié au tube est très limité (man 7 pipe) : il sera très vite plein, bloquant totalement l’enregistrement et la lecture de la vidéo.

Avec un buffer

Nous avons besoin d’un buffer plus important. Pour cela, nous pouvons utiliser mbuffer, ici avec une taille de 10Mo :

raspivid … | mbuffer -m 10m | omxplayer.bin /dev/stdin

Et là, cela semble fonctionner.

Pour décaler un peu plus la lecture par omxplayer.bin, il est possible d’utiliser les commandes groupées (man bash) pour ajouter un appel à sleep avant le démarrage :

raspivid … | mbuffer -m 10m | { sleep 3; omxplayer.bin /dev/stdin; }

raspivid est censé enregistrer à 25 fps (-fps 25) et omxplayer nous indique dans la console qu’il lit à 25 fps.

Cependant, en réalité, le décalage n’est pas constant : il augmente petit à petit au fil des minutes, et le buffer se remplit légèrement plus vite qu’il ne se vide. La lecture consomme moins d’images que n’en produit l’enregistrement, comme si le débit d’images de l’enregistrement était supérieur à celui de lecture.

Il y a donc un manque d’exactitude (à ne pas confondre avec un manque de précision) dans le nombre d’images enregistrées et/ou lues par seconde.

accuracy_precision

Si nous tentons d’enregistrer à un débit légèrement inférieur (24 fps), c’est le contraire : le retard est rattrapé progressivement jusqu’à fournir une lecture en direct.

Comme le débit d’images est la seule information temporelle disponible et qu’elle est inexacte, il semble impossible de contrecarrer cette variation de délai.

Information temporelle

Mais en réalité, ce n’est pas la seule information temporelle dont nous disposons : nous savons que le flux est en direct.

Comment exploiter cette information ? Pour le comprendre, il suffit d’enregistrer à un débit d’images très faible (-fps 5) et de le lire toujours à 25 fps.

Si la lecture provient d’un fichier, alors la vidéo passe en accéléré. Par contre, si la lecture sort de la webcam en direct, alors la vidéo passe à vitesse normale mais à 5 fps : le lecteur a beau vouloir lire 25 images par seconde, s’il n’en reçoit que 5 chaque seconde, il n’a pas d’autre choix que de lire à 5 fps.

Ainsi, sans même connaître sa valeur réelle exacte, nous parvenons à obtenir le même débit d’images à l’enregistrement qu’à la lecture.

Mais comme nous l’avons vu, avec un débit d’images d’enregistrement inférieur, le délai introduit se réduira inexorablement (le retard sera rattrapé). Ce que nous voulons éviter : nous voulons un délai constant.

Delay

Nous avons cependant avancé, car maintenant, si nous disposions d’une commande qui retarde ce qui sort de raspivid pour le donner à omxplayer x secondes plus tard, et que nous enregistrons à un débit d’images légèrement inférieur à celui de la lecture, alors omxplayer rattrapera le retard pour parvenir au direct… décalé de x secondes. Exactement ce que nous voulons !

J’ai donc demandé sur stackoverflow si une telle commande existait, ce qui ne semblait pas être le cas.

Je l’ai donc implémentée (sous licence GPLv3) :

git clone http://git.rom1v.com/delay.git
cd delay
make && sudo make install

(ou sur github)

Elle permet de décaler tout ce qui arrive sur stdin d’un délai constant pour le sortir sur stdout :

delay [-b <dtbufsize>] <delay>

Elle est donc très générique, et n’a aucun lien avec le fait que le flux soit une vidéo.

Elle fonctionne aussi très bien pour différer la lecture de la webcam dans VLC sur un pc classique :

ffmpeg -an -s 320x240 -f video4linux2 -i /dev/video0 -f mpeg2video -b 1M - |
  delay 2s | vlc -

Nous pourrions penser qu’il suffit de faire la même chose avec raspivid et omxplayer, avec un débit d’images légèrement inférieur pour l’enregistrement (24 fps) :

raspivid -t 0 -w 1280 -h 720 -fps 24 -n -o - |
  delay -b10m 4s |
  omxplayer.bin /dev/stdin

Malheureusement, avec omxplayer, ce n’est pas si simple.

Initialisation immédiate

En effet, l’initialisation d’omxplayer pour une lecture vidéo est très longue (plusieurs secondes), et surtout, elle ne débute que lorsque une partie suffisamment importante de la vidéo à lire est reçue (les headers ne suffisent pas). Décaler la vidéo de x secondes décale également l’initialisation de x secondes, ajoutant d’autant plus de décalage.

Certes, le retard supplémentaire sera rattrapé progressivement, mais cela prendra du temps (environ 1 image chaque seconde, soit 1 seconde toutes les 25 secondes). Pour obtenir le délai désiré dès le départ, ce problème doit être évité.

Une solution de contournement consiste à passer les premiers (méga-)octets sortis de raspivid directement à omxplayer.bin, et de ne différer que le reste avec delay. De cette manière, les premières images seront lues immédiatement, permettant au lecteur de s’initialiser, alors que la suite sera différée.

Grâce aux commandes groupées de bash (encore elles), c’est très simple :

raspivid -t 0 -w 1280 -h 720 -fps 24 -n -o - |
  { head -c10M; delay -b10m 4s; } |
  omxplayer.bin /dev/stdin

La commande head va passer immédiatement les 10 premiers méga-octets à omxplayer.bin, puis la commande delay prendra le relai. Ainsi, l’initialisation aura déjà eu lieu quand les premiers octets sortiront de delay.

À part les premières secondes un peu chaotiques, le flux vidéo sera alors bien diffusé en différé avec un délai constant (testé sur 24 heures).

Conclusion

Nous avons donc bricolé une solution qui permet un replay différé en continu sur un Raspberry Pi.

Duplicity : des backups incrémentaux chiffrés

mercredi 14 août 2013 à 16:59

backup

Quiconque s’auto-héberge doit maintenir un système de sauvegarde de son serveur, permettant de tout remettre en place dans le cas d’un crash de disque dur, d’un piratage ou d’un cambriolage.

Objectifs

Il est nécessaire de sauvegarder à la fois des fichiers (les mails, les services hébergés, les fichiers de config…) et le contenu de bases de données (associées aux services hébergés).

Le système de sauvegarde doit conserver les archives durant un certain temps (par exemple 2 mois). En effet, un piratage ou une erreur de manipulation peuvent n’être détectés que quelques jours plus tard : il est important de pouvoir restaurer un état antérieur.

La sauvegarde doit être régulière (par exemple quotidienne).

Seule une infime partie des données étant modifiées d’un jour à l’autre, la sauvegarde a tout intérêt à être incrémentale.

Pour résister aux cambriolages, une sauvegarde doit être réalisée sur (au moins) une machine distante. Il est donc préférable que ces données soient chiffrées.

Duplicity

Vous l’aurez compris, duplicity répond à tous ces besoins.

Je ne vais pas expliquer tout ce qu’il sait faire, mais plutôt comment je l’utilise et pourquoi.

Mes choix d’utilisation

Sauvegarde locale

Personnellement, je n’effectue qu’une sauvegarde locale dans une tâche cron, c’est-à-dire que les fichiers de backups sont stockés sur le serveur lui-même.

En effet, une sauvegarde automatique vers un serveur distant, par SSH par exemple, nécessiterait une clé privée en clair sur le serveur. Cette configuration ne résisterait pas à certains piratages : une intrusion sur le serveur donnerait également accès aux sauvegardes, permettant à un pirate d’effacer à la fois les données et les backups.

C’est donc une autre machine, à l’initiative de la connexion, qui rapatrie les backups. Évidemment, elle ne doit pas synchroniser localement les backups supprimés du serveur (elle serait vulnérable à la suppression des backups par un pirate), mais doit plutôt supprimer les anciennes sauvegardes de sa propre initiative.

Chiffrement

Duplicity utilise GPG pour le chiffrement, permettant :

Le premier choix nécessite à la fois quelque chose que je possède (la clé, de forte entropie) et quelque chose que je connais (la passphrase, de plus faible entropie). Le second ne nécessite que la passphrase à retenir.

L’utilisation d’une clé privée autorise donc une meilleure sécurité, notamment si vous souhaitez envoyer vos backups sur un serveur américain.

Néanmoins, les backups sont surtout utiles lors de la perte de données, notamment dans le cas d’un cambriolage, où la clé GPG a potentiellement également disparu. Et les sauvegardes distantes ne seront d’aucune utilité sans la clé…
Il peut donc être moins risqué d’opter, comme je l’ai fait, pour une simple passphrase.

À vous de placer le curseur entre la protection de vos données et le risque de ne plus pouvoir les récupérer.

Installation

Sur une Debian :

sudo apt-get install duplicity

Fonctionnement

Duplicity effectue des sauvegardes complètes et incrémentales. Les sauvegardes incrémentales nécessitent toutes les sauvegardes depuis la dernière complète pour être restaurées.
Personnellement, j’effectue une sauvegarde complète tous les mois, et une incrémentale tous les jours.

Pour choisir le mode :

Exemple (à exécuter en root pour avoir accès à tous les fichiers) :

duplicity / file:///var/backups/duplicity/ --include-globbing-filelist filelist.txt --exclude '**'

Duplicity va sauvegarder à partir de la racine ("/") tous les fichiers selon les règles d’inclusion et d’exclusion définies dans filelist.txt. Ce fichier contient simplement la liste des fichiers et répertoires à sauvegarder, ainsi que ceux à exclure. Par exemple :

/usr/local/bin/
/home/rom/Maildir/
/home/rom/.procmailrc
- /var/www/blog/wp-content/cache/
/var/www/blog/

Attention : les fichiers et répertoires à exclure doivent apparaître avant l’inclusion d’un répertoire parent. En effet, duplicity s’arrête à la première règle qui matche un chemin donné pour déterminer s’il doit l’inclure ou l’exclure.

Pour restaurer :

duplicity restore file:///var/backups/duplicity/ /any/directory/

(utiliser l’option -t pour restaurer à une date particulière)

Pour supprimer les anciennes sauvegardes (ici de plus de 2 mois) :

duplicity remove-older-than 2M file:///var/backups/duplicity/ --force

Bases de données

Tout comme pour les fichiers, il est préférable de sauvegarder incrémentalement les bases de données (seule une toute petite partie des données change d’un jour à l’autre).

Une première solution serait d’utiliser la fonctionnalité-qui-va-bien de votre SGBD.

Mais si le contenu de vos bases de données ne dépasse pas quelques Go (ce qui est très probable pour de l’auto-hébergement), duplicity permet de faire beaucoup plus simple.

Il suffit en effet de générer un dump complet des bases de données vers des fichiers .sql et d’inclure leur chemin dans la liste des fichiers à sauvegarder. Et là, c’est magique, duplicity va ne sauvegarder que les parties de ces (gros) fichiers qui ont changées, grâce à rsync et à son algorithme qui utilise des rolling checksums.

Bien sûr, il ne faut pas compresser ces fichiers avant de les donner à manger à duplicity (sinon l’intégralité du fichier risque de changer) ; c’est lui qui va s’en charger. De même, il vaut mieux éviter d’inclure dans les fichies SQL des informations liées au dump, comme sa date de génération.

Pour exporter une base de données MySQL par exemple :

mysql -uroot -ppassword --skip-comments -ql my_database > my_database.sql

Script

Il reste donc à écrire un script qui exporte les bases de données et qui appelle duplicity avec la liste de ce qu’il y a à sauvegarder.

Voici un prototype, à sauvegarder dans /usr/local/bin/backup :

#!/bin/bash
BACKUP_HOME=/var/backups
TMP_DBDIR="$BACKUP_HOME/dbdump"
BACKUP_DIR="$BACKUP_HOME/duplicity"
MYSQLPW=mon_password_mysql
PASSPHRASE=ma_passphrase_de_chiffrement_des_backups
DATABASES='blog autre_base'
FILELIST="/usr/local/bin/
/home/rom/Maildir/
/home/rom/.procmailrc
- /var/www/blog/wp-content/cache/
/var/www/blog/
$TMP_DBDIR/"

# databases
mkdir -p "$TMP_DBDIR"
for dbname in $DATABASES
do
  printf "## Dump database $dbname...\n"
  mysqldump -uroot -p"$MYSQLPW" --skip-comments -ql "$dbname" \
    > "$TMP_DBDIR/$dbname.sql"
done

# duplicity
printf '## Backup using duplicity...\n'
unset mode
[ "$1" = full ] && mode=full && printf '(force full backup)\n'
mkdir -p "$BACKUP_DIR"
export PASSPHRASE
duplicity $mode / file://"$BACKUP_DIR"/ \
  --include-globbing-filelist <(echo "$FILELIST") --exclude '**'

printf '## Delete old backups\n'
duplicity remove-older-than 2M file://"$BACKUP_DIR"/ --force

# backups are encrypted, we can make them accessible
chmod +r "$BACKUP_DIR"/*.gpg

# remove temp files
rm "$TMP_DBDIR"/*.sql

Une fois configuré, ne pas oublier de tester : exécuter le script et restaurer les données dans un répertoire de test, puis vérifier que tout est OK. Cette vérification doit être effectuée de temps en temps : il serait dommage de s’apercevoir, lorsqu’on en a besoin, que les backups sont inutilisables ou qu’un répertoire important a été oublié.

Cron

Pour démarrer automatiquement une sauvegarde complète le premier jour du mois et une incrémentale tous les autres jours, cron est notre ami :

sudo crontab -e

Ajouter les lignes :

0 1 1    * * /usr/local/bin/backup full
0 1 2-31 * * /usr/local/bin/backup

La première colonne correspond aux minutes, la deuxième aux heures : le script sera donc exécuté à 1h du matin. La 3e correspond au numéro du jour dans le mois. Les deux suivantes sont le numéro du mois dans l’année et le jour de la semaine.

Il peut être préférable d’exécuter le script en priorité basse :

0 1 1    * * nice -15 ionice -c2 /usr/local/bin/backup full
0 1 2-31 * * nice -15 ionice -c2 /usr/local/bin/backup

Copies

Il ne reste plus qu’à effectuer des copies des fichiers de backups ailleurs.

À partir d’une autre machine, le plus simple est d’utiliser rsync (sans l’option --delete !) :

rsync -rvP --partial-dir=/my/local/tmpbackup --ignore-existing --stats -h server:/var/backups/duplicity/ /my/local/backup/

--ignore-existing évite de récupérer des modifications malicieuses des backups sur le serveur (ils ne sont pas censés être modifiés). Du coup, il faut aussi faire attention à sauvegarder les transferts partiels ailleurs (--partial-dir), sans quoi ils ne se termineront jamais.

Pour supprimer les anciens backups sur cette machine, c’est la même commande que sur le serveur :

duplicity remove-older-than 2M file:///my/local/backup/ --force

Conclusion

La génération de sauvegardes à la fois incrémentales et chiffrées, y compris pour les bases de données, font de duplicity une solution de backup idéale pour l’auto-hébergement.

Je l’utilise depuis plusieurs mois, et j’en suis très satisfait (même si je n’ai pas encore eu besoin de restaurer les backups en situation réelle).

À vos backups !

Duplicity : des backups incrémentaux chiffrés

mercredi 14 août 2013 à 14:59

Quiconque s’auto-héberge doit maintenir un système de sauvegarde de son serveur, permettant de tout remettre en place dans le cas d’un crash de disque dur, d’un piratage ou d’un cambriolage.

Objectifs

Il est nécessaire de sauvegarder à la fois des fichiers (les mails, les services hébergés, les fichiers de config…) et le contenu de bases de données (associées aux services hébergés).

Le système de sauvegarde doit conserver les archives durant un certain temps (par exemple 2 mois). En effet, un piratage ou une erreur de manipulation peuvent n’être détectés que quelques jours plus tard : il est important de pouvoir restaurer un état antérieur.

La sauvegarde doit être régulière (par exemple quotidienne).

Seule une infime partie des données étant modifiées d’un jour à l’autre, la sauvegarde a tout intérêt à être incrémentale.

Pour résister aux cambriolages, une sauvegarde doit être réalisée sur (au moins) une machine distante. Il est donc préférable que ces données soient chiffrées.

Duplicity

Vous l’aurez compris, duplicity répond à tous ces besoins.

Je ne vais pas expliquer tout ce qu’il sait faire, mais plutôt comment je l’utilise et pourquoi.

Mes choix d’utilisation

Sauvegarde locale

Personnellement, je n’effectue qu’une sauvegarde locale dans une tâche cron, c’est-à-dire que les fichiers de backups sont stockés sur le serveur lui-même.

En effet, une sauvegarde automatique vers un serveur distant, par SSH par exemple, nécessiterait une clé privée en clair sur le serveur. Cette configuration ne résisterait pas à certains piratages : une intrusion sur le serveur donnerait également accès aux sauvegardes, permettant à un pirate d’effacer à la fois les données et les backups.

C’est donc une autre machine, à l’initiative de la connexion, qui rapatrie les backups. Évidemment, elle ne doit pas synchroniser localement les backups supprimés du serveur (elle serait vulnérable à la suppression des backups par un pirate), mais doit plutôt supprimer les anciennes sauvegardes de sa propre initiative.

Chiffrement

Duplicity utilise GPG pour le chiffrement, permettant :

Le premier choix nécessite à la fois quelque chose que je possède (la clé, de forte entropie) et quelque chose que je connais (la passphrase, de plus faible entropie). Le second ne nécessite que la passphrase à retenir.

L’utilisation d’une clé privée autorise donc une meilleure sécurité, notamment si vous souhaitez envoyer vos backups sur un serveur américain.

Néanmoins, les backups sont surtout utiles lors de la perte de données, notamment dans le cas d’un cambriolage, où la clé GPG a potentiellement également disparu. Et les sauvegardes distantes ne seront d’aucune utilité sans la clé…

Il peut donc être moins risqué d’opter, comme je l’ai fait, pour une simple passphrase.

À vous de placer le curseur entre la protection de vos données et le risque de ne plus pouvoir les récupérer.

Installation

Sur une Debian :

sudo apt-get install duplicity

Fonctionnement

Duplicity effectue des sauvegardes complètes et incrémentales. Les sauvegardes incrémentales nécessitent toutes les sauvegardes depuis la dernière complète pour être restaurées.

Personnellement, j’effectue une sauvegarde complète tous les mois, et une incrémentale tous les jours.

Pour choisir le mode :

Exemple (à exécuter en root pour avoir accès à tous les fichiers) :

duplicity / file:///var/backups/duplicity/ \
    --include-globbing-filelist filelist.txt \
    --exclude '**'

Duplicity va sauvegarder à partir de la racine (/) tous les fichiers selon les règles d’inclusion et d’exclusion définies dans filelist.txt. Ce fichier contient simplement la liste des fichiers et répertoires à sauvegarder, ainsi que ceux à exclure. Par exemple :

/usr/local/bin/
/home/rom/Maildir/
/home/rom/.procmailrc
- /var/www/blog/wp-content/cache/
/var/www/blog/

Attention : les fichiers et répertoires à exclure doivent apparaître avant l’inclusion d’un répertoire parent. En effet, duplicity s’arrête à la première règle qui matche un chemin donné pour déterminer s’il doit l’inclure ou l’exclure.

Pour restaurer :

duplicity restore file:///var/backups/duplicity/ /any/directory/

(utiliser l’option -t pour restaurer à une date particulière)

Pour supprimer les anciennes sauvegardes (ici de plus de 2 mois) :

duplicity remove-older-than 2M file:///var/backups/duplicity/ --force

Bases de données

Tout comme pour les fichiers, il est préférable de sauvegarder incrémentalement les bases de données (seule une toute petite partie des données change d’un jour à l’autre).

Une première solution serait d’utiliser la fonctionnalité-qui-va-bien de votre SGBD.

Mais si le contenu de vos bases de données ne dépasse pas quelques Go (ce qui est très probable pour de l’auto-hébergement), duplicity permet de faire beaucoup plus simple.

Il suffit en effet de générer un dump complet des bases de données vers des fichiers .sql et d’inclure leur chemin dans la liste des fichiers à sauvegarder. Et là, c’est magique, duplicity va ne sauvegarder que les parties de ces (gros) fichiers qui ont changées, grâce à rsync et à son algorithme qui utilise des rolling checksums.

Bien sûr, il ne faut pas compresser ces fichiers avant de les donner à manger à duplicity (sinon l’intégralité du fichier risque de changer) ; c’est lui qui va s’en charger. De même, il vaut mieux éviter d’inclure dans les fichies SQL des informations liées au dump, comme sa date de génération.

Pour exporter une base de données MySQL par exemple :

mysql -uroot -ppassword --skip-comments -ql my_database > my_database.sql

Script

Il reste donc à écrire un script qui exporte les bases de données et qui appelle duplicity avec la liste de ce qu’il y a à sauvegarder.

Voici un prototype, à sauvegarder dans /usr/local/bin/backup :

#!/bin/bash
BACKUP_HOME=/var/backups
TMP_DBDIR="$BACKUP_HOME/dbdump"
BACKUP_DIR="$BACKUP_HOME/duplicity"
MYSQLPW=mon_password_mysql
PASSPHRASE=ma_passphrase_de_chiffrement_des_backups
DATABASES='blog autre_base'
FILELIST="/usr/local/bin/
/home/rom/Maildir/
/home/rom/.procmailrc
- /var/www/blog/wp-content/cache/
/var/www/blog/
$TMP_DBDIR/"

# databases
mkdir -p "$TMP_DBDIR"
for dbname in $DATABASES
do
  printf "## Dump database $dbname...\n"
  mysqldump -uroot -p"$MYSQLPW" --skip-comments -ql "$dbname" \
    > "$TMP_DBDIR/$dbname.sql"
done

# duplicity
printf '## Backup using duplicity...\n'
unset mode
[ "$1" = full ] && mode=full && printf '(force full backup)\n'
mkdir -p "$BACKUP_DIR"
export PASSPHRASE
duplicity $mode / file://"$BACKUP_DIR"/ \
  --include-globbing-filelist <(echo "$FILELIST") --exclude '**'

printf '## Delete old backups\n'
duplicity remove-older-than 2M file://"$BACKUP_DIR"/ --force

# backups are encrypted, we can make them accessible
chmod +r "$BACKUP_DIR"/*.gpg

# remove temp files
rm "$TMP_DBDIR"/*.sql

Une fois configuré, ne pas oublier de tester : exécuter le script et restaurer les données dans un répertoire de test, puis vérifier que tout est OK. Cette vérification doit être effectuée de temps en temps : il serait dommage de s’apercevoir, lorsqu’on en a besoin, que les backups sont inutilisables ou qu’un répertoire important a été oublié.

Cron

Pour démarrer automatiquement une sauvegarde complète le premier jour du mois et une incrémentale tous les autres jours, cron est notre ami :

sudo crontab -e

Ajouter les lignes :

0 1 1    * * /usr/local/bin/backup full
0 1 2-31 * * /usr/local/bin/backup

La première colonne correspond aux minutes, la deuxième aux heures : le script sera donc exécuté à 1h du matin. La 3e correspond au numéro du jour dans le mois. Les deux suivantes sont le numéro du mois dans l’année et le jour de la semaine.

Il peut être préférable d’exécuter le script en priorité basse :

0 1 1    * * nice -15 ionice -c2 /usr/local/bin/backup full
0 1 2-31 * * nice -15 ionice -c2 /usr/local/bin/backup

Copies

Il ne reste plus qu’à effectuer des copies des fichiers de backups ailleurs.

À partir d’une autre machine, le plus simple est d’utiliser rsync (sans l’option --delete !) :

rsync -rvP --partial-dir=/my/local/tmpbackup --ignore-existing --stats \
    -h server:/var/backups/duplicity/ /my/local/backup/

--ignore-existing évite de récupérer des modifications malicieuses des backups sur le serveur (ils ne sont pas censés être modifiés). Du coup, il faut aussi faire attention à sauvegarder les transferts partiels ailleurs (--partial-dir), sans quoi ils ne se termineront jamais.

Pour supprimer les anciens backups sur cette machine, c’est la même commande que sur le serveur :

duplicity remove-older-than 2M file:///my/local/backup/ --force

Conclusion

La génération de sauvegardes à la fois incrémentales et chiffrées, y compris pour les bases de données, font de duplicity une solution de backup idéale pour l’auto-hébergement.

Je l’utilise depuis plusieurs mois, et j’en suis très satisfait (même si je n’ai pas encore eu besoin de restaurer les backups en situation réelle).

À vos backups !

Anti-AdBlock et Hadopi, même combat ?

jeudi 13 juin 2013 à 17:47

abp

La presse s’inquiète de plus en plus de l’impact des bloqueurs de publicités sur leurs sources de revenus et condamne, plus ou moins ouvertement, leur utilisation par les internautes.

Le débat se polarise alors entre :

La seconde catégorie avance un argument simple et de bon sens : le contenu produit nécessite du travail, et leurs auteurs ont évidemment besoin de se loger, de se nourrir, et bien d’autres choses encore… Ce qui implique, dans notre société, d’avoir de l’argent. Ce que rapporte un peu la publicité.

Les autres sont des pirates sans conscience désirant tout gratuitement, à qui il faut expliquer qu’une attitude responsable consiste à ne pas bloquer les publicités. À moins que…

Hadopi

Des auteurs qui proposent du « contenu » difficilement vendable à un public qui y accède sans le payer, ça ne vous rappelle rien ?

L’un des fondements de l’idéologie répressive d’Hadopi est qu’il faut éduquer et contraindre les utilisateurs, sans quoi les « créateurs » ne pourront plus « vivre de leur travail », et donc ne pourront plus « créer ».

Faut-il établir un cadre psychologique incitant à accepter l’intrusion de publicités afin que les journalistes puissent « gagner leur vie » ?

De la même manière qu’il faut dépasser l’opposition entre le partage de la culture et son financement par la vente de copies, je pense qu’il faut dépasser celle entre la liberté de (non) réception (appliquée au web) et le financement par la publicité.

Les discussions portant sur la négociation d’un compromis entre les deux positions, comme l’acceptation de publicités moins intrusives, paraissent vaines.

Valeur et financement

Ne pas pouvoir vendre un contenu ne signifie pas qu’il n’a pas de valeur : un article rendu accessible à tous n’a pas moins de valeur que le même article restreint par un accès payant.

Par contre, y ajouter de la publicité diminue la valeur que leur attribuent les lecteurs, mais aussi les auteurs eux-mêmes : tous préféreraient ne pas la subir. La publicité agit donc comme un parasite.

Et pour les journalistes, ce parasite engendre une dépendance financière pouvant influencer le fond du discours. D’une manière générale, les sources de financement ont souvent tendance à réduire l’indépendance, pierre angulaire du journalisme.

C’est la raison pour laquelle certains essaient de ne dépendre que de leurs lecteurs. Mais, pour la plupart d’entre eux (pas tous), cela signifie limiter le contenu aux seuls abonnés. En effet, si tout est déjà accessible, pourquoi payer ?

Cependant, en pratique, seuls quelques gros sites peuvent se permettre cette restriction (les internautes ne vont pas s’abonner aux milliers de sites qu’ils visitent). Et au fond, est-ce bien le web que nous voulons, cloisonné par des barrières à péages ?

Du côté des lecteurs, la publicité ralentit la navigation, impose une pollution visuelle et collecte certaines données personnelles ; pas étonnant qu’ils cherchent à s’en prémunir !

Monnaie, revenu et travail

Dans le monde de la rareté, l’échange monétaire provient de la rencontre de l’offre et de la demande. Dans le monde de l’immatériel, l’offre et la demande se rencontrent déjà parfaitement sans monnaie, qui ne joue alors plus le rôle de facilitateur d’échange, mais au contraire d’obstacle : il est nécessaire de restreindre ou dégrader le résultat du travail pour pouvoir « gagner sa vie ».

Le métier de certains journalistes, dont personne ne conteste l’utilité, est menacé, car nous avons intégré le fait que le revenu devait provenir exclusivement du travail. Mais si ce travail ne génère pas d’argent, doit-il disparaître ?

La publicité est omniprésente car il faut absolument gagner de l’argent autrement, l’information en elle-même n’en rapportant pas. Pourtant, les internautes vont bien sur les sites de presse pour l’information, pas pour la publicité !

Revenu de base

Et si une partie du revenu était indépendante du travail ? Dissocier le revenu et l’emploi n’améliorerait-il pas la situation, pour tout le monde et en particulier pour les journalistes ?

Le revenu de base augmente la possibilité d’activités non rentables, sans ajouter de dépendances (car inconditionnel).

La rentabilité ayant tendance à entraver et dénaturer le journalisme, ne devrions-nous envisager sérieusement cette proposition dans les débats sur le financement de la presse, et plus généralement sur les productions potentiellement non-marchandes ?

Le revenu de base n’interdirait bien sûr pas les autres sources de financement : il s’y ajouterait. Simplement, il augmenterait la capacité à refuser des financements contraignants, tels que ceux provenant de la publicité…

Error happened! 0 - Call to undefined function simplexml_load_string() In: /var/www/Projet-Autoblog/autoblogs/autoblog.php:364 http://www.couturat.fr/Projet-Autoblog/autoblogs/blogrom1vcom_4af8d17d34d978843ff2ff40339aa5760e6458bc/?8 #0 /var/www/Projet-Autoblog/autoblogs/autoblog.php(932): VroumVroum_Blog->update() #1 /var/www/Projet-Autoblog/autoblogs/blogrom1vcom_4af8d17d34d978843ff2ff40339aa5760e6458bc/index.php(1): require_once('/var/www/Projet...') #2 {main}