Skip to content

Instantly share code, notes, and snippets.

@stephdl
Last active March 10, 2026 15:52
Show Gist options
  • Select an option

  • Save stephdl/25851d01a86245db532d5ec74792259f to your computer and use it in GitHub Desktop.

Select an option

Save stephdl/25851d01a86245db532d5ec74792259f to your computer and use it in GitHub Desktop.
Déploiement GLPI avec Ansible — Stack Apache + MariaDB + PHP

🚀 Déploiement GLPI avec Ansible — Stack Apache + MariaDB + PHP

Session MCA — Ansible en pratique
Déploiement automatisé de GLPI 11 (outil ITSM/CMDB) sur Debian 13 et Rocky Linux 10.
Stack : Apache2 · MariaDB · PHP-FPM · Jinja2 templates · Variables externalisées

⚠️ Compatibilité PHP :
GLPI 10.x supporte PHP 7.4 à 8.3 max. GLPI 11.x supporte PHP 8.2 à 8.5.
Debian 13 embarquant PHP 8.4 natif, GLPI 11 est requis.


1. Structure du projet

glpi-ansible/
├── deploy_glpi.yml          # Playbook principal
├── vars/
│   └── main.yml             # Toutes les variables
└── templates/
    ├── virtualhost.conf.j2  # VirtualHost Apache (template Jinja2)
    └── glpi-cron.j2         # Crontab des tâches automatiques GLPI

Concept — pourquoi séparer variables et templates ?
Le playbook décrit quoi faire, les variables définissent avec quelles valeurs, les templates génèrent les fichiers de configuration. Cette séparation permet de réutiliser le même playbook pour déployer GLPI en dev, staging et prod en changeant uniquement vars/main.yml. C'est le principe DRY (Don't Repeat Yourself) appliqué à l'infrastructure.


2. Fichier de variables — vars/main.yml

---
# ============================================================
# GLPI — Version et téléchargement
# ============================================================
glpi_version: "11.0.4"
glpi_archive_url: "https://github.com/glpi-project/glpi/releases/download/{{ glpi_version }}/glpi-{{ glpi_version }}.tgz"
glpi_install_dir: "/var/www/glpi"
glpi_files_dir: "/var/lib/glpi"       # Données hors webroot (sécurité)
glpi_log_dir: "/var/log/glpi"

# ============================================================
# Base de données MariaDB
# ============================================================
db_host: "localhost"
db_name: "glpidb"
db_user: "glpiuser"
db_password: "ChangeMe_S3cur3!"       # À remplacer ou chiffrer avec ansible-vault
db_root_password: "R00t_ChangeMe!"

# ============================================================
# Apache / VirtualHost
# ============================================================
glpi_domain: "glpi.mondomaine.local"
glpi_admin_email: "admin@mondomaine.local"
apache_log_dir: "/var/log/apache2"    # Debian
apache_log_dir_rh: "/var/log/httpd"   # Rocky/RHEL

# ============================================================
# PHP
# ============================================================
# Stack PHP-FPM sur les deux distributions
# Debian 13 : PHP 8.4 — Rocky 10 : PHP 8.3
# PHP-FPM + proxy_fcgi : plus performant que mod_php, cohérent sur les deux distros

# ============================================================
# Système
# ============================================================
# Debian : www-data / Rocky : apache
apache_user: "{{ 'www-data' if ansible_facts['os_family'] == 'Debian' else 'apache' }}"
apache_group: "{{ 'www-data' if ansible_facts['os_family'] == 'Debian' else 'apache' }}"

# Socket PHP-FPM selon la distribution
php_fpm_socket: "{{ '/run/php/php8.4-fpm.sock' if ansible_facts['os_family'] == 'Debian' else '/run/php-fpm/www.sock' }}"

# ============================================================
# SSL — Certificat auto-signé
# ============================================================
ssl_cert_dir: "/etc/pki/tls/certs"
ssl_key_dir: "/etc/pki/tls/private"
ssl_cert: "{{ ssl_cert_dir }}/glpi.crt"
ssl_key: "{{ ssl_key_dir }}/glpi.key"
ssl_days: 3650

Concept — ansible-vault pour les mots de passe :
Les mots de passe en clair dans un fichier versionné sous Git sont une faille de sécurité. ansible-vault chiffre les valeurs sensibles avec AES-256.

# Chiffrer une valeur
ansible-vault encrypt_string 'ChangeMe_S3cur3!' --name 'db_password'

# Lancer le playbook avec le vault
ansible-playbook deploy_glpi.yml --ask-vault-pass

3. Template VirtualHost Apache — templates/virtualhost.conf.j2

Concept — templates Jinja2 :
Ansible utilise le moteur de templates Jinja2 (le même que Flask/Django) pour générer des fichiers de configuration dynamiques. Les variables entre {{ }} sont remplacées par leurs valeurs au moment du déploiement. Les blocs {% if %} permettent des configurations conditionnelles selon la distribution. Un seul template couvre ainsi Debian et Rocky.

# Fichier généré par Ansible — ne pas modifier manuellement
# Template : templates/virtualhost.conf.j2
# Déployé le : {{ ansible_date_time.date }}

# Directives globales de sécurité (hors VirtualHost)
ServerTokens Prod
ServerSignature Off

# -------------------------------------------------------
# VirtualHost HTTP — redirection forcée vers HTTPS
# -------------------------------------------------------
<VirtualHost *:80>
    ServerName {{ glpi_domain }}
    RewriteEngine On
    RewriteRule ^(.*)$ https://{{ glpi_domain }}$1 [R=301,L]
</VirtualHost>

# -------------------------------------------------------
# VirtualHost HTTPS — GLPI
# -------------------------------------------------------
<VirtualHost *:443>
    ServerName {{ glpi_domain }}
    ServerAdmin {{ glpi_admin_email }}

    DocumentRoot {{ glpi_install_dir }}/public

    # SSL
    SSLEngine on
    SSLCertificateFile    {{ ssl_cert }}
    SSLCertificateKeyFile {{ ssl_key }}
    SSLProtocol           all -SSLv3 -TLSv1 -TLSv1.1
    SSLCipherSuite        HIGH:!aNULL:!MD5

    <Directory {{ glpi_install_dir }}/public>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted

        # Routeur Symfony — toutes les requêtes vers index.php
        RewriteEngine On
        RewriteCond %{REQUEST_FILENAME} !-f
        RewriteRule ^(.*)$ index.php [QSA,L]
    </Directory>

    # PHP-FPM via socket Unix
    <FilesMatch \.php$>
        SetHandler "proxy:unix:{{ php_fpm_socket }}|fcgi://localhost"
    </FilesMatch>

    # Logs
{% if ansible_facts['os_family'] == 'Debian' %}
    ErrorLog {{ apache_log_dir }}/glpi_error.log
    CustomLog {{ apache_log_dir }}/glpi_access.log combined
{% else %}
    ErrorLog {{ apache_log_dir_rh }}/glpi_error.log
    CustomLog {{ apache_log_dir_rh }}/glpi_access.log combined
{% endif %}

    # Headers de sécurité
    Header always set X-Content-Type-Options "nosniff"
    Header always set X-Frame-Options "SAMEORIGIN"
    Header always set X-XSS-Protection "1; mode=block"
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"

</VirtualHost>

4. Template Crontab GLPI — templates/glpi-cron.j2

GLPI nécessite l'exécution régulière de tâches automatiques (notifications, inventaire, nettoyage) via son système de cron interne.

# Crontab GLPI — généré par Ansible
# Tâches automatiques : notifications, alertes, maintenance
# Exécuté en tant que : {{ apache_user }}

# Toutes les minutes — déclencheur principal des actions automatiques
* * * * * {{ apache_user }} php {{ glpi_install_dir }}/front/cron.php --force > /dev/null 2>&1

# Toutes les heures — nettoyage des sessions expirées
0 * * * * {{ apache_user }} php {{ glpi_install_dir }}/bin/console glpi:session:cleanup > /dev/null 2>&1

# Tous les jours à 2h — optimisation de la base de données
0 2 * * * {{ apache_user }} php {{ glpi_install_dir }}/bin/console glpi:database:optimize --no-interaction > /dev/null 2>&1

5. Playbook principal — deploy_glpi.yml

---
- name: Déploiement GLPI — Apache + MariaDB + PHP
  hosts: all_servers
  become: true
  gather_facts: true   # Explicite : requis pour os_family avant toute tâche conditionnelle
  vars_files:
    - vars/main.yml

  # ============================================================
  # HANDLERS — déclenchés uniquement si une tâche notifie
  # ============================================================
  handlers:

    - name: restart apache
      ansible.builtin.service:
        name: "{{ 'apache2' if ansible_facts['os_family'] == 'Debian' else 'httpd' }}"
        state: restarted

    - name: reload apache
      ansible.builtin.service:
        name: "{{ 'apache2' if ansible_facts['os_family'] == 'Debian' else 'httpd' }}"
        state: reloaded

    - name: restart mariadb
      ansible.builtin.service:
        name: mariadb
        state: restarted

    - name: restart php-fpm
      ansible.builtin.service:
        name: "{{ 'php8.4-fpm' if ansible_facts['os_family'] == 'Debian' else 'php-fpm' }}"
        state: restarted

  tasks:

    # ----------------------------------------------------------
    # ÉTAPE 1 — Collecte des facts système
    # ----------------------------------------------------------

    - name: Collecter les facts Ansible
      ansible.builtin.setup:
        gather_subset:
          - distribution
          - date_time

    # ----------------------------------------------------------
    # ÉTAPE 2 — Installation des paquets
    # ----------------------------------------------------------

    - name: "[Debian] Installer Apache, MariaDB, PHP et extensions"
      ansible.builtin.apt:
        name:
          - apache2
          - mariadb-server
          - php
          - php8.4-fpm
          - php-mysql
          - php-xml
          - php-curl
          - php-gd
          - php-mbstring
          - php-intl
          - php-ldap
          - php-zip
          - php-bz2
          - php-opcache
          - php-bcmath
          - python3-pymysql
          - sudo
          - tar
          - curl
          - acl
        state: present
        update_cache: yes
      when: ansible_facts['os_family'] == 'Debian'
      notify: restart apache

    - name: "[Rocky] Installer Apache, MariaDB, PHP et extensions"
      ansible.builtin.dnf:
        name:
          - httpd
          - mariadb-server
          - php
          - php-fpm
          - php-mysqlnd
          - php-xml
          - php-curl
          - php-gd
          - php-mbstring
          - php-intl
          - php-ldap
          - php-zip
          - php-bz2
          - php-opcache
          - php-bcmath
          - python3-PyMySQL
          - firewalld           # ← Démon firewall (absent par défaut sur certaines images cloud)
          - python3-firewall    # ← FIX : requis par ansible.posix.firewalld
          - sudo
          - tar
          - curl
          - openssl
        state: present
      when: ansible_facts['os_family'] == 'RedHat'
      notify: restart apache

    # ----------------------------------------------------------
    # ÉTAPE 3 — Activation des services
    # ----------------------------------------------------------

    - name: Activer et démarrer MariaDB
      ansible.builtin.service:
        name: mariadb
        state: started
        enabled: yes
      ignore_errors: "{{ ansible_check_mode }}"

    - name: "[Debian] Activer et démarrer Apache2"
      ansible.builtin.service:
        name: apache2
        state: started
        enabled: yes
      when: ansible_facts['os_family'] == 'Debian'

    - name: "[Debian] Activer et démarrer PHP-FPM"
      ansible.builtin.service:
        name: php8.4-fpm
        state: started
        enabled: yes
      when: ansible_facts['os_family'] == 'Debian'

    - name: "[Rocky] Activer et démarrer httpd"
      ansible.builtin.service:
        name: httpd
        state: started
        enabled: yes
      when: ansible_facts['os_family'] == 'RedHat'

    - name: "[Rocky] Activer et démarrer PHP-FPM"
      ansible.builtin.service:
        name: php-fpm
        state: started
        enabled: yes
      when: ansible_facts['os_family'] == 'RedHat'

    # ----------------------------------------------------------
    # ÉTAPE 4 — Modules Apache
    # ----------------------------------------------------------

    - name: "[Debian] Activer les modules Apache nécessaires"
      community.general.apache2_module:
        name: "{{ item }}"
        state: present
      loop:
        - rewrite
        - proxy_fcgi
        - setenvif
        - headers
      notify: restart apache
      when: ansible_facts['os_family'] == 'Debian'

    - name: "[Debian] Activer la configuration PHP-FPM dans Apache"
      ansible.builtin.command:
        cmd: a2enconf php8.4-fpm
      notify: restart apache
      when: ansible_facts['os_family'] == 'Debian'
      changed_when: true

    - name: "[Debian] Activer le module SSL Apache"
      community.general.apache2_module:
        name: ssl
        state: present
      notify: restart apache
      when: ansible_facts['os_family'] == 'Debian'

    - name: "Configurer session.cookie_secure dans PHP-FPM (HTTPS)"
      ansible.builtin.lineinfile:
        path: "{{ '/etc/php/8.4/fpm/php.ini' if ansible_facts['os_family'] == 'Debian' else '/etc/php.ini' }}"
        regexp: '^;?session.cookie_secure'
        line: 'session.cookie_secure = On'
      notify: restart php-fpm

    - name: "Configurer session.cookie_httponly dans PHP-FPM"
      ansible.builtin.lineinfile:
        path: "{{ '/etc/php/8.4/fpm/php.ini' if ansible_facts['os_family'] == 'Debian' else '/etc/php.ini' }}"
        regexp: '^;?session.cookie_httponly'
        line: 'session.cookie_httponly = On'
      notify: restart php-fpm

    - name: "Configurer session.cookie_samesite dans PHP-FPM"
      ansible.builtin.lineinfile:
        path: "{{ '/etc/php/8.4/fpm/php.ini' if ansible_facts['os_family'] == 'Debian' else '/etc/php.ini' }}"
        regexp: '^;?session.cookie_samesite'
        line: 'session.cookie_samesite = Lax'
      notify: restart php-fpm

    - name: "[Rocky] Installer mod_ssl"
      ansible.builtin.dnf:
        name: mod_ssl
        state: present
      when: ansible_facts['os_family'] == 'RedHat'
      notify: restart apache

    - name: "Créer les répertoires SSL"
      ansible.builtin.file:
        path: "{{ item }}"
        state: directory
        mode: '0755'
      loop:
        - "{{ ssl_cert_dir }}"
        - "{{ ssl_key_dir }}"

    - name: "Générer le certificat SSL auto-signé ({{ ssl_days }} jours)"
      ansible.builtin.command:
        cmd: >
          openssl req -x509 -nodes
          -days {{ ssl_days }}
          -newkey rsa:2048
          -keyout {{ ssl_key }}
          -out {{ ssl_cert }}
          -subj "/C=FR/ST=Local/L=Local/O=GLPI/CN={{ glpi_domain }}"
        creates: "{{ ssl_cert }}"
      notify: restart apache

    - name: "Sécuriser la clé privée SSL"
      ansible.builtin.file:
        path: "{{ ssl_key }}"
        mode: '0600'

    # ----------------------------------------------------------
    # ÉTAPE 5 — Firewall (Rocky uniquement)
    # ← FIX : firewalld + python3-firewall installés à l'étape 2.
    #   Le service doit être démarré avant d'appliquer les règles.
    #   Le guard `when: os_family == 'RedHat'` évite tout appel
    #   sur les hôtes Debian (debian13-glpi ne jouera plus ces tâches).
    # ----------------------------------------------------------

    - name: "[Rocky] Activer et démarrer firewalld"
      ansible.builtin.service:
        name: firewalld
        state: started
        enabled: yes
      when: ansible_facts['os_family'] == 'RedHat'

    - name: "[Rocky] Ouvrir le port 80 dans firewalld"
      ansible.posix.firewalld:
        service: http
        permanent: yes
        state: enabled
        immediate: yes
      when: ansible_facts['os_family'] == 'RedHat'

    - name: "[Rocky] Ouvrir le port 443 dans firewalld"
      ansible.posix.firewalld:
        service: https
        permanent: yes
        state: enabled
        immediate: yes
      when: ansible_facts['os_family'] == 'RedHat'

    # ----------------------------------------------------------
    # ÉTAPE 6 — Sécurisation MariaDB
    # ----------------------------------------------------------

    - name: Définir le mot de passe root MariaDB
      community.mysql.mysql_user:
        name: root
        host: localhost
        password: "{{ db_root_password }}"
        login_unix_socket: "{{ '/var/run/mysqld/mysqld.sock' if ansible_facts['os_family'] == 'Debian' else '/var/lib/mysql/mysql.sock' }}"
        state: present

    - name: Supprimer les utilisateurs anonymes MariaDB
      community.mysql.mysql_user:
        name: ''
        host_all: yes
        state: absent
        login_user: root
        login_password: "{{ db_root_password }}"
        login_unix_socket: "{{ '/var/run/mysqld/mysqld.sock' if ansible_facts['os_family'] == 'Debian' else '/var/lib/mysql/mysql.sock' }}"

    - name: Supprimer la base de test MariaDB
      community.mysql.mysql_db:
        name: test
        state: absent
        login_user: root
        login_password: "{{ db_root_password }}"
        login_unix_socket: "{{ '/var/run/mysqld/mysqld.sock' if ansible_facts['os_family'] == 'Debian' else '/var/lib/mysql/mysql.sock' }}"

    # ----------------------------------------------------------
    # ÉTAPE 7 — Création de la base de données GLPI
    # ----------------------------------------------------------

    - name: Créer la base de données GLPI
      community.mysql.mysql_db:
        name: "{{ db_name }}"
        encoding: utf8mb4
        collation: utf8mb4_unicode_ci
        state: present
        login_user: root
        login_password: "{{ db_root_password }}"

    - name: Créer l'utilisateur MariaDB pour GLPI
      community.mysql.mysql_user:
        name: "{{ db_user }}"
        password: "{{ db_password }}"
        priv: "{{ db_name }}.*:ALL"
        host: localhost
        state: present
        login_user: root
        login_password: "{{ db_root_password }}"

    # ----------------------------------------------------------
    # ÉTAPE 8 — Téléchargement et déploiement de GLPI
    # ----------------------------------------------------------

    - name: Créer les répertoires GLPI
      ansible.builtin.file:
        path: "{{ item }}"
        state: directory
        owner: "{{ apache_user }}"
        group: "{{ apache_group }}"
        mode: '0755'
      loop:
        - "{{ glpi_install_dir }}"
        - "{{ glpi_files_dir }}"
        - "{{ glpi_log_dir }}"

    - name: Télécharger l'archive GLPI {{ glpi_version }}
      ansible.builtin.get_url:
        url: "{{ glpi_archive_url }}"
        dest: "/tmp/glpi-{{ glpi_version }}.tgz"
        mode: '0644'

    - name: Extraire l'archive GLPI
      ansible.builtin.unarchive:
        src: "/tmp/glpi-{{ glpi_version }}.tgz"
        dest: "/var/www/"
        remote_src: yes
        owner: "{{ apache_user }}"
        group: "{{ apache_group }}"
        creates: "{{ glpi_install_dir }}/index.php"

    - name: Appliquer les permissions sur les répertoires GLPI
      ansible.builtin.file:
        path: "{{ item }}"
        owner: "{{ apache_user }}"
        group: "{{ apache_group }}"
        recurse: yes
      loop:
        - "{{ glpi_install_dir }}"
        - "{{ glpi_files_dir }}"
        - "{{ glpi_log_dir }}"

    # ----------------------------------------------------------
    # ÉTAPE 9 — Configuration downstream.php (chemins sécurisés)
    # ----------------------------------------------------------

    - name: Créer le fichier downstream.php pour sécuriser les chemins
      ansible.builtin.copy:
        dest: "{{ glpi_install_dir }}/config/downstream.php"
        owner: "{{ apache_user }}"
        group: "{{ apache_group }}"
        mode: '0644'
        content: |
          <?php
          define('GLPI_VAR_DIR', '{{ glpi_files_dir }}');
          define('GLPI_LOG_DIR', '{{ glpi_log_dir }}');
          define('GLPI_CACHE_DIR', '{{ glpi_files_dir }}/_cache');

    - name: Créer les sous-répertoires GLPI dans files_dir
      ansible.builtin.file:
        path: "{{ item }}"
        state: directory
        owner: "{{ apache_user }}"
        group: "{{ apache_group }}"
        mode: '0755'
      loop:
        - "{{ glpi_files_dir }}/_cache"
        - "{{ glpi_files_dir }}/_cron"
        - "{{ glpi_files_dir }}/_dumps"
        - "{{ glpi_files_dir }}/_graphs"
        - "{{ glpi_files_dir }}/_lock"
        - "{{ glpi_files_dir }}/_pictures"
        - "{{ glpi_files_dir }}/_plugins"
        - "{{ glpi_files_dir }}/_rss"
        - "{{ glpi_files_dir }}/_sessions"
        - "{{ glpi_files_dir }}/_tmp"
        - "{{ glpi_files_dir }}/_uploads"
        - "{{ glpi_files_dir }}/_log"

    - name: Supprimer le répertoire files de l'archive (remplacé par un lien)
      ansible.builtin.file:
        path: "{{ glpi_install_dir }}/files"
        state: absent

    - name: Créer le lien symbolique files → glpi_files_dir dans le webroot
      ansible.builtin.file:
        src: "{{ glpi_files_dir }}"
        dest: "{{ glpi_install_dir }}/files"
        state: link
        owner: "{{ apache_user }}"
        group: "{{ apache_group }}"
        force: yes

    # ----------------------------------------------------------
    # ÉTAPE 10 — Déploiement du VirtualHost via template Jinja2
    # ----------------------------------------------------------

    - name: "[Debian] Déployer le VirtualHost Apache depuis le template"
      ansible.builtin.template:
        src: templates/virtualhost.conf.j2
        dest: "/etc/apache2/sites-available/glpi.conf"
        owner: root
        group: root
        mode: '0644'
      notify: reload apache
      when: ansible_facts['os_family'] == 'Debian'

    - name: "[Rocky] Déployer le VirtualHost Apache depuis le template"
      ansible.builtin.template:
        src: templates/virtualhost.conf.j2
        dest: "/etc/httpd/conf.d/glpi.conf"
        owner: root
        group: root
        mode: '0644'
      notify: reload apache
      when: ansible_facts['os_family'] == 'RedHat'

    - name: "[Debian] Activer le site GLPI (a2ensite)"
      ansible.builtin.command:
        cmd: a2ensite glpi.conf
      notify: reload apache
      when: ansible_facts['os_family'] == 'Debian'
      changed_when: true

    - name: "[Debian] Désactiver le site par défaut Apache"
      ansible.builtin.command:
        cmd: a2dissite 000-default.conf
      notify: reload apache
      when: ansible_facts['os_family'] == 'Debian'
      changed_when: true

    # ----------------------------------------------------------
    # ÉTAPE 11 — Crontab GLPI
    # ----------------------------------------------------------

    - name: Déployer la crontab GLPI depuis le template
      ansible.builtin.template:
        src: templates/glpi-cron.j2
        dest: /etc/cron.d/glpi
        owner: root
        group: root
        mode: '0644'

    # ----------------------------------------------------------
    # ÉTAPE 12 — SELinux (Rocky Linux uniquement)
    # ----------------------------------------------------------

    - name: "[Rocky] Autoriser Apache à se connecter à la BDD (SELinux)"
      ansible.posix.seboolean:
        name: httpd_can_network_connect_db
        state: yes
        persistent: yes
      when: ansible_facts['os_family'] == 'RedHat'

    - name: "[Rocky] Autoriser Apache à lire/écrire dans le webroot (SELinux)"
      community.general.sefcontext:
        target: "{{ glpi_install_dir }}(/.*)?"
        setype: httpd_sys_rw_content_t
        state: present
      when: ansible_facts['os_family'] == 'RedHat'

    - name: "[Rocky] Appliquer le contexte SELinux"
      ansible.builtin.command:
        cmd: "restorecon -Rv {{ glpi_install_dir }}"
      when: ansible_facts['os_family'] == 'RedHat'
      changed_when: true

    # ----------------------------------------------------------
    # ÉTAPE 13 — Installation CLI de GLPI (sans wizard web)
    # ----------------------------------------------------------

    - name: Vérifier si GLPI est déjà installé
      ansible.builtin.stat:
        path: "{{ glpi_install_dir }}/config/config_db.php"
      register: glpi_config

    - name: Vérifier les prérequis PHP pour GLPI
      ansible.builtin.command:
        cmd: php {{ glpi_install_dir }}/bin/console system:check_requirements --allow-superuser
      register: glpi_requirements
      failed_when: false
      changed_when: false

    - name: Afficher les prérequis PHP
      ansible.builtin.debug:
        msg: "{{ glpi_requirements.stdout_lines }}"

    - name: Installer GLPI via la CLI (si pas encore installé)
      ansible.builtin.command:
        cmd: >
          php {{ glpi_install_dir }}/bin/console db:install
          --db-host={{ db_host }}
          --db-name={{ db_name }}
          --db-user={{ db_user }}
          --db-password={{ db_password }}
          --default-language=fr_FR
          --no-interaction
          --allow-superuser
      environment:
        GLPI_VAR_DIR: "{{ glpi_files_dir }}"
        GLPI_LOG_DIR: "{{ glpi_log_dir }}"
      when: not glpi_config.stat.exists
      register: glpi_install_result

    - name: Afficher le résultat de l'installation GLPI
      ansible.builtin.debug:
        msg: "{{ glpi_install_result.stdout_lines }}"
      when: not glpi_config.stat.exists

    - name: Définir l'URL de base GLPI en base de données
      community.mysql.mysql_query:
        login_user: "{{ db_user }}"
        login_password: "{{ db_password }}"
        login_db: "{{ db_name }}"
        query: "UPDATE glpi_configs SET value='https://{{ glpi_domain }}' WHERE name='url_base'"
      when: not glpi_config.stat.exists

    - name: Corriger les permissions après installation (fichiers créés par root)
      ansible.builtin.file:
        path: "{{ item }}"
        owner: "{{ apache_user }}"
        group: "{{ apache_group }}"
        recurse: yes
      loop:
        - "{{ glpi_files_dir }}"
        - "{{ glpi_log_dir }}"
        - "{{ glpi_install_dir }}/config"
      when: not glpi_config.stat.exists

    - name: Vider le cache GLPI après installation
      ansible.builtin.command:
        cmd: php {{ glpi_install_dir }}/bin/console cache:clear
      become_user: "{{ apache_user }}"
      when: not glpi_config.stat.exists
      changed_when: true

    # ----------------------------------------------------------
    # ÉTAPE 14 — Sécurisation post-installation
    # ----------------------------------------------------------

    - name: Supprimer le répertoire d'installation GLPI (sécurité)
      ansible.builtin.file:
        path: "{{ glpi_install_dir }}/install"
        state: absent

    - name: Afficher l'URL d'accès GLPI
      ansible.builtin.debug:
        msg:
          - "✅ GLPI {{ glpi_version }} déployé avec succès !"
          - "🌐 URL : https://{{ glpi_domain }}"
          - "👤 Identifiants par défaut : glpi / glpi"
          - "⚠️  Changer les mots de passe par défaut immédiatement !"

Concept — handlers :
Un handler est une tâche déclenchée uniquement si une autre tâche l'a notifiée via notify: et si cette tâche a réellement effectué un changement (changed). Cela évite de redémarrer Apache inutilement à chaque exécution. Les handlers s'exécutent une seule fois à la fin du play, même s'ils sont notifiés plusieurs fois.

Concept — idempotence avec creates: et stat: :
creates: sur unarchive indique qu'Ansible ne ré-extrait pas si le fichier cible existe déjà. stat: + register + when: not x.stat.exists permet de conditionner une tâche à l'absence d'un fichier. Ces patterns garantissent qu'on peut relancer le playbook sans tout écraser.


6. Exécution du playbook

# Vérifier la syntaxe avant tout
ansible-playbook deploy_glpi.yml -i inventory.ini --syntax-check

# Simuler sans rien modifier (dry-run)
ansible-playbook deploy_glpi.yml -i inventory.ini --check

# Déploiement réel sur tous les serveurs
ansible-playbook deploy_glpi.yml -i inventory.ini --ask-vault-pass

# -------------------------------------------------------
# Cibler uniquement Debian 13
# -------------------------------------------------------
ansible-playbook deploy_glpi.yml -i inventory.ini --ask-vault-pass \
  --limit debian_servers

# Ou par nom d'hôte
ansible-playbook deploy_glpi.yml -i inventory.ini --ask-vault-pass \
  --limit debian13-glpi

# -------------------------------------------------------
# Cibler uniquement Rocky Linux 10
# -------------------------------------------------------
ansible-playbook deploy_glpi.yml -i inventory.ini --ask-vault-pass \
  --limit rocky_servers

# Ou par nom d'hôte
ansible-playbook deploy_glpi.yml -i inventory.ini --ask-vault-pass \
  --limit rocky10-glpi

# -------------------------------------------------------
# Options utiles
# -------------------------------------------------------

# Relancer uniquement à partir d'une étape échouée
ansible-playbook deploy_glpi.yml -i inventory.ini --ask-vault-pass \
  --start-at-task "Créer la base de données GLPI"

# Verbose pour déboguer
ansible-playbook deploy_glpi.yml -i inventory.ini -vvv

Concept — --limit :
--limit restreint l'exécution à un sous-ensemble de l'inventaire. On peut passer un nom d'hôte, un groupe (debian_servers, rocky_servers), ou une combinaison (--limit "debian_servers,rocky10-glpi"). C'est indispensable pour déployer sur un seul nœud sans toucher les autres.

Concept — --check vs exécution réelle :
Le mode --check est un dry-run : Ansible simule chaque tâche et indique ce qui serait modifié sans toucher au système. Certaines tâches (command, shell) ne peuvent pas être simulées et sont marquées skipped en mode check. C'est un filet de sécurité précieux avant un déploiement en production.


7. Inventaire inventory.ini

[debian_servers]
debian13-glpi ansible_host=192.168.1.10 ansible_user=admin

[rocky_servers]
rocky10-glpi ansible_host=192.168.1.20 ansible_user=rocky

[all_servers:children]
debian_servers
rocky_servers

[all_servers:vars]
ansible_python_interpreter=/usr/bin/python3
ansible_ssh_private_key_file=~/.ssh/id_ed25519

8. Collections Ansible requises

Ce playbook utilise des modules de collections tierces. À installer avant le premier run :

# Installer les collections nécessaires
ansible-galaxy collection install community.mysql
ansible-galaxy collection install community.general
ansible-galaxy collection install ansible.posix

# Ou via un fichier requirements.yml
cat > requirements.yml << 'EOF'
collections:
  - name: community.mysql
  - name: community.general
  - name: ansible.posix
EOF

ansible-galaxy collection install -r requirements.yml

Concept — collections Ansible Galaxy :
Ansible Galaxy est le dépôt officiel de roles et collections communautaires, comparable à PyPI pour Python ou npm pour Node.js. Les collections regroupent des modules, plugins et roles autour d'un domaine (ici community.mysql pour tout ce qui concerne MySQL/MariaDB). Les modules builtin (préfixe ansible.builtin) sont inclus nativement sans installation.


9. Accès et identifiants par défaut GLPI

Compte Login Mot de passe par défaut
Super-Admin glpi glpi
Admin tech tech
Post-only post-only postonly
Normal normal normal

⚠️ Changer tous ces mots de passe immédiatement après la première connexion.


10. Ressources


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment