Construction des sockets UNIX des bases de données sous Server 3.0

Ceux qui me suivent sur Twitter ou qui sont abonné à la liste de diffusion MacEnterprise savent que depuis la sortie d’OS X Mavericks et Server 3.0 je cherche à comprendre comment fonctionne le système des bases de données dédiées aux services Gestionnaire de Profils, Wiki ainsi que Calendriers et Contacts.

Après quelques longues heures de lecture de code des services Apple qui, comme à leur habitude, ne sont pas documentés, j’ai pu enfin dessiner le fonctionnement global des bases PostgreSQL.

Si j’ai tant cherché à obtenir cette information, c’est qu’elle est capitale pour l’écriture de scripts de sauvegarde portable pour OS X Server.

Sur 10.8 et Server 2.x les choses étaient simples. L’ensemble des services d’Apple utilisait une base de données dédiée et dont les réglages étaient disponibles via la ligne de commande serveradmin.

Aujourd’hui ce n’est plus le cas. Autant le dire très clairement, les ingénieurs d’Apple on fait du grand n’importe quoi dans la gestion des bases de données sous OS X Mavericks et Server 3.0.

Une rapide analyse /Applications/Server.app/Contents/ServerRoot/usr/bin permet de voir que le binaire postgres est remplacé par un script permettant d’attendre que le volume contenant la base de données soit monté avant de lancer le véritable postgres, renommé pour l’occasion postgres_real. Jusqu’ici, rien de grave c’est quelque chose d’assez commun.

Là où les choses se gâte c’est lorsque l’on regarde les processus en mémoire.

bash-3.2# ps aux | grep postgres_real
_postgres         135   0,0  0,0  2487376    816   ??  Ss    9:04     0:01.21 /Applications/Server.app/Contents/ServerRoot/usr/bin/postgres_real -D /Volumes/DataHD/Library/Server/PostgreSQL/Data -c listen_addresses=127.0.0.1,::1 -c log_connections=on -c log_directory=/Library/Logs/PostgreSQL -c log_filename=PostgreSQL.log -c log_line_prefix=%t -c log_lock_waits=on -c log_statement=ddl -c logging_collector=on -c unix_socket_directory=/private/var/pgsql_socket -c unix_socket_group=_postgres -c unix_socket_permissions=0770
_teamsserver      336   0,0  0,1  2505304   1952   ??  S     9:04     0:03.22 /Applications/Server.app/Contents/ServerRoot/usr/bin/postgres_real -D /Volumes/DataHD/Library/Server/Wiki/Database.xpg/Cluster.pg -c log_line_prefix=%t -c log_lock_waits=on -c log_statement=ddl -c logging_collector=on -c max_connections=500 -c unix_socket_directory=/Library/Server/Wiki/PostgresSocket -c unix_socket_group=_teamsserver -c unix_socket_permissions=0770 -c log_connections=on -c listen_addresses= -c log_directory=/Library/Server/Wiki/Logs -c log_filename=postgres-%a.log -c log_rotation_age=1440 -c log_truncate_on_rotation=on
_devicemgr        205   0,0  0,1  2488960   2696   ??  S     9:04     0:02.02 /Applications/Server.app/Contents/ServerRoot/usr/bin/postgres_real -D /Library/Server/ProfileManager/Config/ServiceData/Data/PostgreSQL -c unix_socket_directory=/Library/Server/ProfileManager/Config/var/PostgreSQL -c logging_collector=on -c log_rotation_size=10MB -c log_connections=on -c log_lock_waits=on -c log_statement=ddl -c log_line_prefix=%t -c listen_addresses= -c log_directory=/Library/Logs/ProfileManager -c log_filename=PostgreSQL-%F.log -c unix_socket_group=_devicemgr -c unix_socket_permissions=0770 -c max_connections=128
_calendar       41044   0,0  0,2  2451128   4508   ??  S    11:19     0:01.51 /Applications/Server.app/Contents/ServerRoot/usr/bin/postgres_real -c listen_addresses= -k /var/run/caldavd/ccs_postgres_1ad21686ab7994fdf44622de3b07db61 -c shared_buffers=75 -c max_connections=50 -c standard_conforming_strings=on -c log_lock_waits=TRUE -c deadlock_timeout=10 -c log_line_prefix=%m [%p]  -c logging_collector=on -c log_truncate_on_rotation=on -c log_directory=/var/log/caldavd/postgresql -c log_filename=postgresql_%w.log -c log_rotation_age=1440

La chose est dense, mais avec un peu d’attention, on se rend compte que nous avons un processus pour chaque usage de base PostgreSQL.

Le plus commun est celui lancé par l’utilisateur _postgres et utilisant le socket de connexion standard /private/var/pgsql_socket. Cette instance servira à héberger les données des services non fournis par Apple. Vos services en sommes.

Ensuite, nous avons le service dédié au wiki, lancé depuis l’utilisateur _teamsserver, celui-ci utilise /Library/Server/Wiki/PostgresSocket comme socket de connexion.

Autre service, la gestion des terminaux mobiles, représentés par l’utilisateur _devicemgr, se voit une base de données associée et disponible au chemin /Library/Server/ProfileManager/Config/var/PostgreSQL.

Et enfin, les services Calendriers et Contacts nous réserve le meilleur pour la fin, lancé depuis l’utilisateur _calendar le chemin d’accès est /var/run/caldavd/ccs_postgres_1ad21686ab7994fdf44622de3b07db61.

Sur les trois bases maintenues pour les besoins d’Apple, aucune des trois n’utilise les mêmes conventions d’écriture. Quand je parlais de grand n’importe quoi…

Or, il faut être capable de déterminer ces chemins d’accès si l’on souhaite pouvoir utiliser l’outil pg_dumpall pour faire des sauvegardes de ces bases.

Base de données pour vos services

Ici la chose est simple, le chemin est standard est sera toujours /private/var/pgsql_socket.

Base de données du Wiki

Quelque soit la localisation des données de travail d’OS X Server, le Wiki persiste à utiliser /Library/Server/Wiki/PostgresSocket comme chemin d’écoute. Une analyse du fonctionnement du service montre que le lancement de cette instance est géré par le script ruby /Applications/Server.app/Contents/ServerRoot/usr/sbin/collabd_database_loader.

Une lecture des premières lignes du fichier permet de comprendre que ce chemin ne changera que difficilement, il est codé en dur !

#!/usr/bin/env /Applications/Server.app/Contents/ServerRoot/usr/bin/ruby
...
$ServerLibraryPath = '/Library/Server'
$ServiceConfigPath = '/Library/Server/Wiki/Config'
...
$PostgresSocketDir = $ServerLibraryPath + '/Wiki/PostgresSocket'
...
    postgres_options = "-c log_line_prefix=%t -c log_lock_waits=on -c log_statement=ddl -c logging_collector=on " +
            "-c max_connections=500 -c unix_socket_directory=#{$PostgresSocketDir} -c unix_socket_group=_teamsserver " +
            "-c unix_socket_permissions=0770 -c log_connections=on -c listen_addresses= -c log_directory=/Library/Server/Wiki/Logs " +
            "-c log_filename=postgres-%a.log -c log_rotation_age=1440 -c log_truncate_on_rotation=on"
    cmd = "#{$ServerInstallPathPrefix}/usr/bin/xpg_ctl " + ($ShouldStartDatabase ? 'start' : 'stop') + " -w -t #{XPG_CTL_WAIT_SECONDS} -D " + $DatabaseClusterDir.shellescape + " -l #{$ServerLibraryPath}/Wiki/Logs/postgres-xpg.log -o \"#{postgres_options}\""
 
    $logger.info("Calling out to xpg_ctl: #{cmd}")
    `su -m _teamsserver -c '#{cmd}'`

La variable PostgresSocketDir est formée de l’assemblage de deux variables statiques…

Base de données du Gestionnaire de Profils

Un poil plus complexe, cette base de données est lancée via un service launchd dont le plist de configuration se trouve dans /Applications/Server.app/Contents/ServerRoot/System/Library/LaunchDaemons/com.apple.DeviceManagement.postgres.plist.

À la lecture de ce fichier on se rend compte que le lancement du service est conditionné par le fichier de configuration /Library/Server/ProfileManager/Config/PostgreSQL_config.plist qui contient l’ensemble des réglages nécessaire au lancement de la base.

Un moyen simple d’extraire le chemin qui nous intéresse est par exemple :

bash-3.2# /usr/libexec/PlistBuddy -c print /Library/Server/ProfileManager/Config/PostgreSQL_config.plist | grep unix_socket_directory | awk 'BEGIN { FS = "=" } ; { print $2 }' | tr -d "\""
/Library/Server/ProfileManager/Config/var/PostgreSQL

Base de données des Calendriers et Contacts

Ici se trouve le plus grand n’importe quoi, un chemin de socket avec un hash md5 à l’intérieur. Ce genre de chose est réellement utile lorsqu’un service peut être lancé plusieurs fois pour servir différentes organisations sur un même serveur. Chose totalement hors de propos sur OS X Server qui n’est toujours pas capable de travailler en cluster comme à l’époque de Mac OS X Server Snow Leopard.

Une analyse poussée du fonctionnement du serveur permet de voir que le service de base de données est lancé par un code python situé dans le fichier /Applications/Server.app/Contents/ServerRoot/usr/share/caldavd/lib/python/txdav/base/datastore/subpostgres.py

                # Unix socket length path limit
                self.socketDir = CachingFilePath("%s/ccs_postgres_%s/" %
                    (socketDir, md5(dataStoreDirectory.path).hexdigest()))
                if len(self.socketDir.path) > 64:
                    socketDir = "/tmp"
                    self.socketDir = CachingFilePath("/tmp/ccs_postgres_%s/" %
                        (md5(dataStoreDirectory.path).hexdigest()))
                self.host = self.socketDir.path
                self.port = None

Pour faire simple, ce code va composer un chemin d’accès à partir de l’empreinte MD5 formée par le chemin de localisation des données Postgres sur le disque. On vient de sortir l’arme atomique pour écraser une mouche.

Vous trouverez un exemple de script shell sur mon GitHub permettant de générer correctement ce chemin d’accès.

4 thoughts on “Construction des sockets UNIX des bases de données sous Server 3.0

  1. Bonjour,

    Tout d’abord merci de cet article, j’ai passé des jours à googliser ce problème, tu est semble-il le seul à s’être penché sur le problème et à partagé ses trouvailles.

    j’avais essayé votre solution avec server app 3.0.2 et ca avait bien l’air de fonctionner, mais entre temps j’ai mis à jour (j’aurais p’têt pas du) le server app vers 3.1.1 et j’ai installé osx 10.9.2.
    Et bien depuis ça ne marche plus, je reçois bien le chemin d’accès, mais quand je le réutilise pour faire mon dump voici l’erreur que je reçois.

    server:~ sadmin$ sudo sh /Users/sadmin/Desktop/PostgresSocket.sh
    /var/run/caldavd/ccs_postgres_3d403b3009fe0c830944d87bd751fbe9/

    server:~ sadmin$ sudo /Applications/Server.app/Contents/ServerRoot/usr/bin/pg_dump -U caldav -h « /var/run/caldavd/ccs_postgres_3d403b3009fe0c830944d87bd751fbe9/ » -F c -c -b > /Users/sadmin/Desktop/CalendarServerBackup_$(date +%F).pgdump

    Password:

    pg_dump: [archiver (db)] connection to database « caldav » failed: could not connect to server: No such file or directory
    Is the server running locally and accepting
    connections on Unix domain socket « /var/run/caldavd/ccs_postgres_3d403b3009fe0c830944d87bd751fbe9/.s.PGSQL.5432 »?

    si tu pouvais aider le débutant que je suis, ce serait vraiment…comment dire -> très cool :-)

    • Bonjour,

      En effet, 3.1 a encore changé les socket pour quelque chose de différent (et encore plus complexe) pour CalDAV.

      Je n’ai pas encore eu le temps de comprendre exactement comment ça marche. Le fait est que le code dégueulasse du md5 est toujours là. Il semblerait simplement qu’un réglage préalable force une autre valeur de socket. Mais je n’ai pas encore trouvé où est configurée cette valeur pour aller la récupérer et l’intégrer dans mon algo.

Laisser un commentaire