Sauvegarde de bases MySQL via SVN

Il existe de nombreuses possibilités pour sauvegarder et archiver des bases de données, MySQL ou autres. En général, le protocole de sauvegarde dépend largement de l'objectif que l'on s'impose et des moyens dont on dispose.
La rétention des données sur le long terme pose bien sûr des problèmes de format et de support : vais-je pouvoir relire mes sauvegardes dans dix ans ? Elle pose aussi des problèmes de volume : puis-je me permettre d'archiver l'intégralité de mes bases une fois par jour pendant des années ?
Personne n'a de réponses absolues à toutes ces questions, car finalement tout est affaire de compromis. Dans la plupart des cas, j'utilise des scripts qui font un dump de mes bases de données, et qui archivent le résultat avec une rétention, en général, d'une semaine.
Le dump a cela de fantastique que c'est un format texte, il est donc lisible et modifiable par l'homme. Pas besoin de retrouver une version de MySQL compatible pour récupérer le contenu des bases archivées. Néanmoins il peut être assez volumineux suivant les options choisies, et le stockage à long terme peut vite devenir problématique. Dans le cadre de mon travail par exemple, le volume d'un dump pour un jour donné atteint 2,2 Go. Par contre, dans la plupart des bases de données, assez peu de données sont modifiées d'un jour sur l'autre. On pourrait économiser un maximum de place en n'enregistrant que la différence avec la veille. C'est là qu'intervient Subversion (SVN). Cet outil de versioning permet de ne stocker que la différence entre la version originale d'un fichier enregistrée initialement, et les versions ultérieures. Subversion est fourni de base avec Mac OS X, et il est disponible sur de très nombreux systèmes.
Continue reading

Related posts

Variables bash et expansion des accolades

Si vous manipulez un peu bash, et si vous faites des scripts shell dans cet environnement, vous avez forcément une petite expérience de l'expansion.

Par exemple, vous pouvez utiliser une * pour spécifier à bash qu'il doit construire tout seul la liste des arguments :

$ ls -1d /home/*
/home/riri
/home/fifi
/home/loulou

Il existe d'autres types d'expansion, mais celui qui m'intéresse est le cas assez inusité des expansions d'accolades. Ces dernières permettent de spécifier des listes totalement arbitraires. Par exemple, je peux écrire :

$ ls -1d /home/{riri,fifi}
/home/riri
/home/fifi

En précisant un préfix ou un suffix commun, je n'ai plus qu'à placer la partie variable entre { }. Je peux aussi préciser un intervalle qui permet de générer une séquence :

$ ls -1 /dev/ttyp{0..3}
/dev/ttyp0
/dev/ttyp1
/dev/ttyp2
/dev/ttyp3

Je maîtrise alors bien mieux le résultat de l'expansion qu'en utilisant simplement *. Par ailleurs, l'utilisation de * est impossible si les éléments ciblés n'existent pas sur le disque, ce qui limite énormément son périmètre d'utilisation.

Si dans le cadre d'un script je décide de définir d'un côté ma liste de dossiers, et de l'autre côté d'utiliser cette liste dans une boucle par exemple, je peux procéder comme cela :

$ MaListe=/home/*
$ for Dossier in $MaListe; do echo $Dossier; done
/home/riri
/home/fifi
/home/loulou

Cela fonctionne sans problème avec * mais cela ne fonctionne pas avec les accolades car l'expansion de { } ne se fait pas dans le cadre d'une assignation de variable :

$ MaListe=/home/{riri,fifi}
$ for Dossier in $MaListe; do echo $Dossier; done
/home/{riri,fifi}

Il est possible de contourner cette limitation. On utilise alors les parenthèses, qui permettent de forcer l'expansion des accolades, et qui transforment MaListe en tableau :

$ MaListe=( /home/{riri,fifi} )
$ for Dossier in ${MaListe[@]}; do echo $Dossier; done
/home/riri
/home/fifi

Et voilà !

Related posts

A script to list service ACLs on Mac OS X 10.5

I personally don't think it's a good thing to blog in english when you're french, unless you are very fluent and your target audience reads english. Today, my audience is the worldwide crowd of Mac OS X Server sysadmin. So, while I'm not fluent, I'm going to write my first post in english.

Background

There is something quite messy in the Service Access Control Lists (SACLs) on Mac OS X 10.5: you just can't display the full users & groups list of a SACL in command line.
Basically, you can do this:

$ dscl . -read /Groups/com.apple.access_ssh
AppleMetaNodeLocation: /Local/Default
GeneratedUID: A7E16606-3C52-42B9-852E-D197C7598EA8
NestedGroups: 955F946A-7C9D-4D3E-B286-E16003380282 ABCDEFAB-CDEF-ABCD-EFAB-CD...
PrimaryGroupID: 101
RealName:
 Remote Login Group
RecordName: com.apple.access_ssh
RecordType: dsRecTypeStandard:Groups

As you can see, this SACL group com.apple.access_ssh has no direct members, only nested groups (NestedGroups key). So, in order to list users, you have to read the content of each nested group. But groups are only available by their name. So the first step is to find out group's names.
At this stage, you have no way to know if the target group is local or if it sits on a remote open directory server, so you must use the /Search path:

$ dscl /Search -search /Groups GeneratedUID 955F946A-7C9D-4D3E-B286-E16003380282
myadmins		GeneratedUID = (
    "955F946A-7C9D-4D3E-B286-E16003380282"
)

The second step is to list users of the group:

$ dscl /Search -read /Groups/myadmins GroupMembership
GroupMembership: admin01 admin02 user01 user02 ldapuser01

But guess what: this group might have more than just users, may be its NestedGroups key is not empty! So at this point, you must also check the NestedGroups value, and recursively follow each group GUID, until you find only users.
Think "huge groups", think "handfulls of nested groups", and watch your fingers as you're going thru dscl torments. You've figured it out: Mac OS X lacks a good command line tool for following a SACL tree of users and groups.

Here come's getsacls.sh

I won't promise you a killer command line tool with foolproof error and recursion handling, but I still believe I've designed a usable piece of shell script. Even if it looks like it's the worst code I've ever wrote (wich is not true, I've made things way uglier).
The source code is too long and messy to be just copy-pasted here, just follow this link to download the getsacls.sh script.

How to get getsacls.sh:
Just download the latest version from here.

How to install getsacls.sh:
Simply copy to your Mac OS X 10.5 server (or managed client). Somewhere in your $PATH should be fine. Then chmod +x the script, so that it can be executed.

How to configure getsacls.sh:
Defaults values should be ok, but if you really want to change something, open the script in your favorite editor, and find the "FEW USER TUNABLE MISCS" section. Edit at your own risks.

How to use getsacls.sh:
It's simple, you just have to launch it. It will then proceed with the parsing of every SACL on your local system.
DO NOT use the sh command to launch this script. getsacls.sh uses special escape sequences and command options that sh will not recognize. Just run:

$ getsacls.sh

If you want to parse only some SACLs, you can provide each SACL name at the command line:

$ getsacls.sh com.apple.access_ssh com.apple.access_loginwindow

Still, you should only use SACL names that exist on your local system.

The default output is "fancy", it uses bold, indentation, and a beach-ball cursor. If you want the "no fancy" mode, you can either edit the corresponding "tunable misc variable" or define FANCY=NO at launch time:

$ FANCY=NO getsacls.sh com.apple.access_ssh

This "no fancy" mode allows for later parsing.

Caveats/bug:
The script will not handle circular references. If your SACL uses nested groups in a circular way (group 1 -> group 2 -> group 1), the script will not stop.
When finding two or more similar users or groups (for example the local admin group and the open directory admin group), it will use only one of them, and that should be the local one.
The script uses SQLite3 as a backend, because bash is not good with arrays, and because I'm not good with PERL/Python/Ruby.

Sample "fancy" output:

com.apple.access_ssh
--------------------------------
   myadmins	/LDAPv3/192.168.128.34	955F946A-7C9D-4D3E-B286-...
     admin01	/Local/Default	9A7917D1-D8E7-49D6-8211-...
     admin02	/Local/Default	40D516A2-4D02-4C92-9505-...
     ldapuser01	/LDAPv3/ldap.example.com	ldapuser01_OUT_OF_OD
     ldapuser02	/LDAPv3/ldap.example.com	ldapuser02_OUT_OF_OD
     ldapuser03	/LDAPv3/ldap.example.com	ldapuser03_OUT_OF_OD
     user01	/LDAPv3/192.168.128.34	49EF9C64-D98B-11D8-BCFA-...
   admin	/Local/Default	ABCDEFAB-CDEF-ABCD-EFAB-...
     root	/Local/Default	FFFFEEEE-DDDD-CCCC-BBBB-...
     admin01	/Local/Default	9A7917D1-D8E7-49D6-8211-...
     admin02	/Local/Default	40D516A2-4D02-4C92-9505-...
     user01	/LDAPv3/192.168.128.34	49EF9C64-D98B-11D8-BCFA-...
================================

Sample "no fancy" output:

com.apple.access_ssh
--------------------------------
g 1 myadmins /LDAPv3/192.168.128.34 955F946A-7C9D-4D3E-B286-...
u 2 admin01 /Local/Default 9A7917D1-D8E7-49D6-8211-...
u 2 admin02 /Local/Default 40D516A2-4D02-4C92-9505-...
u 2 ldapuser01 /LDAPv3/ldap.example.com ldapuser01_OUT_OF_OD
u 2 ldapuser02 /LDAPv3/ldap.example.com ldapuser02_OUT_OF_OD
u 2 ldapuser03 /LDAPv3/ldap.example.com ldapuser03_OUT_OF_OD
u 2 user01 /LDAPv3/192.168.128.34 49EF9C64-D98B-11D8-BCFA-...
g 1 admin /Local/Default ABCDEFAB-CDEF-ABCD-EFAB-...
u 2 root /Local/Default FFFFEEEE-DDDD-CCCC-BBBB-...
u 2 admin01 /Local/Default 9A7917D1-D8E7-49D6-8211-...
u 2 admin02 /Local/Default 40D516A2-4D02-4C92-9505-...
u 2 user01 /LDAPv3/192.168.128.34 49EF9C64-D98B-11D8-BCFA-...
================================

Current version:
As of now, current version of getsacls.sh is 407 ($Id: getsacls.sh 407 2009-07-09 09:36:26Z patpro $). Next revisions will be listed here.

Update: $Id: getsacls.sh 409 2009-07-09 14:30:01Z patpro $
I've added some error handling for a rare case: when a user account lives on a LDAP server distinct from the Open Directory server, the GroupMembership field is not updated on the OD if the user account is destroyed on the LDAP. So according to the GroupMembership the user is still here, but according to the LDAP the user is nowhere to be found.

Update: $Id: getsacls.sh 412 2009-07-23 20:24:54Z patpro $
I'm forcing LC_NUMERIC in the beachball function, so that sleep 0.05 runs as expected even for people not using the dot as a decimal separator. Some cleanup.

Update: $Id: getsacls.sh 414 2009-08-03 10:33:30Z patpro $
Some cleanup and english corrections. Added some delay to the beatchball rotation so it's more enjoyable.

Feel free to comment, and to correct my english ;)

Related posts

jot et seq, créer des séquences en ligne de commande

Quand on écrit des scripts shell, il arrive fréquemment d'avoir besoin de générer des séquences de chiffres ou de lettres (nourrir une boucle, créer des noms de fichiers aléatoires ou non...).
Il existe pour cela deux outils, seq et jot. seq est la commande GNU, elle est donc très répandue (Linux). jot est une commande originaire du monde BSD (FreeBSD, Mac OS X, ...). Elle est moins répandue, et surtout moins connue, ce qui est bien dommage. En effet, jot est nettement plus puissante et versatile que seq.

Voyons quelques cas d'utilisation concrets de ces deux commandes, avec pour commencer une boucle for classique. Note : toutes les commandes présentées ici ont été testées dans un shell bash. La syntaxe des différentes boucles peut varier dans d'autres shells.
Imaginons que j'ai besoin d'une boucle qui tourne sur une liste de chiffres de 1 à 10, je peux très simplement faire ceci, et obtenir à chaque fois le même résultat exactement :

# syntaxe basique
for i in 1 2 3 4 5 6 7 8 9 10; do
	# mon travail
	echo $i
done

# syntaxe avancée
for ((a=1; a<=10 ; a++)); do
	# mon travail
	echo $a
done

# autre syntaxe avancée
for a in {1..10}; do
	# mon travail
	echo $a
done

# syntaxe avec seq
for b in $(seq 1 10); do
	# mon travail
	echo $b
done

# syntaxe avec jot
for c in $(jot 10); do
	# mon travail
	echo $c
done

Pour ces cas simples, l'intérêt des commandes seq et jot n'est pas immédiat, d'autant que si la syntaxe basique est hors jeux pour les listes longues (1 à 1000 par exemple), les syntaxes avancées sauront en général s'en tirer.
Voyons comment faire maintenant si je souhaite faire une liste de 01 à 10 :

La syntaxe basique fonctionne toujours, mais je dois taper à la main 01, 02, ... C'est fastidieux. Les syntaxes avancées ne savent pas faire directement. Je peux par contre invoquer printf pour faire une réécriture de mes valeurs :

for a in {1..20}; do
	a=$(printf "%02d" $a)
	# mon travail
	echo $a
done

Pour faire ce type d'itération sans étapes intermédiaires il reste seq et jot.
L'ajout de l'option w à la commande seq permet de forcer le formatage de sorte que tous les nombres affichés aient la même largeur. En cas de besoin, un ou plusieurs 0 sont ajoutés devant le nombre pour compléter :

seq -w 1 10
# résultat :
01
02
...
09
10

Pour jot, le padding n'est pas complètement automatique, il faut en régler la largeur via le formatage de type printf. On obtient le même résultat qu'avec seq :

jot -w '%02d' 10

C'est plus délicat, mais cela nous permet de mettre le doigt sur une première limitation de seq : ce dernier ne sait pas utiliser d'autres options de printf que %e, %f et %g.
Ainsi, pour produire une liste de 00001 à 00010, je peux utiliser jot avec un formatage printf ou seq avec un formatage automatique (a priori, c'est une nouveauté apparue depuis la rédaction de cet article), mais pas seq :

jot -w '%05d' 10
# résultat :
00001
00002
...
00009
00010
seq -w 00010
# résultat :
00001
00002
...
00009
00010

Les deux commandes sont bien sûr capables de gérer une borne inférieure et une borne supérieure, mais leur gestion des incréments est une de leurs différences fondamentales. Vous pouvez indiquer à chacune de travailler de 1 à 1000 mais seq ne sait travailler que par incrément fixe :

# seq sait aller de 1 à *maximum* 1000 par saut de 250 :
seq 1 250 1000
# résultat :
1
251
501
751

Alors que jot travaille par défaut sur le nombre d'étapes :

# jot sait aller de 1 à 1000 strictement, en 5 étapes :
jot 5 1 1000
# résultat :
1
251
500
750
1000

jot fonctionne en fait en calculant l'incrément optimal. Ce dernier comporte parfois des décimales. Comme le format par défaut pour jot est l'entier, le résultat ci-dessus n'est qu'un arrondi des valeurs réelles de travail de jot. Si on force l'affichage des décimales, on obtient bien une série mathématiquement satisfaisante :

jot -w '%g' 5 1 1000
# résultat :
1
250.75
500.5
750.25
1000

Il sait aussi travailler sur un incrément explicite, comme seq :

# et jot donne alors le même résultat que seq :
jot 5 1 1000 250
# résultat :
1
251
501
751

Nous laissons définitivement seq derrière nous, car il ne sait travailler qu'avec des chiffres, alors que jot peut utiliser tout l'ASCII, générer des listes aléatoires, écrire une chaîne donnée x fois. Et si vous le jumelez avec son ami rs, il sait générer des matrices, des séries de mots de passe...
Voici quelques exemples tirés du man et d'ailleurs :

# les 128 caractères ASCII :
jot -c 128 0

# une ligne de 40 points : 
jot -s "" -b . 40

# l'alphabet en majuscule :
jot -c 26 A

# créer 5 noms de fichiers partiellement aléatoires :
jot -r -w '/tmp/fichier-%05d' 5 0 10000

# 10 mots de passe de 12 caractères ASCII aléatoires entre ! et ~ :
jot -r -c 120 33 126 | rs -g0 -w12

# une matrice 10x10, de 100 nombres tirés au hasard entre 1 et 100
jot -r 100 | rs 10 10 

Un peu d'histoire pour finir. Le nom de la commande jot ne cesse de faire râler les linuxiens qui doivent un jour ou l'autre travailler sur un système BSD. Il est indéniable que seq est un nom qui tombe sous le sens, de sequence à seq, il n'y a qu'un pas. Pour jot, ce n'est pas aussi immédiat.
Le nom de jot signifie en anglais "très petite quantité", et vient du latin iota. John A. Kunze, qui a développé jot, mais aussi rs et lam, m'a expliqué qu'il a écrit ces trois commandes il y'a très longtemps, en s'inspirant des opérateurs iota, reshape, et laminate du langage APL. Il a alors décidé de donner à ses commandes des noms typiquement "unixiens". Ainsi sont nées jot, rs et lam.

edit 1 : ajout du résultat pour certains exemples, correction d'une typo dans une commande.
edit 2 : correction d'un exemple, suppression de certaines boucles for inutiles, ajout de précision sur les incréments décimaux.
edit 3 : ajout des séquences du type {1..10} gérées par bash.
edit 4 : mise à jour au suejt de la capacité de seq à gérer le padding automatique.

Related posts

Slife et la vie privée, un mois après

icone Slife (c) SlifeLabsIl y a un mois, j'écrivais ici à propos de Slife, un outil de time tracking gratuit pour Mac OS X et Windows. Je m'en suis servi tout ce temps sur ma machine personnelle et sur ma machine professionnelle. Il est donc temps de revenir sur ce logiciel.
À mon sens, le gros défaut de Slife (une fois qu'on a accepté ses limites fonctionnelles) c'est l'impossibilité d'accéder aux données enregistrées a postériori. Cela pose un vrai gros problème de respect de la vie privée. En effet, chaque document, chaque page web, chaque email ouvert va donner lieu à un enregistrement dans la base de données de Slife. Ce dernier pourra afficher sans état d'âme des événements comme ceux-ci dans votre tracking journalier et mensuel :

  • aide mobilité site:SuperJob.fr - Google Search
  • Grosse Cochonne sans culotte - xXx

Inutile de vous faire un dessin. Dans votre navigateur vous êtes libre à tout moment d'effacer votre historique. Dans Slife, vous ne pouvez pas le faire. Si vous avez oublié d'activer le mode "Privé" avant de chercher un nouveau job, ou d'aller mater quelques paires de fesses au nez et à la barbe de votre employeur, vous êtes cuits.
Bien sûr, il y aura toujours un petit malin pour aller éditer la base de données de Slife à la main. Mais ce n'est pas le genre d'acrobatie à la portée du premier venu. Je vais néanmoins donner la marche à suivre. Notez bien : j'ai bien sûr testé cette manipulation, mais je ne peux pas garantir qu'elle est inoffensive sur le long terme. Il n'est pas complètement exclu que cela crée des problèmes de cohérence des données dans Slife.
Comme on s'y serait attendu, Slife stocke ses données dans une base de données SQLite (en tout cas sous Mac OS X). SQLite est un moteur de bases de données SQL, léger et rapide. Contrairement à MySQL ou autre, vous n'avez pas besoin qu'un serveur tourne en permanence pour accéder aux données. C'est aussi ce moteur qui est utilisé par Spotlight.
Il est possible de rentrer dans des bases de données SQLite directement avec les outils fournis dans Mac OS X. Il faut pour cela lancer l'application Terminal, et taper au prompt :

sqlite3 "Library/Application Support/Slife/db20.slife"

ce qui retourne :

SQLite version 3.4.0
Enter ".help" for instructions
sqlite>

On peut ensuite taper :

.databases

ce qui doit donner quelque chose de ce genre, modulo votre nom d'utilisateur :

seq  name             file                                                      
---  ---------------  ----------------------------------------------------------
0    main             /Users/patpro/Library/Application Support/Slife/db20.slife

Posons maintenant que vous souhaitiez supprimer tous les événements enregistrés dont le titre contient SuperJob.fr, car vous ne voulez pas que votre patron soupçonne que vous préparez votre départ.
La première étape pour ne pas faire de bêtise c'est de quitter Slife car être deux (Slife et vous) en train d'éditer la base de données au même instant pourrait être fâcheux. Après avoir quitté Slife, faites une copie de sauvegarde du fichier db20.slife.
La seconde étape consiste à afficher sans les détruire les événements ciblés. On utilise pour cela l'instruction SQL SELECT. Au prompt sqlite> tapez :

SELECT * FROM ZITEMRECORDED WHERE ZNAME LIKE "%SuperJob.fr%";

Après validation, vous allez obtenir un listing de tous les enregistrements qui contiennent "SuperJob.fr" dans le titre :

10|2478|1|2008|7|10|30|34.0|Safari||aide mobilité site:SuperJob.fr - Google Search
10|2479|2|2008|7|10|30|8.0|Safari||SuperJob.fr, numéro 1 de l’emploi en ligne
10|2480|1|2008|7|10|30|9.0|Safari||Bulletin Officiel de SuperJob.fr n° 2002-2

Lisez bien tous les titres pour vérifier que vous n'allez pas supprimer des événements qui ne devraient pas l'être.
Si c'est bon, vous pouvez lancer le DELETE :

DELETE FROM ZITEMRECORDED WHERE ZNAME LIKE "%SuperJob.fr%";

Vous pouvez, à l'issue de ce DELETE, refaire le SELECT pour vérifier qu'il ne reste rien. Mais il n'y a aucune raison qu'un enregistrement ait échappé au DELETE.

Ensuite, vous quittez SQLite en tapant au prompt .quit, puis vous pouvez relancer Slife et vérifier que les enregistrements incriminants ont disparu.

Pour ceux qui sont désespérément allergiques au terminal, il existe une paire de clients graphiques pour accéder à des bases SQLite. J'ai testé rapidement SQLite Database Browser 1.3, et à première vue il donne satisfaction. Néanmoins il est compilé pour PowerPC uniquement. Je ne saurai garantir son fonctionnement sur plateforme Intel.

Related posts

Recevoir les mails de periodic sous Mac OS X Server 10.5

Les BSDistes de tout poil sont habitués aux emails envoyés chaque nuit, chaque semaine, et chaque mois à l'issue du lancement des scripts periodic. Sous Mac OS X, le résultat de ces scripts en par défaut renvoyé dans les fichiers /var/log/daily.out /var/log/weekly.out et /var/log/monthly.out. Néanmoins, l'administrateur avisé aura tôt fait de les diriger vers son mail en utilisant un fichier /etc/periodic.conf.local comme celui-ci :

daily_output=root
weekly_output=root
monthly_output=root

La formule fonctionne parfaitement pour FreeBSD ou Mac OS X 10.4, mais pas pour Mac OS X Server 10.5.4. Launchd semble présenter un bug qui l'empêche de gérer la création du mail post-periodic. On lit alors cette erreur dans /var/log/system.log :

Jul 29 03:17:42 myserver com.apple.launchd[1] (com.apple.periodic-daily[...]):
  Stray process with PGID equal to this dead job: PID ... PPID 1 sendmail

C'est fâcheux, mais ne nous laissons pas abattre, car il existe une solution. Contrairement à ce qu'on peut lire à droite et à gauche, il ne faut pas modifier la configuration de Postfix, et laisser les références à Cyrus tranquilles. La solution est plutôt du côté de launchd. Certains ont mis en évidence que le mail sera bien généré si au lieu d'exécuter simplement le periodic, on exécute en plus et juste après une petite pause.
Après quelques tests, j'ai trouvé que c'est une solution assez satisfaisante. Sur le plan fonctionnel elle est parfaite, mais elle n'est pas idéale, car elle impose de modifier des fichiers fournis par Apple. Donc cette correction est susceptible d'être perdue au détour d'une mise à jour du système.

J'ai choisi de modifier les plist de launchd correspondant aux lancements de periodic :

[edit] : En réalité, la modification des plists com.apple.periodic* n'a pas donné le résultat escompté sur le terrain. Sur mon serveur de test, les mails de periodic étaient bien envoyés, mais sur mes serveurs de production, 3 machines sur 4 n'ont pas réussi à envoyer les mails pour le daily. Par ailleurs, le nombre d'erreurs dans les log système a sensiblement augmenté.

J'ai donc décidé de restaurer les fichiers com.apple.periodic* dans leur état d'origine et de modifier à la place la commande periodic. J'ai renommé /usr/sbin/periodic en /usr/sbin/periodic_orig, puis j'ai créé un script shell nommé /usr/sbin/periodic :

#!/bin/bash 
/usr/sbin/periodic_orig $@ 
sleep 1

Ainsi, le lancement par launchd de la commande `periodic daily` va en réalité lancer `/usr/sbin/periodic_orig daily` (donc le script periodic original), puis va lancer `sleep 1`, ce qui suffit à launchd pour pouvoir générer le mail de résultat de periodic.

J'ai comme l'impression qu'avec launchd, on n'a pas fini d'en baver...

Related posts