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
Archives
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à !
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 ;)
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.
Slife et la vie privée, un mois après
Il 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.
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...