Le projet
L’idée de départ était simple : déployer OCS Inventory NG pour centraliser l’inventaire matériel et logiciel de l’infrastructure. Un serveur, des agents, une console web. Rien de bien compliqué.
Spoiler : Je coyais que ce serait du caviar, ben j’ai mangé des cailloux.
Acte 1 — L’installation serveur, ou “perl est ton ennemi”
L’installation d’OCS côté serveur passe par un script setup.sh qui pose des questions. Beaucoup de questions. Et qui attend des réponses précises — appuyer sur Entrée sans réfléchir, c’est la croix et la bannière.
Après avoir répondu correctement à l’interrogatoire, premier redémarrage d’Apache. Échec.
Can't locate Switch.pm in @INC
Can't load Perl module Apache::Ocsinventory
Deux modules Perl absents. libswitch-perl, ça se trouve facilement. libxml-entities-perl… introuvable dans les dépôts Ubuntu 24.04. Direction cpan, configuration automatique, téléchargement depuis internet, compilation, installation. Pour un module. Bienvenue en 2026.
Une fois les modules en place, Apache repart. Victoire ? Pas encore.
Acte 2 — La base de données qui ne répond pas
Une fois Apache relancé, on accède à /ocsreports pour la première configuration. On tombe sur ce formulaire — noter au passage le warning en rouge qui annonce déjà la couleur sur z-ocsinventory-server.conf :
Je saisis les infos de la base de données. Internal Server Error.
Fouille des logs Apache. Le module Perl ne peut pas se connecter à MariaDB. Inspection du fichier de configuration z-ocsinventory-server.conf et….. damned:
PerlSetVar OCS_DB_PWD ocs
Le mot de passe par défaut était resté en place. Le script d’installation ne l’avait pas mis à jour malgré les réponses. Correction à la main, redémarrage d’Apache. Cette fois c’est bon.
À retenir : après l’installation, vérifiez systématiquement que le mot de passe dans
z-ocsinventory-server.confcorrespond bien à ce que vous avez saisi.
Cette fois le formulaire passe et on obtient enfin la confirmation :
En cliquant sur le lien, surprise — un écran de mise à jour du schéma de base de données. Beaucoup de gens paniquent en le voyant, c’est pourtant tout à fait normal :
Un clic sur “Perform the update” et on accède à l’interface. Première chose qu’on voit :
Deux alertes sécurité immédiates : supprimer install.php et changer le mot de passe admin par défaut. Une fois traité, on attaque le déploiement des agents Windows.
Acte 3 — L’agent Windows, ou “le packager qui ne package pas”
Côté client Windows, OCS propose un outil appelé OCS Packager (qui s’appelle en réalité Packager-for-Windows sur GitHub, détail amusant). L’idée est séduisante : on lui donne l’installeur, le certificat CA, les paramètres de connexion, et il génère un exécutable tout-en-un prêt à être déployé via GPO.
En théorie.
En pratique, l’exécutable généré ignorait superbement les paramètres configurés et continuait à pointer vers http://ocsinventory-ng/ocsinventory — l’adresse par défaut codée en dur. Relance du Packager, vérification des options, même résultat.
Abandon du Packager et choix d’une approche plus directe : un batch GPO qui installe l’agent, écrase le fichier ocsinventory.ini avec la bonne configuration, et copie le certificat CA au bon endroit.
@echo off
if exist "C:\Program Files\OCS Inventory Agent\OCSInventory.exe" goto config
\\serveur\deploy$\OCS\OCS-Windows-Agent-Setup-x64.exe /S /NOTRAY
timeout /t 10
:config
net stop "OCS Inventory Service"
copy /Y \\serveur\deploy$\OCS\ocsinventory.ini "C:\ProgramData\OCS Inventory NG\Agent\ocsinventory.ini"
copy /Y \\serveur\deploy$\OCS\ca.cert.pem "C:\ProgramData\OCS Inventory NG\Agent\cacert.pem"
net start "OCS Inventory Service"
:fin
exit
Rustique. Efficace.
Acte 4 — Le cacert.pem, ou “le détail qui tue”
L’agent installé, la configuration correcte en place… et toujours rien qui remonte dans la console.
Après investigation, le fichier cacert.pem n’était tout simplement pas présent dans le répertoire de l’agent. Sans ce fichier, l’agent refuse la connexion HTTPS — logique — mais sans le dire clairement.
Une fois le certificat copié manuellement, la machine est apparue dans la console en quelques secondes.
C’est exactement pour ça que le batch copie désormais les deux fichiers : l’ocsinventory.ini et le cacert.pem.
Acte 5 — Les serveurs Linux, ou “merci Ansible”
Pour les serveurs Linux, pas question de se battre avec des GPO et des batch. Il y a déjà un playbook Ansible qui tourne deux fois par semaine pour mettre à jour tous les serveurs — NTP, paquets, rapport HTML par mail. J’ai simplement ajouté les tâches OCS dedans.
L’idée : installer le paquet ocsinventory-agent, déposer le certificat CA, écraser la configuration avec les bons paramètres, et lancer un inventaire immédiat si c’est une nouvelle installation. Le tout intégré dans le flux existant, avec un statut “installé / déjà présent” dans le rapport HTML.
Avant de lancer le playbook, il faut copier le certificat CA sur le serveur Ansible :
mkdir -p /etc/ansible/files
scp user@serveur-ocs:/etc/ssl/ocs/ca.cert.pem /etc/ansible/files/
Voici le playbook complet avec les tâches OCS intégrées :
---
- name: Configuration du serveur NTP, fuseau horaire et mise à jour des paquets
hosts: serveurs
become: true
become_user: root
tasks:
# --- Définir le fuseau horaire ---
- name: Définir le fuseau horaire sur Europe/Paris
community.general.timezone:
name: Europe/Paris
register: timezone_set
when: inventory_hostname not in ['kronoss', 'demeter']
# --- Gestion de Chrony ---
- name: S'assurer que Chrony est installé
ansible.builtin.apt:
name: chrony
state: present
register: chrony_installed
when: inventory_hostname != 'kronoss'
- name: Déployer la configuration chrony.conf
ansible.builtin.copy:
dest: /etc/chrony/chrony.conf
content: |
server 192.168.x.x iburst prefer
server 192.168.x.x iburst
keyfile /etc/chrony/chrony.keys
driftfile /var/lib/chrony/chrony.drift
ntsdumpdir /var/lib/chrony
logdir /var/log/chrony
maxupdateskew 100.0
rtcsync
makestep 1 3
leapsectz right/UTC
backup: yes
register: chrony_config_changed
when: inventory_hostname not in ['kronoss', 'demeter']
- name: Redémarrer le service Chrony
ansible.builtin.systemd:
name: chronyd
state: restarted
enabled: yes
register: chrony_service_restarted
when: inventory_hostname not in ['kronoss', 'demeter']
- name: Vérifier la synchronisation NTP
ansible.builtin.command:
cmd: chronyc tracking
register: ntp_status
changed_when: false
# --- Installation agent OCS Inventory ---
- name: Installer l'agent OCS Inventory
ansible.builtin.apt:
name: ocsinventory-agent
state: present
register: ocs_installed
- name: Créer le répertoire de configuration OCS
ansible.builtin.file:
path: /etc/ocsinventory
state: directory
mode: '0755'
- name: Copier le certificat CA pour OCS
ansible.builtin.copy:
src: /etc/ansible/files/ca.cert.pem
dest: /etc/ocsinventory/cacert.pem
mode: '0644'
- name: Déployer la configuration OCS
ansible.builtin.copy:
dest: /etc/ocsinventory/ocsinventory-agent.cfg
content: |
server=https://serveur-ocs.domaine.local/ocsinventory
ssl=1
ca=/etc/ocsinventory/cacert.pem
logfile=/var/log/ocsinventory-agent/ocsinventory-agent.log
debug=0
backup: yes
- name: Lancer un inventaire immédiat si nouvel install
ansible.builtin.command:
cmd: ocsinventory-agent --force
changed_when: false
when: ocs_installed.changed
# --- Mise à jour des paquets ---
- name: Mettre à jour la liste des paquets
ansible.builtin.apt:
update_cache: yes
cache_valid_time: 3600
register: apt_update_result
- name: Effectuer une mise à jour complète des paquets
ansible.builtin.apt:
upgrade: full
autoremove: yes
autoclean: yes
register: apt_upgrade_result
# --- Gestion du redémarrage (informations uniquement) ---
- name: Vérifier si un redémarrage est nécessaire
ansible.builtin.stat:
path: /var/run/reboot-required
register: reboot_required
# --- Vérification espace disque (seuil 70%) ---
- name: Vérifier espace disque sur chaque serveur
ansible.builtin.shell: >
df -h --output=source,pcent,target | tail -n +2 |
grep -v tmpfs | grep -v udev |
awk -F'[% ]+' '$2 >= 70 {print $1"|"$2"|"$3}'
register: disk_check
changed_when: false
# --- Vérification certificats SSL (seuil 30 jours) ---
- name: Vérifier expiration certificats SSL
ansible.builtin.shell: |
for cert in /etc/letsencrypt/live/*/cert.pem; do
domain=$(echo $cert | cut -d'/' -f5)
expiry=$(openssl x509 -enddate -noout -in $cert | cut -d'=' -f2)
expiry_epoch=$(date -d "$expiry" +%s)
now_epoch=$(date +%s)
days_left=$(( ($expiry_epoch - $now_epoch) / 86400 ))
echo "$domain|$days_left"
done
register: ssl_check
changed_when: false
delegate_to: serveur-web
run_once: true
# --- Construction du message récapitulatif global en HTML ---
- name: Construire le message récapitulatif global
ansible.builtin.set_fact:
recap_message: |
<html>
<body style="font-family: Arial, sans-serif; background-color: #f4f4f4; padding: 20px;">
<div style="max-width: 800px; margin: auto; background: white; border-radius: 8px; padding: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<h2 style="color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 10px;">
🖥️ Rapport de mise à jour Ansible
</h2>
<p style="color: #7f8c8d;">Exécuté le {{ ansible_date_time.date }} à {{ ansible_date_time.time }}</p>
{% for host in ansible_play_hosts %}
<div style="margin-top: 20px; border: 1px solid #ddd; border-radius: 6px; overflow: hidden;">
<div style="background-color: #3498db; color: white; padding: 10px 15px;">
<h3 style="margin: 0;">{{ host | upper }}</h3>
</div>
<table style="width: 100%; border-collapse: collapse;">
<tr style="background-color: #f9f9f9;">
<td style="padding: 8px 15px; border-bottom: 1px solid #eee; width: 60%;">Fuseau horaire (Europe/Paris)</td>
<td style="padding: 8px 15px; border-bottom: 1px solid #eee;">
{% if hostvars[host].timezone_set.changed %}
<span style="color: #e67e22;">⚠️ MODIFIÉ</span>
{% else %}
<span style="color: #27ae60;">✅ OK</span>
{% endif %}
</td>
</tr>
<tr>
<td style="padding: 8px 15px; border-bottom: 1px solid #eee;">Chrony</td>
<td style="padding: 8px 15px; border-bottom: 1px solid #eee;">
{% if hostvars[host].chrony_installed.changed %}
<span style="color: #e67e22;">⚠️ INSTALLÉ</span>
{% else %}
<span style="color: #27ae60;">✅ PRÉSENT</span>
{% endif %}
</td>
</tr>
<tr style="background-color: #f9f9f9;">
<td style="padding: 8px 15px; border-bottom: 1px solid #eee;">Configuration NTP</td>
<td style="padding: 8px 15px; border-bottom: 1px solid #eee;">
{% if hostvars[host].chrony_config_changed.changed %}
<span style="color: #e67e22;">⚠️ MODIFIÉ</span>
{% else %}
<span style="color: #27ae60;">✅ OK</span>
{% endif %}
</td>
</tr>
<tr>
<td style="padding: 8px 15px; border-bottom: 1px solid #eee;">Agent OCS Inventory</td>
<td style="padding: 8px 15px; border-bottom: 1px solid #eee;">
{% if hostvars[host].ocs_installed.changed %}
<span style="color: #3498db;">🔄 INSTALLÉ</span>
{% else %}
<span style="color: #27ae60;">✅ PRÉSENT</span>
{% endif %}
</td>
</tr>
<tr style="background-color: #f9f9f9;">
<td style="padding: 8px 15px; border-bottom: 1px solid #eee;">Mise à jour des paquets</td>
<td style="padding: 8px 15px; border-bottom: 1px solid #eee;">
{% if hostvars[host].apt_upgrade_result.changed %}
<span style="color: #3498db;">🔄 EFFECTUÉE</span>
{% else %}
<span style="color: #27ae60;">✅ À JOUR</span>
{% endif %}
</td>
</tr>
<tr style="background-color: #f9f9f9;">
<td style="padding: 8px 15px; border-bottom: 1px solid #eee;">Redémarrage nécessaire</td>
<td style="padding: 8px 15px; border-bottom: 1px solid #eee;">
{% if hostvars[host].reboot_required.stat.exists %}
<span style="color: #e74c3c;">🔴 OUI</span>
{% else %}
<span style="color: #27ae60;">✅ NON</span>
{% endif %}
</td>
</tr>
<tr>
<td style="padding: 8px 15px;">Espace disque (>70%)</td>
<td style="padding: 8px 15px;">
{% set disk_issues = [] %}
{% for line in hostvars[host].disk_check.stdout_lines %}
{% set parts = line.split('|') %}
{% if parts | length == 3 %}
{% set _ = disk_issues.append(parts) %}
{% endif %}
{% endfor %}
{% if disk_issues | length > 0 %}
{% for d in disk_issues %}
<span style="color: #e74c3c;">🔴 {{ d[0] }} → {{ d[1] }}% ({{ d[2] }})</span><br>
{% endfor %}
{% else %}
<span style="color: #27ae60;">✅ OK</span>
{% endif %}
</td>
</tr>
</table>
</div>
{% endfor %}
<!-- Section SSL -->
<div style="margin-top: 30px; border: 1px solid #ddd; border-radius: 6px; overflow: hidden;">
<div style="background-color: #8e44ad; color: white; padding: 10px 15px;">
<h3 style="margin: 0;">🔒 Certificats SSL</h3>
</div>
<table style="width: 100%; border-collapse: collapse;">
<tr style="background-color: #f0f0f0; font-weight: bold;">
<td style="padding: 8px 15px; border-bottom: 1px solid #eee; width: 60%;">Domaine</td>
<td style="padding: 8px 15px; border-bottom: 1px solid #eee;">Jours restants</td>
</tr>
{% for line in ssl_check.stdout_lines %}
{% set parts = line.split('|') %}
{% if parts | length == 2 %}
{% set days = parts[1] | int %}
<tr style="background-color: {% if days <= 30 %}#fdf2f2{% else %}#f9f9f9{% endif %};">
<td style="padding: 8px 15px; border-bottom: 1px solid #eee;">{{ parts[0] }}</td>
<td style="padding: 8px 15px; border-bottom: 1px solid #eee;">
{% if days <= 30 %}
<span style="color: #e74c3c; font-weight: bold;">🔴 {{ days }} jours — RENOUVELLEMENT URGENT</span>
{% elif days <= 60 %}
<span style="color: #e67e22;">⚠️ {{ days }} jours</span>
{% else %}
<span style="color: #27ae60;">✅ {{ days }} jours</span>
{% endif %}
</td>
</tr>
{% endif %}
{% endfor %}
</table>
</div>
<p style="color: #95a5a6; font-size: 12px; margin-top: 20px; text-align: center;">
Généré automatiquement par Ansible
</p>
</div>
</body>
</html>
run_once: true
# --- Envoi d'un seul e-mail récapitulatif (en HTML) ---
- name: Envoyer un e-mail récapitulatif unique
ansible.builtin.mail:
host: serveur-mail.domaine.local
port: 25
to: "admin@domaine.local"
subject: "Rapport de mise à jour Ansible - {{ ansible_date_time.date }}"
body: "{{ recap_message }}"
from: "ansible@serveur-ansible.domaine.local"
secure: never
subtype: html
run_once: true
delegate_to: localhost
Le résultat : au prochain passage du cron, tous les serveurs Linux installent l’agent, remontent leur inventaire, et le rapport mail indique proprement “INSTALLÉ” ou “PRÉSENT” pour chacun.
Ce qui fonctionne à la fin
- Serveur OCS en HTTPS avec certificat signé par la CA interne
- Agents Windows déployés via GPO avec configuration automatique
- Agents Linux déployés via Ansible
- Toutes les machines remontent leur inventaire complet : CPU, RAM, disques, logiciels, IP, MAC
Le résultat final est propre. Le chemin pour y arriver l’était beaucoup moins.
Leçons retenues
- Vérifier le mot de passe dans
z-ocsinventory-server.confaprès installation — le mot de passe y est en clair, restriction des permissions immédiatement après :sudo chmod 640 /etc/apache2/conf-available/z-ocsinventory-server.conf sudo chown root:www-data /etc/apache2/conf-available/z-ocsinventory-server.conf - Ne pas faire confiance au Packager pour propager les paramètres — écraser le
.inidirectement - Toujours copier le
cacert.pemdans le répertoire de l’agent en HTTPS XML::Entitiesn’est pas dans les dépôts Ubuntu 24.04 — prévoircpanpour l’installer