diff --git a/pr/137/.well-known/mta-sts.txt b/pr/137/.well-known/mta-sts.txt deleted file mode 100644 index 740174fd..00000000 --- a/pr/137/.well-known/mta-sts.txt +++ /dev/null @@ -1,6 +0,0 @@ - -version: STSv1 -mode: enforce -mx: mail.protonmail.ch -mx: mailsec.protonmail.ch -max_age: 1209600 \ No newline at end of file diff --git a/pr/137/.well-known/security.txt b/pr/137/.well-known/security.txt deleted file mode 100644 index 6a418ea6..00000000 --- a/pr/137/.well-known/security.txt +++ /dev/null @@ -1,3 +0,0 @@ -Contact: security@rix.fr -Expires: Wed, 1 Jan 2030 00:00 +0200 -Encryption: https://keys.openpgp.org/vks/v1/by-fingerprint/67B08D2BE9C8ACC2C40D28F1F69D39C94B03BD79 diff --git a/pr/137/404.html b/pr/137/404.html deleted file mode 100644 index 0256e385..00000000 --- a/pr/137/404.html +++ /dev/null @@ -1,270 +0,0 @@ - - - - - - - Page introuvable - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - Retour à l'accueil - - - - - - - - -
-
-
-
-
-

- Erreur - 404 -

-

La page que vous recherchez n'existe pas ou a été supprimée.

-

Que voulez-vous faire ?

- -
-
-
- - - - - - diff --git a/pr/137/a-propos/index.html b/pr/137/a-propos/index.html deleted file mode 100644 index 2cb8ad84..00000000 --- a/pr/137/a-propos/index.html +++ /dev/null @@ -1,413 +0,0 @@ - - - - - - - Rix 🐺 - À propos de nous. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - Retour à l'accueil - - - - - - - - -
-
-
- - -
-
-
- -

- Histoire - Notre histoire -

-
-
-
-
    -
  • -

    Analyse

    -

    Parce que nous avons évolué au plus près des équipes de développement, nous savons qu'il est essentiel de bien comprendre le métier et d'assimiler ses contraintes.

    -
  • -
  • -

    Exploitation

    -

    Parce que nous sommes issus de l’exploitation nous avons la prétention d’avoir une bonne vision des problématiques de mise en oeuvre d’une application web moderne. Et bien évidemment de son maintien en conditions opérationnelles.

    -
  • -
  • -

    Hébergement

    -

    Spécialistes de l’hébergement des applications PHP/ Symfony métier sur-mesure nous avons au cours de notre expérience été confrontés à beaucoup des situations que vous rencontrez peut-être aujourd’hui.

    -
  • -
-
-
-

- Nos valeurs - «Profile, don't assume !» -

-
-
    -
  • -
    - -

    Anticiper

    -
    - Personne n’aime travailler en plein incident, nous mettons un point d’honneur à essayer d’anticiper les plus courants et quand nous n'y arrivons pas à effectuer un diagnostique précis et rapide. -
  • -
  • -
    - -

    Préserver et sécuriser

    -
    - Vos données sont le coeur de votre métier. Nous nous efforçons de toujours être irréprochables quant à leur conservation, à leur intégrité et à leur sécurité. -
  • -
  • -
    - -

    Accompagner

    -
    - Nous avons conscience que nos métiers se sont énormément complexifiés, nous proposons toujours la solution qui nous semble le mieux convenir au contexte actuel d'un client sans jamais oublier que ce projet va évoluer, se transformer et grandir. -
  • -
-
-
-
-
-
- -
-
-

- Notre équipe - Notre meute, c'est notre force -

-

Nous sommes soudés et fiers de l'être depuis de nombreuses années.

- -
-
-
- - - - - - diff --git a/pr/137/android-chrome-192x192.png b/pr/137/android-chrome-192x192.png deleted file mode 100644 index 751e5e92..00000000 Binary files a/pr/137/android-chrome-192x192.png and /dev/null differ diff --git a/pr/137/apple-touch-icon.png b/pr/137/apple-touch-icon.png deleted file mode 100644 index 229f5b3f..00000000 Binary files a/pr/137/apple-touch-icon.png and /dev/null differ diff --git a/pr/137/blog/cours/ansible/ansible-environnement-cle-en-main/index.html b/pr/137/blog/cours/ansible/ansible-environnement-cle-en-main/index.html deleted file mode 100644 index cc83d5df..00000000 --- a/pr/137/blog/cours/ansible/ansible-environnement-cle-en-main/index.html +++ /dev/null @@ -1,468 +0,0 @@ - - - - - - - Ansible - Un environnement de travail clé en main avec Lazy Ansible. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - Retour à l'accueil - - - - - - - - -
-
-
- - -
-
-
-
-
    -
  • - #Cours -
  • -
  • - #Ansible -
  • -
  • - #Automation -
  • -
  • - #Manala -
  • -
-

Ansible - Un environnement de travail clé en main avec Lazy Ansible.

-

Utilisation de la recipe Lazy Ansible du projet Manala pour mettre en oeuvre un environnement de travail dédié Ansible.

-
-
    -
  1. - Préambule -
  2. -
  3. - Pré-requis -
  4. -
  5. - Mise en route -
  6. -
  7. - Fichiers de configuration -
      -
    1. - Configurer Ansible -
    2. -
    3. - Configurer SSH -
    4. -
    5. - Configurer GIT -
    6. -
    -
  8. -
- -
-
-

Préambule

-

Les environnements dits « lazy » issus du projet Manala sont des outils destinés à mettre en oeuvre de manière rapide des environnements de travail.

-

Leur finalité étant multiple:

-
    -
  • Être en capacité de déployer un environnement sans être familier avec l'outil cible;
  • -
  • Ne pas avoir à installer et/ou modifier sa configuration locale (sur la machine de travail);
  • -
  • Disposer d'environnements homogènes de manière à favoriser le collaboratif.
  • -
-

Dans le cadre de travaux autour d'Ansible ou si vous suivez la partie « cours » nous utiliserons la « recipe » qui lui est dédiée (https://github.com/manala/manala-recipes/tree/master/lazy.ansible), son utilisation nécessite l'installation de Manala.

-

Pré-requis

- -

Mise en route

-

La mise en place d'un nouvel environnement en utilisant Manala est relativement simple, il nous suffit de l'initialiser dans un répertoire dédié (cela peut-être un projet existant) à l'aide de la commande manala init.

-

Démonstration ci-dessous:

-
- -
- Création d'un environnement Ansible avec Manala. -
-
-

Nous disposons ainsi d'un environnement Ansible « conteneurisé » utilisable en quelques secondes sans n'avoir rien à installer sur nos postes (à l'exception de docker bien évidemment). -Et pour ceux et celles qui doivent faire avec plusieurs versions d'Ansible dans leur quotidien, cela permet d'avoir des environnements isolés et dédiés à certaines versions de l'outils.

-

Fichiers de configuration

-

Il est bien évidemment possible à partir des fichiers de configuration Manala, d'agir sur les configurations d'ansible mais également la configuration SSH.

-

Pour cela il faudra modifier le fichier .manala.yaml qui doit, après la manipulation précédente, se trouver à la racine de votre répertoire de travail.

-
-

Prendre en compte vos modifications

-

- Si vous modifiez les fichiers de configuration comme indiqué ci-dessous il faudra penser à utiliser la commande manala up afin que vos modifications soient bien prises en compte. -

-
-

Configurer Ansible

-

Il est possible d'interagir sur la configuration Ansible à partir de la section suivante:

-
system:
-    ansible:
-        version: 2.15.5
-        config: |
-            [defaults]
-            control_path_dir = /tmp/ansible/cp
-            [privilege_escalation]
-            become = True
-            become_flags = -H -S
-            [ssh_connection]
-            control_path = /tmp/%%h-%%r
-

On notera qu'il est possible d'agir sur la version d'ansible utilisée dans notre conteneur Docker mais également sur les directives de configuration propres à Ansible (https://docs.ansible.com/ansible/latest/reference_appendices/config.html).

-
-

Le fichier ansible.cfg

-

- Les modifications de configuration comme ci-dessus se traduisent par l'ajout de directives dans le fichier /etc/ansible/ansible.cfg. Il est possible de surcharger ce fichier en placant un fichier du même nom à la racine des répertoires de travail de vos projets permettant ainsi l'introduction de directives spécifiques à chacun d'entre eux. -

-
-

Configurer SSH

-

Concernant SSH le fonctionnement est le même, on retrouve une section dédiée au sein du fichier .manala.yaml qui nous permettra de jouer sur les directives de configuration SSH:

-
ssh:
-    config: |
-        Host *
-            User debian
-            ForwardAgent yes
-

Et vous voilà en quelques lignes en capacité d'utiliser un environnement Ansible.

-

Configurer GIT

-

Toujours dans le même fichier, la section cette fois-ci sera la suivante:

-
git:
-    config: |
-        # Silence false positive dubious ownership errors
-        #[safe]
-        #directory = *
-

Vous voilà prêt à attaquer Ansible ;)

-
- -
-
-

- Une typo ? - Modifier cet article sur Github -

-
-
-
-
-
- - - - - - diff --git a/pr/137/blog/cours/ansible/ansible-les-inventaires-statiques/index.html b/pr/137/blog/cours/ansible/ansible-les-inventaires-statiques/index.html deleted file mode 100644 index bd40f52c..00000000 --- a/pr/137/blog/cours/ansible/ansible-les-inventaires-statiques/index.html +++ /dev/null @@ -1,751 +0,0 @@ - - - - - - - Ansible - Les inventaires statiques - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - Retour à l'accueil - - - - - - - - -
-
-
- - -
-
-
-
-
    -
  • - #Cours -
  • -
  • - #Ansible -
  • -
  • - #Automation -
  • -
-

Ansible - Les inventaires statiques

-

Première étape vers l'utilisation d'Ansible, les inventaires. Ils sont le point d'entrée vers vos infras et sont donc central au pilotage de vos instances / serveurs.

-
-
    -
  1. - Préambule -
  2. -
  3. - Pré-requis -
  4. -
  5. - Introduction -
  6. -
  7. - Structurer ses inventaires -
  8. -
  9. - La configuration d'un inventaire -
      -
    1. - Clés génériques -
    2. -
    3. - Clés spécifiques à SSH -
    4. -
    5. - Clés spécifiques à l'escalade de privilèges -
    6. -
    -
  10. -
  11. - Enrichir son inventaire -
      -
    1. - Définir des groupes de machines -
    2. -
    3. - La commande « ansible-inventory » -
    4. -
    5. - Ordre de chargement des inventaires -
    6. -
    -
  12. -
  13. - Exercices -
      -
    1. - Exercice 1 -
    2. -
    3. - Exercice 2 -
    4. -
    5. - Exercice 3 -
    6. -
    -
  14. -
  15. - Cibler des groupes de machines avec les « patterns » -
      -
    1. - L'opérateur OR -
    2. -
    3. - L'opérateur AND -
    4. -
    5. - L'opérateur NOT -
    6. -
    7. - Combinaisons multiples -
    8. -
    -
  16. -
  17. - Conclusion -
  18. -
  19. - Aller plus loin avec les sources -
  20. -
- -
-
-

Préambule

-

Ce cours est utilisé dans le cadre de TP au sein de l'IUT Lyon 1. Il est notamment dispensé à des étudiants peu ou pas familiers avec les stratégies d'automatisation et de déploiement des infrastructures. -Bien que très axé débutants il peut également représenté une possibilité de monter « rapidement » pour certaines équipes sur les principes fondamentaux d'Ansible afin de disposer du bagage minimal nécessaire à son utilisation.

-

Il s'agit bien évidemment de supports à vocation pédagogique qui ne sont pas toujours transposables à une activité professionnelle.

-

Pré-requis

-

Disposer d'un environnement de travail Ansible fonctionnel, si ça n'est pas encore le cas vous pouvez jeter un oeil ici !

-

Introduction

-

Afin de pouvoir attaquer nos différentes machines, Ansible a besoin d'un référentiel de celles-ci avec un minimum d'informations les concernants (histoire de savoir comment s'y connecter par exemple ;)).

-

C'est là qu'entre en jeu les inventaires. Il existe deux façons de constituer des inventaires, la première est manuelle, et consiste à écrire ni plus ni moins la liste des machines que l'on souhaites manager on parle dans ce cas d'inventaire statique.

-

La deuxième méthode introduit un principe de « reconnaissance » des machines disponibles, dans ce cas de figure on constituera nos inventaires de manière automatique, on parle dans ce cas d'inventaires dynamiques que nous verrons plus tard.

-

Les inventaires permettent également de structurer / hiérarchiser nos machines en utilisant une notion de groupe. -Ansible propose plusieurs plugins capablent de gérer des inventaires de machines, ils sont consultables à l'aide de la commande:

-
ansible-doc -t inventory -l
-

Qui devrait vous renvoyer la liste suivante:

-
02:48:02 lazy@ansible_env lazy → ansible-doc -t inventory -l
-ansible.builtin.advanced_host_list Parses a 'host list' with ranges                                                                    
-ansible.builtin.auto               Loads and executes an inventory plugin specified in a YAML config                                   
-ansible.builtin.constructed        Uses Jinja2 to construct vars and groups based on existing inventory                                
-ansible.builtin.generator          Uses Jinja2 to construct hosts and groups from patterns                                             
-ansible.builtin.host_list          Parses a 'host list' string                                                                         
-ansible.builtin.ini                Uses an Ansible INI file as inventory source                                                        
-ansible.builtin.script             Executes an inventory script that returns JSON                                                      
-ansible.builtin.toml               Uses a specific TOML file as an inventory source                                                    
-ansible.builtin.yaml               Uses a specific YAML file as an inventory source  
-

Dans notre cas nous nous appuyerons essentiellement sur le plugin yaml.

-

Structurer ses inventaires

-

Un inventaire n'est en fait ni plus ni moins qu'un ou plusieurs fichiers contenant des informations concernant le parc de machines que l'on souhaite piloter.

-

En terme de structure vous rencontrerez énormément de façons de faire, celles-ci étant bien évidemment guider par le besoin métier, on pourra citer comme contraintes par exemple:

-
    -
  • La taille des infrastructures (le nombre d'éléments qui la constitue);
  • -
  • Leur localisation géographique (pays, ville...);
  • -
  • Le besoin d'adresser finement un groupe de machines et pas un autre...
  • -
-

Bref tout est imaginable à ce niveau. -En ce qui nous concerne nous interviendrons sur un parc plutôt modeste puisque pour nos travaux nous utiliserons au maximum 4 machines.

-

Nous allons donc commencer par créer un répertoire qui leur sera dédié appelé inventories nous déplacerons ensuite le fichier hosts.yml que nous avions créé précédemment.

-

Vous devriez donc disposer d'une arborence similaire à la suivante:

-
ansible/
-├── inventories
-│   └── hosts.yml
-└── Makefile
-

La configuration d'un inventaire

-

Pour rappel le contenu de votre fichier hosts.yml doit pour l'heure être le suivant:

-
all:
-  hosts:
-    vm-web-prod-01:
-      ansible_host: XXX.XXX.XXX.XXX
-      ansible_user: debian
-

Clés génériques

-

La structure du fichier nous permet de mettre en évidence deux clés essentielles:

-
    -
  • ansible_host: Le nom résolvable ou l'adresse IP de la machine distante;
  • -
  • ansible_user: L'utilisateur à utiliser pour ouvrir une session sur cette même machine.
  • -
-

Il est toutefois possible d'utiliser d'autres clés de configuration pour enrichir la définition de notre machine comme:

-
    -
  • ansible_port: Permet de spécifier le port de connexion SSH (si différent du port standard, pour rappel le port par défaut est 22)
  • -
-

Clés spécifiques à SSH

-
    -
  • ansible_ssh_pass: Le mot de passe du compte SSH utilisé (on lui préferera une authentification par clés);
  • -
  • ansible_ssh_private_key_file: Le chemin vers la clé à utiliser pour se connecter au compte SSH;
  • -
  • ansible_ssh_extra_args: Permet d'ajouter des options supplémentaires à la ligne de commande SSH utilisée par Ansible.
  • -
-

Clés spécifiques à l'escalade de privilèges

-
    -
  • ansible_become: Permet de forcer l'escalade de privilèges;
  • -
  • ansible_become_method: Permet de spécifier la méthode d'escalade des privilèges;
  • -
  • ansible_become_user: Permet de spécifier l'utilisateur cible de l'escalade de privilèges;
  • -
  • ansible_become_pass: Permet de spécifier le mot de passe de l'utilisateur cible de l'escalade de privilèges (encore une fois, on préfera la méthode par clés SSH).
  • -
-

Enrichir son inventaire

-

À présent que nous avons effectuer un petit tour rapide du propriétaire, nous allons « étoffer » notre inventaire initial en ajoutant une deuxième machine comme ci-dessous:

-
# Fichier hosts.yml
-all:
-  hosts:
-    vm-web-prod-01:
-      ansible_host: XXX.XXX.XXX.XXX
-      ansible_user: debian
-    vm-web-staging-01:
-      ansible_host: XXX.XXX.XXX.XXX
-      ansible_user: debian
-

En ajoutant une machine et en jouant la commande ansible -i inventories/hosts.yml all -m ping nous devrions voir qu'ansible considère bien nos deux machines:

-
- -
- Utilisation du module ping sur deux machines. -
-
-
-

Hôte local

-

- Il est bien évidemment possible d'interroger notre propre machine à l'aide d'Ansible, en modifiant relativement simplement notre fichier d'inventaire. Attention toutefois à ce que vous faites puisque vous pouvez directement impacter la configuration et donc le fonctionnement de votre machine. -

-
-

Exemple de fichier d'inventaire pour piloter une machine locale:

-
all:
-  hosts:
-    localhost:
-      ansible_host: 127.0.0.1
-      ansible_connection: local
-

Définir des groupes de machines

-

Pour l'exemple nous allons créer un fichier groups.yml (toujours dans inventories) contenant:

-
all:
-  children:
-    webservers:
-      hosts:
-        vm-web-prod-01: ~
-        vm-web-staging-01: ~
-

ATTENTION: children est une sous clé de all ;)

-

La commande « ansible-inventory »

-

Ansible propose différentes commandes parfois très spécifiques, l'occasion de tester notre configuration d'inventaire !

-

Testons: ansible-inventory --list -i inventories

-

On notera que cette fois-ci nous donnons le répertoire inventories en paramètre.

-

Cette commande devrait vous afficher la sortie suivante (format json):

-
{
-    "_meta": {
-        "hostvars": {
-            "vm-web-prod-01": {
-                "ansible_host": "192.168.140.XXX",
-                "ansible_user": "debian"
-            },
-            "vm-web-staging-01": {
-                "ansible_host": "192.168.140.XXX",
-                "ansible_user": "debian"
-            }
-        }
-    },
-    "all": {
-        "children": [
-            "ungrouped",
-            "webservers"
-        ]
-    },
-    "webservers": {
-        "hosts": [
-            "vm-web-prod-01",
-            "vm-web-staging-01"
-        ]
-    }
-}
-

ou encore avec l'option --graph en lieu et place de --list qui est plus parlante visuellement ansible-inventory --graph -i inventories:

-
@all:
-  |--@ungrouped:
-  |--@webservers:
-  |  |--vm-web-prod-01
-  |  |--vm-web-staging-01
-

À retenir: -Ansible utilise une arborescence ou figurera toujours:

-
    -
  • Un groupe all: C'est le groupe racine auquel appartiendra toutes vos machines sans exception (On remarquera avec cette information que lorsque nous avons utilisé la commande ansible -i inventories/hosts.yml all -m ping, all indiquait donc le groupe cible).
  • -
  • Un groupe ungrouped: Groupe auquel sera affectée toute machine n'appartenant à aucun groupe (exception faites de all bien évidemment);
  • -
-

Dans notre exemple ci-dessus on voit donc bien que nos deux machines font bien partie du groupe webservers.

-
-

Fichiers d'inventaire multiples

-

- Si l'option -i d'Ansible prend un fichier d'inventaire en paramètre elle peut également prendre un répertoire et dans ce cas Ansible considérera l'ensemble des fichiers présents dans le répertoire. - Ce fonctionnement offre la possibilité avec des infrastructures composées de nombreuses machines de pouvoir les séparer dans plusieurs fichiers en fonction de différents critères. -

-
-

Ordre de chargement des inventaires

-

Vous aurez compris que si l'on aborde le sujet c'est qu'il est d'importance... pour l'illustrer créons un nouveau fichier d'inventaire que l'on nommera misc.yml contenant:

-
all:
-  hosts:
-    vm-web-prod-01:
-      ansible_host: XXX.XXX.XXX.XXX
-      ansible_user: debian
-      ansible_port: 22
-    vm-web-staging-01:
-      ansible_host: XXX.XXX.XXX.YYY
-

Une fois ces modifications faites, rejouez la commande ansible-inventory --list -i inventories vous devriez constater de subtils changements au niveau des informations que vous affiche Ansible.

-
{
-    "_meta": {
-        "hostvars": {
-            "vm-web-prod-01": {
-                "ansible_host": "192.168.140.12",
-                "ansible_port": 22,
-                "ansible_user": "debian"
-            },
-            "vm-web-staging-01": {
-                "ansible_host": "192.168.140.10",
-                "ansible_user": "debian"
-            }
-        }
-    },
-    "all": {
-        "children": [
-            "ungrouped",
-            "webservers"
-        ]
-    },
-    "webservers": {
-        "hosts": [
-            "vm-web-prod-01",
-            "vm-web-staging-01"
-        ]
-    }
-}
-

On constatera ainsi:

-
    -
  • L'ajout de la clé ansible_port sur notre première instance;
  • -
  • La modification de l'adresse IP de la seconde.
  • -
-

Qu'en retenir ?

-

L'utilisation du Yaml comme langage de définition introduit une notion d'arborescence au niveau de vos clés, il faut ainsi voir la définition de votre machine comme un tableau multidimensionnel indexé.

-
array (
-  'vm-web-prod-01' => 
-  array (
-    'ansible_host' => '192.168.140.12',
-    'ansible_port' => 22,
-    'ansible_user' => 'debian',
-  ),
-)
-

On comprendra donc facilement:

-
    -
  • Que l'ajout d'une clé entraine l'ajout d'un élément à notre tableau pour la clé concernée (Dans notre cas l'ajout de la clé ansible_port);
  • -
  • Que la modification de la valeur d'une clé écrase sa valeur précédente (Dans notre cas la modification de l'IP de notre machine).
  • -
-

Conclusion: Lorsque l'on utilise des fichiers d'inventaire multiples il vaut bien prendre en compte leur ordonnancement, la dernière valeur déclarée pour une clé étant celle qui sera retenu dans notre tableau final.

-
-

Groupes de groupes

-

- La hiérarchie de groupe d'un inventaire peut avoir plusieurs niveaux. Il est donc possible d'avoir de l'imbrication de groupes. Attention toutefois à ne pas en abuser afin de ne vous perdre dans des arborescence trop complexes. -

-
-

Complétons pour finir notre inventaire groups.yml afin d'obtenir le contenu suivant:

-
all:
-  children:
-    webservers:
-      hosts:
-        vm-web-prod-01: ~
-        vm-web-staging-01: ~
-    staging:
-      hosts:
-        vm-web-staging-01: ~
-    production:
-      hosts:
-        vm-web-prod-01: ~
-

Exercices

-

Rapide mise en pratique des inventaires.

-

Exercice 1

-

Reprendre les différents fichiers contenu dans notre répertoire inventories et les compiler en un seul et même fichier hosts.yml, les autres fichiers ne sont finalement plus utiles et peuvent être supprimés.

-

Nous compléterons notre inventaire avec deux machines supplémentaires vm-db-prod-01 et vm-db-staging-01 appartenant toutes deux au groupe dbservers (Attention à les affecter également à leurs groupes d'environnements respectifs).

-

Souvenez-vous vous pouvez tester un fichier d'inventaire en particulier en le passant en paramètre de la commande ansible-inventory: ansible-inventory --list -i inventories/hosts.yml.

-

Exercice 2

-

Nous avons vu qu'il existait différent plugin permettant de « lire » un inventaire (si,si au tout début), essayez d'écrire le même inventaire mais à un format différent (format ini par exemple).

-

Exercice 3

-

Revenons à notre fichier hosts.yml séparez son contenu en fonction des environnements que nous avons définis (staging et production)

-

Cibler des groupes de machines avec les « patterns »

-

Notre infrastructure est modeste, mais vous serez parfois amenés à travailler avec des infrastructures d'envergure et serez dans l'obligation de « cibler » certaines machines ou groupes de machines. -Il est ainsi possible d'indiquer explicitement à Ansible quelles sont les machines à considérer pour une action donnée.

-

Certains « patterns » sont très simple et vous devriez en reconnaitre certains:

-

Le « wildcard » * par exemple qui désignera n'importe quelle valeur et qui est utilisable au sein d'une valeur de clé ip ou hostname (192.168.140.* ou encore *.example.com).

-

Ceux que vous rencontrerez le plus souvent: :, :& ou encore :!.

-

L'opérateur OR

-

L'opérateur : signifiera qu'une machine peut-être dans un groupe OU dans un autre, par exemple staging ou production.

-

Essayons toujours avec notre module ping: ansible -i inventories/hosts.yml 'staging:production' -m ping

-
-

Avec la commande ansible-inventory

-

- ansible-inventory -i inventories/hosts.yml --host='webservers:production' -

-
-

L'opérateur AND

-

L'opérateur :& signifiera qu'une machine peut-être dans un groupe ET dans un autre, par exemple webservers et production.

-

ansible -i inventories/hosts.yml 'webservers:&production' -m ping

-

Cette fois-ci vous ne devriez avoir que la machine vm-web-prod-01 qui est solicité par Ansible.

-
-

Avec la commande ansible-inventory

-

- ansible-inventory -i inventories/hosts.yml --host='webservers:&production' -

-
-

L'opérateur NOT

-

L'opérateur :! permettra de cibler une machine qui est dans un groupe mais pas dans un autre par exemple membre du groupe webservers mais non présente dans le groupe production.

-

ansible -i inventories/hosts.yml 'webservers:!production' -m ping

-

Vous devriez ne soliciter cette fois que vm-web-staging-01.

-
-

Avec la commande ansible-inventory

-

- ansible-inventory -i inventories/hosts.yml --host='webservers:!production' -

-
-

Combinaisons multiples

-

Il est bien évidemment possible de combiner les opérateurs prenez toutefois garde aux expressions trop complexes qui gêneront à la compréhension et pourront être source d'erreur !

-

On peut donc imaginer des choses comme cibler les machines du groupe webservers OU staging mais qui ne sont pas dans production (On est d'accord, ça n'a aucune sens c'est pour l'exemple ;)).

-

ansible -i inventories/hosts.yml 'webservers:staging:!production' -m ping

-

Il est également possible de mixer nom de groupe et nom de machine: ansible -i inventories/hosts.yml 'webservers:staging:!vm-web-staging-01' -m ping

-
-

Avec la commande ansible-inventory

-

- ansible-inventory -i inventories/hosts.yml --host='webservers:staging:!vm-web-staging-01' -

-
-

Conclusion

-

Nous aurons donc vu que les inventaires bien qu'à priori relativement simples, peuvent amener une forme de complexité sur des infrastructures volumineuses, leur organisation peut donc vite devenir stratégique notamment dans l'optique de faciliter la maintenance du parc piloté par Ansible.

-

Dans la prochaine étape nous aborderons une nouvelle notion d'Ansible, les playbooks qui nous permettront d'écrire nos première tâches !

-

Aller plus loin avec les sources

- -
- -
-
-

- Une typo ? - Modifier cet article sur Github -

-
-
-
-
-
- - - - - - diff --git a/pr/137/blog/cours/ansible/ansible-les-playbooks/index.html b/pr/137/blog/cours/ansible/ansible-les-playbooks/index.html deleted file mode 100644 index 6b698f56..00000000 --- a/pr/137/blog/cours/ansible/ansible-les-playbooks/index.html +++ /dev/null @@ -1,945 +0,0 @@ - - - - - - - Ansible - Les playbooks - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - Retour à l'accueil - - - - - - - - -
-
-
- - -
-
-
-
-
    -
  • - #Cours -
  • -
  • - #Ansible -
  • -
  • - #Automation -
  • -
  • - #Playbook -
  • -
-

Ansible - Les playbooks

-

Découverte des playbooks, élément essentiel d'Ansible qui va nous permettre d'organiser et structurer nos tâches !

-
-
    -
  1. - Préambule -
  2. -
  3. - Pré-requis -
  4. -
  5. - Introduction -
  6. -
  7. - Les playbooks -
      -
    1. - Structure générale d'un playbook -
    2. -
    3. - La commande ansible-playbook -
    4. -
    5. - Les notions de « play » et de « tasks » -
    6. -
    7. - Visualiser les machines ciblées -
    8. -
    -
  8. -
  9. - Les tâches -
      -
    1. - L'escalade de privilèges -
    2. -
    3. - Organiser ses tâches -
    4. -
    5. - Taguer ses tâches -
    6. -
    -
  10. -
  11. - Point de progression -
  12. -
  13. - Aller plus loin avec les sources -
  14. -
- -
-
-

Préambule

-

Ce cours est utilisé dans le cadre de TP au sein de l'IUT Lyon 1. Il est notamment dispensé à des étudiants peu ou pas familiers avec les stratégies d'automatisation et de déploiement des infrastructures. -Bien que très axé débutants il peut également représenté une possibilité de monter « rapidement » pour certaines équipes sur les principes fondamentaux d'Ansible afin de disposer du bagage minimal nécessaire à son utilisation.

-

Il s'agit bien évidemment de supports à vocation pédagogique qui ne sont pas toujours transposables à une activité professionnelle.

-

Pré-requis

-

Disposer d'un environnement de travail Ansible fonctionnel, si ça n'est pas encore le cas vous pouvez jeter un oeil ici !

-

Introduction

-

Un playbook est un élément central pour Ansible puisque c'est dans ce fichier de configuration (écrit en YAML toujours), que l'on va organiser et indiquer quelles actions nous souhaitons déclencher.

-

Les playbooks décrivent des « tâches » à exécuter sur des groupes de machines identifiés dans les inventaires, ils sont écrits à l'aide du format YAML.

-
-

La syntaxe Yaml

-

- Yaml est un langage ou plutôt un format, qui permet de sérialiser des données afin qu'elles restent lisiblent pour nous. Les données sont organisées sous la forme de paire clé/valeur dans des listes ordonnées, il est de plus en plus utilisé notamment pour les fichiers de configuration. -

-
-

Les playbooks

-

Afin de rentrer dans le vif du sujet nous allons écrire un playbook relativement simple utilisant un module que nous avons vu précédemment, le module ping. Nous avons vu que les modules Ansible peuvent être utilisés en ligne de commande pour exécuter une tâche ponctuelle mais rappelons que le but d'Ansible est en premier lieu, d'automatiser les choses et donc de les rendre réutilisables !

-

Structure générale d'un playbook

-

Créons un nouveau fichier que nous appellerons example.yml dans notre répertoire de travail (pour rappel de mon côté workspace/ansible) contenant:

-
---
-- hosts: webservers
-
-  tasks:
-    - name: Check if host is alive # Description of the task
-      ansible.builtin.ping: ~
-

La structure YAML permet une compréhension relativement aisée du contenu, on identifiera ainsi:

-
    -
  • Une clé principale appelée hosts permettant de définir les « hôtes », les machines concernées par les tâches qui vont suivre (identifiées par leur groupe d'appartenance);
  • -
  • Une clé tasks permettant de définir les différentes « tâches » ou actions que nous souhaitons déclencher sur nos machines.
  • -
-

Un bloc ainsi défini est appelé un « play ».

-
-

---

-

- Vous l'aurez noté, les playbooks que nous rédigeons commencent tous avec trois tirets ---, ceux-ci marquent en effet en syntaxe Yaml, le début du document, ils sont donc indispensables à l'interprétation du fichier. - Ils permettent entre autres d'utiliser des directives en entêtes de fichier ou de disposer de plusieurs flux yaml dans un même fichier (le début de chaque flux étant indiqué par ces trois tirets). -

-
-

Rappelez-vous nous pouvons traduire les instructions suivantes sous forme de tableaux:

-
array (
-    'hosts' => 'webservers',
-    'tasks' => 
-        array (
-            0 => 
-                array (
-                    'name' => 'Check if host is alive',
-                    'ansible.builtin.ping' => NULL,
-                ),
-        ),
-  )
-

On constate encore plus aisément sous cette forme que la clé tasks permet de définir un nombre indéfini de « tâches » à exécuter, celles-ci ayant toujours au moins pour structure:

-
    -
  • Un nom (clé name);
  • -
  • L'appel à un module ansible (ici nous faisons appel au module ping utilisé précédemment) chaque module acceptant ou non des paramètres.
  • -
-
-

Les modules Ansible

-

- Un module pour Ansible est un composant logiciel qui encapsule une tâche spécifique. - Pour résumer, il s'agit d'un bloc de code qui est exécuté par Ansible pour accomplir une action donnée sur un système cible. On trouve notamment des modules capables de gérer des fichiers, d'exécuter des commandes ou encore de gérer des services. - Ils peuvent être écrits dans divers langages de programmation, mais la plupart sont écrits en Python. Ansible vient avec une large gamme de modules intégrés pour gérer différents aspects des systèmes, des applications et des infrastructures. -

-
-

La commande ansible-playbook

-

Nous avons déjà vu les commandes ansible et ansible-inventory au tour de ansible-playbook

-

En exécutant cette commande ansible-playbook example.yml -i inventories vous devriez obtenir la sortie suivante ou équivalente (si tout se passe bien).

-
- -
- Utilisation du module ping dans un playbook. -
-
-

Nous venons donc de faire appel au module ping mais à l'intérieur d'un playbook. Ce que nous avons en retour et une sortie type d'Ansible sur laquelle on peut remarquer quelques informations toujours intéressantes (tout en bas) avec notamment:

-
    -
  • ok: Le nombre de tâches (tasks) qui se sont correctement exécutées;
  • -
  • changed: Le nombre de changements opérés sur nos machines cibles;
  • -
  • unreachable: Le nombre de machines injoignables (par soucis de réseau par exemple);
  • -
  • failed: Le nombre de tâches en erreur;
  • -
  • skipped: Le nombre de tâches qui ont été ignorées (par exemple si une tâches ne remplie pas certaines conditions prédéfinies).
  • -
-

Les notions de « play » et de « tasks »

-

Il est bon de noté également qu'un playbook peut contenir plusieurs « plays » nous pouvons donc avoir des tâches attribuées à différents groupes cible comme ceci:

-
---
-- hosts: webservers
-
-  tasks:
-    - name: Check if host is alive # Description of the task
-      ansible.builtin.ping:
-
-- hosts: dbservers
-
-  tasks:
-    - name: Get stats of a file
-      ansible.builtin.stat:
-        path: /etc/hosts
-
- -
- Playbook contenant deux « plays ». -
-
-

Nous avons dans ce cas de figure deux groupes chacun concerné par une « tâche »:

-
    -
  • Le groupe webservers (contenant donc 2 machines) sur lequel nous exécutons le module ping;
  • -
  • Le groupe dbservers (contenant également 2 machines) sur lequel nous exécutons le module stat.
  • -
-
-

Le module stat

-

- Le module stat permet d'exécuter la commande système stat sur un fichier du serveur cible. Il prends différents paramètres que l'on peut retrouver sur la documentation officielle dont le paramètre path qui lui est obligatoire. -

-
-

Rappelez-vous également qu'un « play » peut exécuter plusieurs tâches les unes à la suite des autres, exemple:

-
---
-- hosts: webservers
-
-  tasks:
-    - name: Check if host is alive # Description of the task
-      ansible.builtin.ping:
-    - name: Execute a simple command
-      ansible.builtin.shell: echo "Ansible was here !" > ansible.txt
-
-- hosts: dbservers
-
-  tasks:
-    - name: Get stats of a file
-      ansible.builtin.stat:
-        path: /etc/hosts
-

Vous devriez, en jouant votre playbook (ansible-playbook example.yml -i inventories) obtenir une sortie équivalente à:

-
- -
- Playbook contenant deux « plays » et plusieurs « tasks ». -
-
-

À la différence de nos premières tâches, l'utilisation du module shell a entrainé une modification de l'état des deux serveurs membres du groupe webservers qu'Ansible nous confirme à l'aide de son retour changed.

-

Visualiser les machines ciblées

-

Lorsque l'on dispose de playbook assez longs, il peut-être intéressant de vérifier la liste des hôtes concernés, c'est faisable à l'aide de la commande ansible-playbook example.yml -i inventories --list-host.

-
- -
- Lister les hôtes concernés par un playbook. -
-
-

Il est également possible de lister les tâches avec l'option --list-tasks et bien évidemment de combiner ces deux options ansible-playbook example.yml -i inventories --list-tasks --list-hosts.

-
- -
- Lister les hôtes et les tâches concernés par un playbook. -
-
-

Vous aurez également remarqué la présence d'une information [TAGS] très utile dont nous parlerons par la suite ;)

-

Les tâches

-

Nous avons vu les principes fondamentaux du fonctionnement des « tasks » avec Ansible, mais celles-ci peuvent être plus complexes que celles que nous avons pu voir jusqu'à présent. -Afin de s'en rendre compte nous allons déployer un serveur web Nginx sur nos instances web.

-

Nous allons ajouter une tâche dédiée dans un nouveau playbook que nous appelerons webservers.yml et qui contiendra les instructions suivantes:

-
---
-- hosts: webservers
-
-  tasks:
-    - name: Install Nginx web server
-      ansible.builtin.apt:
-        name: nginx
-        update_cache: yes
-        state: present
-

En l'exécutant (ansible-playbook webservers.yml -i inventories) vous devriez obtenir la sortie ci-dessous, Ansible nous indiquant qu'il a effectué une modification sur nos machines distantes:

-
- -
- Installation d'Nginx avec Ansible. -
-
-

On notera l'utilisation du module apt (attention, utilisable bien évidemment uniquement avec les distributions basées sur Debian) accompagné de plusieurs paramètres;

-
    -
  • name: le nom du paquet à installer
  • -
  • update_cache: Indique qu'il faut effectuer une mise à jour de l'index des paquets avant l'installation.
  • -
  • state: « L'état » dans lequel nous souhaitons avoir le paquet installé (present, absent, latest...)
  • -
-

Vous devriez également pouvoir interroger votre serveur web en utilisant l'IP de votre machine http://XXX.XXX.XXX.XXX.

-

L'escalade de privilèges

-

Avec cette tâche d'installation nous touchons une autre problématique qui est celle de la gestion des droits et notamment la modification de manière globale de l'état d'un système, ce qui relève normalement de la compétence du compte root, super administrateur des systèmes à base UNIX.

-

Si nous n'avons pas rencontré de problème jusqu'à présent c'est parce que nous utilisons un lazy Ansible pré-configuré avec certaines directives dans le fichier de configuration d'Ansible dont nous reparlerons un peu plus tard.

-

Il est toutefois possible le cas échéant, d'indiquer au niveau d'une tâche que celle-ci réclame des droits d'administrateur et qu'il faut déclencher une escalade de privilèges. Cela se fait en utilisant des clés réservées comme ci-dessous (en reprenant la tâche d'installation Nginx):

-
---
-- hosts: webservers
-  remote_user: debian
-
-  tasks:
-    - name: Ensure Nginx service is started
-      service:
-        name: nginx
-        state: started
-      become: yes
-      become_user: root # This is the default value, so it's not mandatory here
-

On remarquera les clés:

-
    -
  • remote_user: Précisant l'utilisateur avec lequel se connecter à la machine;
  • -
  • become: Indiquant s'il faut déclencher une escalade de privilèges;
  • -
  • become_user: Indiquant le nom du compte utilisateur vers lequel faire l'escalade (Par défaut root).
  • -
-

Le mécanisme utilisé par Ansible pour faire son escalade est par défaut la commande sudo, il est possible de modifier ce comportement avec la clé become_method.

-

Organiser ses tâches

-

Nous avons vu que chaque « play » peut contenir plusieurs tâches, il faut savoir que celles-ci sont exécutées dans l'ordre dans lequel elles apparaissent et sur l'ensemble des machines qui correspondent à leur domaine d'application (un groupe ou une machine). -Le but de chacune de ces tâches est d'exécuter un module (nous en avons déjà vu plusieurs, ping, stat, apt, shell), chaque module se devant d'être idempotent garantissant ainsi que peu importe le nombre de fois ou il sera exécuté il produira toujours le même résultat si ses paramètres d'entrée demeurent inchangés.

-

Il est important que chaque tâche dispose d'une clé name fournissant des instructions claires sur son rôle (pensez à la maintenance).

-

Ceci-dit il est bon de savoir que nos tâches peuvent être organisées de différentes manières et notamment en utilisant les différentes sections que l'on peut trouver dans un playbook:

-
    -
  • pre_tasks: Contient les tâches à exécuter en premier;
  • -
  • roles: Indiquant une liste de rôles à utiliser dans notre « play » (Nous verrons cette notion plus tard);
  • -
  • post_tasks: Contenant les tâches à exécuter en dernier;
  • -
  • handlers: Contenant des tâches déclenchées à la fin de chaque bloc ou section de tâches en fonction de certains évènements.
  • -
-

La section pre_tasks

-

Elle correspond à une exécution conditionnelle d'un bloc de code AVANT de lancer le « play », elle peut être chargée soit de vérifier des pré-requis soit de valider un état.

-

Souvenez-vous au moment d'installer notre paquet Nginx nous avions ajouté le paramètre update_cache afin de mettre à jour l'index des paquets. Mais nous avons une autre option qui se présente, plutôt que de faire figurer cette option sur chaque appel au module apt que nous aurons dans notre playbook, nous pouvons gérer la mise à jour de l'index des paquets avant de le jouer.

-

Modifions notre playbook webservers.yml de la façon suivante:

-
---
-- hosts: webservers
-
-  pre_tasks:
-    - name: Updating APT cache index
-      ansible.builtin.apt:
-        update_cache: yes
-
-  tasks:
-    - name: Install Nginx web server
-      ansible.builtin.apt:
-        name: nginx
-        state: present
-

Et exécutons le, nous devrions obtenir la sortie suivante:

-
- -
- Exécution d'une pre-task. -
-
-

Où l'on constate qu'effectivement la première tâche exécutée est notre mise à jour d'index de paquets (nous parlerons plus en détails de la tâche « Gathering facts » lorsque nous aborderons les variables)

-

Nous ne détaillerons pas la section post_tasks puisqu'elle fonctionne exactement de la même manière, toutefois nous pouvons enrichir notre playbook avec de nouvelles instructions concernant nos instances de bases de données.

-

Pour cela nous avons plusieurs options:

-
    -
  • Soit tout concentrer dans notre playbook webservers.yml, mais l'on pourra faire remarquer à raison, que le nom du fichier n'est plus en adéquation avec son contenu;
  • -
  • Soit créer un nouveau playbook dédié aux instances de bases de données dbservers.yml.
  • -
-

Nous nous orienterons pour l'exemple, sur cette seconde option, nous créerons donc un fichier dbservers.yml à la racine de notre répertoire de travail dont le contenu ressemblera beaucoup à celui de webservers.yml.

-
- hosts: dbservers
-
-  pre_tasks:
-    - name: Updating APT cache index
-      ansible.builtin.apt:
-        update_cache: yes
-
-  tasks:
-    # MARIADB
-    - name: Install MariaDB server
-      ansible.builtin.apt:
-        name: mariadb-server
-        state: present
-

Notre nouveau playbook est bien evidemment « jouable » à l'aide de la commande ansible-playbook dbservers.yml -i inventories.

-

Les handlers

-

Bien, reprenons notre exemple d'installation d'Nginx, nous avons un paquet « tout neuf » auquel nous n'avons apporté aucune configuration pour l'instant. -Imaginons à présent que nous souhaitions ajouter un fichier de configuration spécifique pour notre serveur web, pour notre exemple nous mettrons en place un fichier qui nous renvoie simplement un « état » de notre serveur Nginx.

-

En premier lieu nous allons créer un nouveau répertoire appelé files à la racine de notre espace de travail lui même contenant un répertoire nginx (histoire d'organiser un minimum les choses) dans lequel nous ajouterons le fichier status.conf contenant:

-
server {
-    listen *:8080;
-    root /usr/share/nginx/html;
-    access_log off;
-    location / {
-        return 404;
-    }
-    location = /nginx/status {
-        stub_status on;
-    }
-}
-

Modifions à présent notre fichier webservers.yml de la façon suivante:

-
---
-- hosts: webservers
-
-  pre_tasks:
-    - name: Updating APT cache index
-      ansible.builtin.apt:
-        update_cache: yes
-
-  tasks:
-    - name: Install Nginx web server
-      ansible.builtin.apt:
-        name: nginx
-        state: present
-    - name: Nginx status configuration file
-      ansible.builtin.copy:
-        src: nginx/status.conf
-        dest: /etc/nginx/conf.d/status.conf
-

Si tout s'est déroulé correctement vous devriez avoir un fichier status.conf nouvellement créé par Ansible sur vos machines cibles dans le répertoire /etc/nginx/conf.d/. -Pour terminer nous pouvons vérifier que notre configuration fonctionne bien en interrogeant l'adresse: http://XXX.XXX.XXX.XXX:8080/nginx/status... ou pas !

-
-

Le module copy

-

- Le module copy permet de gérer les fichiers de nos machines cibles à savoir leur création, leur suppression mais également leur type (n'oubliez pas, sur des systèmes UNIX tout est fichier !). Il permet notamment de transférer des fichiers de configuration vers des machines. Par défaut à son utilisation Ansible recherche le fichier passé en paramètre dans le répertoire files de l'espace de travail. -

-
-

En l'état actuel vous devriez avoir pour toute réponse une page blanche, en effet bien que notre fichier de configuration ait été déposé sur le serveur, Nginx ne le prend pas encore en compte car le service n'a pas été redémarré. -C'est là qu'entre en jeu les handlers !

-

Les handlers sont des tâches un peu particulières qui ne se déclenchent que lorsqu'elles sont notifiées. -Complétons notre playbook de manière à obtenir la configuration suivante:

-
---
-- hosts: webservers
-
-  pre_tasks:
-    - name: Updating APT cache index
-      ansible.builtin.apt:
-        update_cache: yes
-
-  tasks:
-    - name: Install Nginx web server
-      ansible.builtin.apt:
-        name: nginx
-        state: present
-    - name: Nginx status configuration file
-      ansible.builtin.copy:
-        src: nginx/status.conf
-        dest: /etc/nginx/conf.d/status.conf
-      notify:
-          - restart_nginx
-
-  handlers:
-    - name: restart_nginx
-      ansible.builtin.service:
-        name: nginx
-        state: restarted
-

En rejouant notre playbook ansible-playbook webservers.yml -i inventories nous constons que cela n'a rien changé ! Effectivement, comme dit précédemment un handler réagit à un évènement, dans notre cas à la modification du fichier status.conf. Comme nous ne l'avons pas modifié le handler n'a pas été notifié.

-

En introduisant une modification dans notre fichier (par exemple en modifiant la directive access_log pour la passer à off) nous devrions enfin pouvoir accéder à notre page de statut à l'adresse: http://XXX.XXX.XXX.XXX:8080/nginx/status.

-

Vous devriez également pouvoir constater l'exécution du handler sur la sortie Ansible:

-
- -
- Exécution d'un handler. -
-
-
-

La directive « Notify »

-

- Les actions de type notify sont déclenchées à la fin de chaque bloc de tâches d'un « play » donné, elles ne le sont bien évidemment qu'une seule fois même si elles sont appelée plusieurs fois. -

-
-

Il est également possible d'ajouter à un handler une clé listen comme ci-dessous, celle-ci indiquant au handler « d'écouter » un thème spécifique permettant de regrouper plusieurs « handlers ».

-
handlers:
-    - name: restart_nginx
-      ansible.builtin.service:
-        name: nginx
-        state: restarted
-    listen: restart_http_stack
-
-    - name: restart haproxy
-      ansible.builtin.service:
-        name: haproxy
-        state: restarted
-    listen: restart_http_stack
-

Les instructions import... et include...

-

Vous l'aurez compris, si l'on conserve l'ensemble de nos instructions dans un seul playbook celui-ci peut rapidemnent devenir volumineux et difficile à maintenir. Pour autant séparer nos instructions dans des playbooks dédiés conduit invariablement à dupliquer certains blocs d'instructions ce qui n'est pas non plus l'idéal, fort heureusement il est possible de résoudre ces problématiques de manière élégante en utilisant différentes instructions préfixées import_ et include_.

-

Avant de réorganiser nos travaux il est important de bien comprendre la différence entre les deux:

-
    -
  • Les instructions de type import_* sont « pré-traitées » au moment où les playbooks sont parcourus et donc avant leur exécution;
  • -
  • Les instructions de type include_* sont traitées au moment où elles sont rencontrées durant l'exécution.
  • -
-

Réorganisons à présent nos playbooks en tenant compte de cette nouvelle information:

-
    -
  • L'instruction de mise à jour de notre index APT dans notre pre_tasks peut-être considérée comme une tâche commune à l'ensemble des machines;
  • -
  • Il est intéressant de pouvoir « jouer » de manière indépendante les deux playbooks webservers.yml et dbservers.yml mais il peut aussi être « sympa » de pouvoir les appeler de manière groupée;
  • -
  • Les handlers pourront être potentiellement notifiés de manière transverse par de futures tâches de configuration.
  • -
-

Nous déplacerons donc nos tâches « communes » dans un playbook dédié common.yml qui contiendra donc:

-
- name: Updating APT cache index
-  ansible.builtin.apt:
-    update_cache: yes
-

Nous supprimerons bien évidemment des playbooks webservers.yml et dbservers.yml les instructions correspondantes.

-

Nos handlers eux, finiront dans un nouveau playbook handlers.yml comme ci-dessous:

-
- name: restart_nginx
-  ansible.builtin.service:
-    name: nginx
-    state: restarted
-- name: restart_mariadb
-  ansible.builtin.service:
-    name: mariadb
-    state: restarted
-

Quant à nos deux playbooks principaux nous les modifierons légèrement en y ajoutant:

-
    -
  • l'inclusion des pre_tasks (common.yml);
  • -
  • l'inclusion des handlers (handlers.yml);
  • -
  • un playbook « global » afin de les déclencher.
  • -
-

Respectivement:

-
---
-- hosts: webservers
-
-  pre_tasks:
-    - ansible.builtin.import_tasks: common.yml
-
-  tasks:
-    # NGINX
-    - name: Install Nginx web server
-      ansible.builtin.apt:
-        name: nginx
-        state: present
-
-    - name: Nginx status configuration file
-      ansible.builtin.copy:
-        src: nginx/status.conf
-        dest: /etc/nginx/conf.d/status.conf
-      notify:
-          - restart_nginx
-
-  handlers:
-    - ansible.builtin.include_tasks: handlers.yml
-
---
-- hosts: dbservers
-
-  pre_tasks:
-    - ansible.builtin.import_tasks: common.yml
-
-  tasks:
-    # NGINX
-    - name: Install MariaDB server
-      ansible.builtin.apt:
-        name: mariadb-server
-        state: present
-
-  handlers:
-    - ansible.builtin.include_tasks: handlers.yml
-

Nous créerons enfin pour terminer un dernier playbook main.yml contenant:

-
- ansible.builtin.import_playbook: webservers.yml
-- ansible.builtin.import_playbook: dbservers.yml
-

Nous pouvons à présent jouer l'ensemble avec la commande: ansible-playbook main.yml -i inventories !

-

Tout semble fonctionner ! Mais est-ce vraiment le cas ? -Modifiez à nouveau le fichier de configuration status.conf destiné à Nginx et relancer le provisionning.

-

Vous devriez vous retrouver avec l'erreur ERROR! The requested handler 'restart_nginx' was not found in either the main handlers list nor in the listening handlers list !

-

En effet nous avons utilisé une directive de type include pour inclure notre fichier de handlers, or nous avons vu que ces instructions ne sont pas « pré-traitées ». Ansible lorsqu'il parcours notre playbook rencontre donc l'instruction:

-
notify:
-          - restart_nginx
-

AVANT que notre fichier handlers.yml et donc qu'Ansible ait connaissance de l'existance de ce handler !

-

Pour corriger ce comportement il faudra donc dans notre cas utiliser la directive:

-
handlers:
-    - ansible.builtin.inport_tasks: handlers.yml
-

Taguer ses tâches

-

Nous venons de voir la possibilité de « diviser pour réorganiser » nos tâches, mais il existe également la possibilité de « taguer » nos tâches de manière à les déclencher de manière ciblée, ouvrant également la possibilité de les exécuter de façons transverses si celles-ci appartiennent à plusieurs playbooks différents.

-

Nous verrons qu'ils sont très utiles voir indispensables dès lorsque nous aurons abordé la notion de roles.

-

Reprenons notre exemple précédent où nous disposons de deux playbooks principaux distincts webservers.yml et dbservers.yml nous les modifierons de façon à « taguer » nos différentes tâches comme ci-après:

-

Webservers:

-
...
-  tasks:
-    # NGINX
-    - name: Install Nginx web server
-      ansible.builtin.apt:
-        name: nginx
-        state: present
-      tags:
-        - nginx
-        - installation
-
-    - name: Nginx status configuration file
-      ansible.builtin.copy:
-        src: nginx/status.conf
-        dest: /etc/nginx/conf.d/status.conf
-      notify:
-          - restart_nginx
-      tags:
-        - nginx
-        - configuration
-...
-

Dbservers:

-
...
-  tasks:
-    # NGINX
-    - name: Install MariaDB server
-      ansible.builtin.apt:
-        name: mariadb-server
-        state: present
-      tags:
-        - db
-...
-

Utilisation

-

Maintenant que nos tags sont définis nous sommes en capacité de les exploiter en ajoutant l'option --tags à notre exécution ce qui nous donnera par exemple:

-
    -
  • ansible-playbook main.yml -i inventories --tags "nginx,db".
  • -
-

Il est également possible d'ignorer certains tags avec l'option --skip-tags:

-
    -
  • ansible-playbook main.yml -i inventories --skip-tags db.
  • -
-

Les tags never and always

-

Ansible dans son fonctionnement, prévoit des tags réservés:

-
    -
  • always, permet de systématiquement jouer une tâche sauf lorsqu'elle est explicitement exclue à l'aide de l'option --skip-tags;
  • -
  • never à l'inverse, permettra de ne jamais jouer les tâches concernées à moins de le spécifier explicitement à l'aide le l'option --tags.
  • -
-
-

La tâche « Gathering facts »

-

- La tâche « Gathering fact » porte le tag « always » par défaut qui lui permet d'être jouée systématiquement, il est donc possible d'ignorer cette tâche via les options vues ci-dessus. Toutefois son absence peut entrainer un mauvais fonctionnement (voir une erreur) des différentes tâches devant être exécutées à sa suite. -

-
-

Point de progression

-

Nous savons à présent gérer:

- -

Nous pouvons toutefois encore apporter un peu de dynamisme à ces premières notions par l'introduction de variables dont nous parlerons dans la suite.

-

Aller plus loin avec les sources

- -
- -
-
-

- Une typo ? - Modifier cet article sur Github -

-
-
-
-
-
- - - - - - diff --git a/pr/137/blog/cours/ansible/ansible-les-roles/index.html b/pr/137/blog/cours/ansible/ansible-les-roles/index.html deleted file mode 100644 index 4e71d4ac..00000000 --- a/pr/137/blog/cours/ansible/ansible-les-roles/index.html +++ /dev/null @@ -1,637 +0,0 @@ - - - - - - - Ansible - Roles et collections, savoir factoriser. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - Retour à l'accueil - - - - - - - - -
-
-
- - -
-
-
-
-
    -
  • - #Cours -
  • -
  • - #Ansible -
  • -
  • - #Automation -
  • -
  • - #Playbook -
  • -
  • - #Collection -
  • -
  • - #Role -
  • -
-

Ansible - Roles et collections, savoir factoriser.

-

Automatiser c'est bien, faire en sorte que cela soit réutilisable simplement c'est mieux. Mise en oeuvre des collections et roles avec Ansible.

-
-
    -
  1. - Préambule -
  2. -
  3. - Pré-requis -
  4. -
  5. - Introduction -
  6. -
  7. - Les rôles -
      -
    1. - Créer son premier rôle -
    2. -
    3. - Organisation et structures de contrôle avancées -
    4. -
    5. - Déclarer les variables de rôles -
    6. -
    -
  8. -
  9. - Point de progression -
  10. -
  11. - Aller plus loin avec les sources -
  12. -
- -
-
-

Préambule

-

Ce cours est utilisé dans le cadre de TP au sein de l'IUT Lyon 1. Il est notamment dispensé à des étudiants peu ou pas familiers avec les stratégies d'automatisation et de déploiement des infrastructures. -Bien que très axé débutants il peut également représenté une possibilité de monter « rapidement » pour certaines équipes sur les principes fondamentaux d'Ansible afin de disposer du bagage minimal nécessaire à son utilisation.

-

Il s'agit bien évidemment de supports à vocation pédagogique qui ne sont pas toujours transposables à une activité professionnelle.

-

Pré-requis

-

Avoir suivi/lu les cours concernant:

- -

Introduction

-

Précédemment nous avons vu l'utilisation des concepts fondamentaux d'Ansible, toutefois au fur et à mesure de l'évolution de ce TD vous avez du vous poser plusieurs questions et notamment celles de la maintenance et de la réutilisation de tout ce qu'on à fait. -Et vous avez bien raison, car jusqu'à présent nous n'avons rien produit de bien réutilisable et de correctement maintenable.

-

En effet nos playbooks contiennent de nombreuses instructions qui bien que liées n'ont pas forcément pour finalité de cohabiter et l'ensemble de ces instructions paraissent en l'état, bien difficilement transposables à d'autres projets/contextes.

-

Il est donc plus que temps d'intégrer le concept de rôle et plus largement de collection.

-

Les rôles

-

Au sens Ansible un rôle est assimilable à un regroupement de tâches qui (normalement) s'orientent vers un même objectif. « Normalement » car encore une fois c'est vous qui êtes maître de ce que vous embarquer dans un rôle mais d'expérience je ne saurai que vous conseiller de bien définir le périmètre d'application de chacun de vos rôles et de vous y tenir ;) !

-

On peut ainsi s'imaginer un rôle dédié à la gestion de notre serveur web nginx ou encore à un serveur de bases de données PostgreSQL.

-

La structure d'un rôle est très similaire à l'organisation que l'on a pu voir jusqu'à présent, on retrouvera ainsi une arborescence type:

-
roles/
-    nginx/                # this hierarchy represents a "role"
-        tasks/            #
-            main.yml      #  <-- tasks file can include smaller files if warranted
-        handlers/         #
-            main.yml      #  <-- handlers file
-        templates/        #  <-- files for use with the template resource
-            app.conf.j2   #  <------- templates end in .j2
-        files/            #
-            bar.conf      #  <-- files for use with the copy resource
-            foo.sh        #  <-- script files for use with the script resource
-        vars/             #
-            main.yml      #  <-- variables associated with this role
-        defaults/         #
-            main.yml      #  <-- default lower priority variables for this role
-        meta/             #
-            main.yml      #  <-- role dependencies
-        library/          # roles can also include custom modules
-        module_utils/     # roles can also include custom module_utils
-        lookup_plugins/   # or other types of plugins, like lookup in this case
-

Ansible prévoit un répertoire dédié aux rôles dans lequel nous retrouverons un répertoire par rôle, chacun de ces rôles obéissant à la structure définie ci-dessus.

-
-

Structure d'un rôle

-

- Un rôle doit contenir au moins un des répertoires de l'arborescence ci-dessus. À l'inverse si certains répertoires ne sont pas nécessaires à son fonctionnement, ils peuvent être omis. - Par défaut Ansible « recherchera » les instructions d'un répertoire dans un fichier main.yml. -

-
-

Ansible par défaut « recherchera » des rôles à différents endroits:

-
    -
  • Dans l'emplacement destiné aux collections (dont nous parlerons juste après);
  • -
  • Dans un répertoire roles relativement à la position de notre fichier de playbook;
  • -
  • À partir de la clé role_path configurable notamment dans le fichier de configuration ansible.cfg;
  • -
  • Et pour finir dans le répertoire ou se trouve le playbook.
  • -
-

Il est possible de faire cohabiter tout ce beau monde et d'avoir par exemple des rôles communautaires tout en ayant des rôles propres à votre projet / société mais qui n'ont pas forcément vocation à être partagés !

-

Créer son premier rôle

-

Reprenons nos travaux précédent. Nous avions au final plusieurs tâches, chacune chargée de faire des choses bien différentes (Nginx, PHP, motd...) -Notre objectif pour cette fois sera de refactoriser ces sections de manière à les transformer en rôles.

-

Un rôle Motd

-

Rien de bien compliqué pour commencer, histoire de ne pas vous perdre tout de suite ;) -Créons notre arborescence dans un répertoire roles à la racine de notre répertoire de travail en y ajoutant un répertoire motd. -Dans ce dernier nous allons ensuite créer les répertoires tasks et defaults.

-

À l'intérieur de chacun de ces répertoires nouvellement créés nous ajouterons un fichier main.yml, pour l'instant vide.

-

Nous créerons également une arborescence templates/scripts dans notre role et nous y « rapatrierons » nos fichiers de template production.j2 et staging.j2 du répertoire templates à la racine de notre projet vers le répertoire templates/scripts de notre rôle.

-

Vous devriez à ce stade disposer d'une arborescence pour le rôle motd similaire à la suivante:

-
motd
-├── defaults
-│   └── main.yml
-├── tasks
-│   ├── main.yml
-│   └── scripts.yml
-└── templates
-    └── scripts
-        ├── production.j2
-        └── staging.j2
-

À présent nous allons « redescendre » la tâche motd de notre playbook webservers.yml vers le fichier tasks/main.yml de notre rôle.

-

Il nous faudra adapter notre tâche à notre nouvelle arborescence:

-
- name: MOTD > Installing template
-  ansible.builtin.template:
-    src: "scripts/{{stage}}.j2"
-    dest: "/etc/update-motd.d/10-welcome"
-    owner: root
-    group: root
-    mode: "0755"
-

À ce stade vous avez un premier rôle très simple et fonctionnel, il nous reste à assurer son déclenchement au niveau de notre playbook webservers.yml que nous allons modifier de manière à intégrer l'exécution de notre rôle:

-
- name: "Configuring webservers"
-  hosts: webservers
-
-  roles:
-    - role: motd
-
-  pre_tasks:
-    - name: "Common tasks"
-    ...
-

En exécutant la commande ansible-playbook -i inventories main.yml vous ne devriez constater aucun changements notables si ce n'est que l'exécution des instructions relatives à la configuration de motd se fait désormais à partir de notre nouveau rôle.

-

Organisation et structures de contrôle avancées

-

Dans le but d'étoffer et de rendre un peu plus robuste notre rôle nous allons introduire quelques contraintes:

-
    -
  • La possibilité d'activer ou non une fonctionnalité de contrôle des motd existants côté serveur (tolère t-on ou non la présence de motd non gérés par notre rôle ?);
  • -
  • La possibilité de choisir le répertoire d'installation de nos scripts motd;
  • -
  • La possibilité d'avoir plusieurs scripts déployés (actuellement nous n'en déployons qu'un seul).
  • -
-

Mais avant de commencer nous allons, pour l'exemple voir que nous pouvons tout à fait (et c'est d'ailleurs très courant) géré l'inclusion de tâches dans nos rôles comme dans nos playbook principaux.

-

Créons dans le répertoire roles/motd/tasks un fichier nommé scripts.yml dans lequel nous allons transférer le contenu du fichier roles/motd/tasks/main.yml.

-

À présent dans notre fichier roles/motd/tasks/main.yml nous ajoutons les instructions suivantes:

-
---
-
-- name: Scripts
-  ansible.builtin.import_tasks: scripts.yml
-  tags:
-    - workshop_motd
-    - workshop_motd.scripts
-

On y retrouve les instructions d'import de tâches mais également les tags spécifiques qui nous permettrons de cibler nos exécutions.

-

Déclarer les variables de rôles

-

Souvenez-vous, nous avons créé un répertoire defaults, celui-ci va contenir les variables utilisées par notre rôle et leur valeur par défaut.

-

Initialisation de variables par « défaut »

-

Une bonne pratique consiste à toujours déclarer les variables qui seront utilisées dans notre rôle. -Dans notre cas nous utiliserons 3 variables que nous allons donc déclarer dans le fichier roles/motd/defaults/main.yml.

-

Dans le fichier defaults/main.yml nous ajouterons donc:

-
---
-
-workshop_motd_scripts_exclusive: true
-workshop_motd_scripts_dir: /etc/update-motd.d
-workshop_motd_scripts: []
-

Dans l'ordre:

-
    -
  • Une variable qui nous servira à configurer l'exclusivité des scripts motd;
  • -
  • Une seconde qui permettra de configurer le répertoire cible de destination de nos scripts;
  • -
  • Et enfin une dernière qui nous permettra de paramétrer les scripts à déployer côté serveur.
  • -
-

Exclusivité

-

Première contrainte, faire en sorte que notre rôle soit la référence en terme d'état de la machine cible. -Pour se faire, l'idée et de ne conserver à l'exécution que les scripts déclarés dans notre configuration.

-

Avant de nous lancer, il va falloir faire évoluer légèrement notre variable workshop_motd_scripts que vous retrouverez dans les fichiers:

-
    -
  • group_vars/production.yml que nous modifierons comme ci-dessous:
  • -
-
workshop_motd_scripts:
-  - file: 10-message
-    template: scripts/{{ stage }}.j2
-    message: Attention environnement de production !
-  - file: 20-uname
-
    -
  • group_vars/staging.yml que nous modifierons comme ci-dessous:
  • -
-
workshop_motd_scripts:
-  - file: 10-message
-    template: scripts/{{ stage }}.j2
-    message: Environnement de staging !
-

Nous avons fait évoluer la structure de notre variable vers une structure plus complexe:

-
    -
  • file: contient le nom du fichier attendu côté machine cible;
  • -
  • template: indique le fichier de template à utiliser;
  • -
  • message: contient le message à afficher dans notre motd.
  • -
-

Nous avons donc défini deux scripts motd à installer sur notre environnement de production et un seul sur notre environnement de staging.

-
-

L'option register

-

- L'utilisation de l'option register permet de créer une variable à partir des données de sortie résultantes de l'exécution d'une tâche, dans le but de réutiliser cette variable dans une autre tâche à venir. -

-
-

Nous pouvons donc à présent positionner en tout début du fichier roles/motd/tasks/scripts.yaml le bloc d'instruction suivant:

-
---
-
-- name: Checking files to exclude
-  ansible.builtin.set_fact:
-    workshop_scripts_list: "{{ workshop_scripts_list|default([]) + [item.file] }}"
-  changed_when: false
-  loop: "{{ workshop_motd_scripts }}"
-
-- name: MOTD > Exclusive
-  ansible.builtin.find:
-    path: "{{ workshop_motd_scripts_dir }}"
-    file_type: file
-    patterns: "*"
-    excludes: "{{ workshop_scripts_list }}"
-  changed_when: false
-  register: __workshop_motd_scripts_exclusive_find
-  when: workshop_motd_scripts_exclusive
-

Le premier bloc d'instruction aura vocation à lister sous forme d'un tableau « plat » la liste des scripts que nous avons décidé d'avoir sur notre serveur (variable workshop_motd_scripts). -Le but étant de récupérer une variable contenant des informations compréhensibles par l'option excludes du module ansible.builtin.find dont le comportement est similaire à la commande UNIX.

-

La seconde tâche aura pour but de lister les fichiers côté serveur correspondant aux options que l'on aura passées au module à savoir:

-
    -
  • N'importe quel fichier de type fichier (Il n'y a pas de redondance ici, n'oubliez pas que dans un système UNIX, TOUT est fichier)
  • -
  • Dont le nom remplit les conditions du paramètre pattern à savoir « * » soit n'importe quel caractère ou pour résumer tous les fichiers ;);
  • -
  • En excluant les fichiers présents dans la variable workshop_scripts_list.
  • -
-
-

L'instruction loop

-

- L'instruction « loop » permet d'adopter un comportement itératif au niveau de nos tâches. Ainsi la variable considérée en paramètre de l'instruction sera itérée dans la tâche concernée. Chaque itération rendant accessible les éléments via la variable « item ». -

-
-

Les points à retenir:

-
    -
  • La tâche chargée de lister les scripts prévus dans nos fichiers de « provisionning »;
  • -
  • L'utilisation du module file et de ses paramètres qui nous permet de récupérer tous les éléments (*) de type fichier du répertoire cible;
  • -
  • L'utilisation de notre variable de rôle workshop_motd_scripts_dir;
  • -
  • Le conditionnement de l'exécution avec l'instruction when en fonction de la valeur de la variable workshop_motd_scripts_exclusive.
  • -
-
-

L'option changed_when

-

- L'option changed_when nous permet de « décider » si une tâche a modifié (au sens Ansible) notre machine cible. En fonction de son retour nous pouvons donc décider si un changement doit être mentionné ou si un handler doit être déclenché. Bon à savoir également, si vous y définissez plusieurs conditions celles-ci sont jointes avec un opérateur and par défaut. -

-
-

Nous avons donc récupéré la liste des fichiers présents sur notre machine cible il nous reste à présent à supprimer ceux qui ne sont pas gérés par notre script.

-
- name: Remove file (delete file)
-  ansible.builtin.file:
-    path: "{{ item.path }}"
-    state: absent
-  loop: "{{ __workshop_motd_scripts_exclusive_find.files }}"
-

On retrouve à cette occasion l'utilisation du mot clé «loop» afin d'itérer sur la liste des fichiers à supprimer.

-

Point de progression

-

Nous avons à travers cette série d'articles, fait le tour des principes fondamentaux d'Ansible. Ceci n'est bien évidemment que le début du chemin vers la maîtrise d'Ansible mais c'est une première étape fondamentale avant d'aller plus loin !

-

Aller plus loin avec les sources

- -
- -
-
-

- Une typo ? - Modifier cet article sur Github -

-
-
-
-
-
- - - - - - diff --git a/pr/137/blog/cours/ansible/ansible-les-variables/index.html b/pr/137/blog/cours/ansible/ansible-les-variables/index.html deleted file mode 100644 index 1c0791e1..00000000 --- a/pr/137/blog/cours/ansible/ansible-les-variables/index.html +++ /dev/null @@ -1,867 +0,0 @@ - - - - - - - Ansible - Les variables - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - Retour à l'accueil - - - - - - - - -
-
-
- - -
-
-
-
-
    -
  • - #Cours -
  • -
  • - #Ansible -
  • -
  • - #Automation -
  • -
  • - #Variables -
  • -
-

Ansible - Les variables

-

Les variables avec Ansible, introduisons du dynamisme dans nos playbooks !

-
-
    -
  1. - Préambule -
  2. -
  3. - Pré-requis -
  4. -
  5. - Introduction -
  6. -
  7. - Les variables -
      -
    1. - Les variables d'hôte -
    2. -
    3. - Les variables de groupe -
    4. -
    5. - Définir des variables dans un inventaire -
    6. -
    7. - Comment les variables sont elles fusionnées ? -
    8. -
    9. - Last but not least, les facts -
    10. -
    -
  8. -
  9. - Utiliser les variables dans nos playbooks -
  10. -
  11. - Les templates -
      -
    1. - Définir et utiliser un nouveau template -
    2. -
    3. - Variabiliser ses fichiers de configuration -
    4. -
    5. - Les filtres Jinja2 -
    6. -
    7. - Conditionner l'exécution de ses tâches -
    8. -
    -
  12. -
  13. - Point de progression -
  14. -
- -
-
-

Préambule

-

Ce cours est utilisé dans le cadre de TP au sein de l'IUT Lyon 1. Il est notamment dispensé à des étudiants peu ou pas familiers avec les stratégies d'automatisation et de déploiement des infrastructures. -Bien que très axé débutants il peut également représenté une possibilité de monter « rapidement » pour certaines équipes sur les principes fondamentaux d'Ansible afin de disposer du bagage minimal nécessaire à son utilisation.

-

Il s'agit bien évidemment de supports à vocation pédagogique qui ne sont pas toujours transposables à une activité professionnelle.

-

Pré-requis

-

Avoir suivi/lu les cours concernant les inventaires et les playbooks !

-

Et donc disposer d'un inventaire s'approchant de celui-ci:

-
all:
-  hosts:
-    vm-web-prod-01:
-      ansible_host: XXX.XXX.XXX.XXX
-      ansible_user: debian
-    vm-db-prod-01:
-      ansible_host: XXX.XXX.XXX.XXX
-      ansible_user: debian
-    vm-web-staging-01:
-      ansible_host: XXX.XXX.XXX.XXX
-      ansible_user: debian
-    vm-db-staging-01:
-      ansible_host: XXX.XXX.XXX.XXX
-      ansible_user: debian
-  children:
-    webservers:
-      hosts:
-        vm-web-prod-01: ~
-        vm-web-staging-01: ~
-    dbservers:
-      hosts:
-        vm-db-prod-01: ~
-        vm-db-staging-01: ~
-    staging:
-      hosts:
-        vm-web-staging-01: ~
-        vm-db-staging-01: ~
-    production:
-      hosts:
-        vm-web-prod-01: ~
-        vm-db-prod-01: ~
-

Introduction

-

Nous avons vu précemment comment débuter avec Ansible avec les notions fondamentales d'inventaire et de playbooks. Dans l'idée d'apporter un peu plus de matière à cet ensemble nous allons à présent aborder la notion de variables.

-

Les variables

-

Ansible introduit énormément de souplesse en terme « d'endroits » où peuvent être déclarées des variables ce qui laisse énormémement de liberté sur la façon dont on peut organiser un projet. En contrepartie cela requiert de la rigueur afin de respecter les standards établis pour un projet donné, sous peine que cela devienne très rapidement un vrai foutoir.

-

Les variables d'hôte

-

Pour commencer nous aborderons le principe des variables d'hôtes qui en toute logique, permettent de définir des variables au niveau d'une machine bien précise. Vous verrez avec le temps que sur des infrastructures d'exploitation conséquentes ces variables sont souvent peu utilisées car il est rare de n'avoir qu'une seule machine derrière un service.

-

Commençons par créer un nouveau répertoire host_vars à la racine de notre répertoire de travail qui contiendra des fichiers reprenant les nom d'hôtes définis dans notre inventaire, nous débuterons par les membres du groupe webservers et créerons donc 2 fichiers vm-web-prod-01.yml et vm-web-staging-01.yml.

-

Chaque fichier contiendra pour l'instant une définition de variable selon l'exemple suivant:

-
hostname: web-production-01
-

On fera bien évidemment de même avec le second fichier et les instances du groupe dbservers en ajoutant les fichiers vm-db-prod-01.yml et vm-db-staging-01.yml contenant respectivement:

-
hostname: db-production-01
-

et

-
hostname: db-staging-01
-

Il est possible de faire afficher à Ansible les différentes variables définies via ansible-inventory --graph -i inventories --vars qui devrait vous renvoyer pour l'instant:

-
@all:
-  |--@ungrouped:
-  |--@webservers:
-  |  |--vm-web-prod-01
-  |  |  |--{ansible_host = XXX.XXX.XXX.XXX}
-  |  |  |--{ansible_user = debian}
-  |  |  |--{hostname = web-production-01}
-  |  |--vm-web-staging-01
-  |  |  |--{ansible_host = XXX.XXX.XXX.XXX}
-  |  |  |--{ansible_user = debian}
-  |  |  |--{hostname = web-staging-01}
-  |--@dbservers:
-  |  |--vm-db-prod-01
-  |  |  |--{ansible_host = XXX.XXX.XXX.XXX}
-  |  |  |--{ansible_user = debian}
-  |  |  |--{hostname = db-prod-01}
-  |  |--vm-db-staging-01
-  |  |  |--{ansible_host = XXX.XXX.XXX.XXX}
-  |  |  |--{ansible_user = debian}
-  |  |  |--{hostname = db-staging-01}
-  |--@staging:
-  |  |--vm-web-staging-01
-  |  |  |--{ansible_host = XXX.XXX.XXX.XXX0}
-  |  |  |--{ansible_user = debian}
-  |  |  |--{hostname = web-staging-01}
-  |  |--vm-db-staging-01
-  |  |  |--{ansible_host = XXX.XXX.XXX.XXX}
-  |  |  |--{ansible_user = debian}
-  |  |  |--{hostname = db-staging-01}
-  |--@production:
-  |  |--vm-web-prod-01
-  |  |  |--{ansible_host = XXX.XXX.XXX.XXX}
-  |  |  |--{ansible_user = debian}
-  |  |  |--{hostname = web-production-01}
-  |  |--vm-db-prod-01
-  |  |  |--{ansible_host = XXX.XXX.XXX.XXX}
-  |  |  |--{ansible_user = debian}
-  |  |  |--{hostname = db-prod-01}
-

Où l'on peut constater la présence de nos variables au niveau de chacun des hôtes !

-

Les variables de groupe

-

Passons à présent aux variables de groupes, vous l'aurez compris celles-ci s'appliqueront à un groupe de machines tel que nous l'aurons défini dans notre inventaire. -Leur fonctionnement repose sur le même principe que les variables d'hôtes, nous créerons donc cette fois un répertoire appelé group_vars contenant un fichier pour chacun des groupes que nous aurons défini.

-

Nous allons donc créer les fichiers production.yml et staging.yml contenant respectivement pour l'instant:

-
stage: production
-

et

-
stage: staging
-

En rejouant la commande ansible précédente vous pourrez constater qu'une nouvelle variable stage apparait bien comme définie lors de l'affichage de votre inventaire.

-
-

L'héritage des variables

-

- Il est bien évidemment possible d'appliquer une définition de variable aux groupes parents comme aux groupes enfants, dans ce cas on prendra bien soin de faire attention à l'héritage des variables ! -

-
-

On oubliera pas au passage que même s'il n'apparait pas de manière explicite dans notre inventaire, le groupe all est systématiquement défini par Ansible comme « super parent » et qu'il est donc bien évidemment possible de déclarer des variables pour ce groupe en créant un fichier all.yml dans group_vars contenant par exemple:

-
workshop: ansible
-

Définir des variables dans un inventaire

-

Il est possible (je vous l'ai dit Ansible est très souple) de définir des variables directement dans votre fichier d'inventaire, on l'a déjà plus ou moins vu d'ailleurs avec la définition de clés spécifiques à Ansible comme ansible_host ou ansible_user au niveau d'un hôte, l'ajout de variables sur un hôte fonctionne donc de la même façon en ajoutant des clés à la suite.

-

Au niveau d'un groupe, il faudra passer par la clé vars. Par exemple on pourrait imaginer par exemple indiquer un serveur de temps bien précis pour une zone géographique avec quelque chose comme:

-
france:
-  hosts:
-    host-01:
-    host-02:
-  vars:
-    ntp_server: ntp.univ-lyon1.fr
-

Comment les variables sont elles fusionnées ?

-

Ansible fusionne les variables pour les appliquer de manière spécifique à chacun de nos hôtes, cela signifie que sortie de notre définition d'inventaire et de correspondance hôte/groupe la notion de groupe ne perdure pas, en effet Ansible va écraser les variables préalablement définies en suivant cet ordre (de poids le plus faible au plus important):

-
    -
  • groupe all (n'oubliez pas c'est le parent « racine »)
  • -
  • groupe parent
  • -
  • groupe enfant
  • -
  • hôte
  • -
-

Pour résumer de la variable la moins précise (en terme de périmètre de définition) à la plus précise.

-

Quelques points d'attention toutefois:

-

Pour les groupes de même niveau hiérarchique les variables du dernier groupe considéré écraseront les autres, sauf si une pondération est appliquée au niveau du groupe en utilisant la variable ansible_group_priority comme suit:

-
france:
-  hosts:
-    host-01:
-    host-02:
-  vars:
-    ntp_server: ntp.sophia.mines-paristech.fr
-lyon:
-  hosts:
-    host-01:
-    host-02:
-  vars:
-    ntp_server: ntp.univ-lyon1.fr
-    ansible_group_priority: 10
-
-

Prioriser un groupe

-

- ansible_group_priority peut uniquement être défini au niveau de l'inventaire, il n'est pas possible de l'utiliser dans group_vars. -

-
-

Last but not least, les facts

-

Les facts sont au coeur du fonctionnement des variables dans Ansible dans le sens où ce sont des variables spécifiques récupérées directement depuis l'hôte concerné par un déploiement. -Elles permettent de récupérer pas mal d'information parmi lesquelles notamment la ou les interfaces réseaux des machines, le type d'OS et sa version ou encore des informations concernant le matériel de la cible.

-

On peut les consulter en utilisant par exemple le module setup directement en ligne de commande: ansible -i inventories vm-web-prod-01 -m setup. -Il est possible de filtrer la sortie affichée par Ansible en utilisant l'option filter: ansible -i inventories vm-web-prod-01 -m setup -a "filter=ansible_default_ipv4"

-

Ces « facts » se révèleront fort utiles au fur et à mesure de votre prise en main d'Ansible.

-

Utiliser les variables dans nos playbooks

-

Nous avons vu comment définir des variables, c'est bien beau mais comment les utiliser ?

-

Reprenons par exemple nos fichiers de variables d'hôtes ou nous définissons la clé hostname, on peut constater que celle-ci dispose d'une partie qui reprend le contenu de la clé stage définie au niveau du groupe. -On peut donc les modifier de la manière suivante pour exploiter cette définition:

-
hostname: "web-{{ stage }}-01"
-

Allons ensuite modifier notre fichier (common.yml) pour utiliser ces variables de la manière suivante:

-
---
-- name: Updating APT cache index
-  ansible.builtin.apt:
-    update_cache: yes
-# Setting hostname
-- name: Set a hostname
-  ansible.builtin.hostname:
-    name: "{{ hostname }}"
-

Notre tâche vient ici « consommer » la variable hostname et l'utiliser comme paramètre du module Ansible.

-

Les templates

-

Sujet étroitement lié à l'utilisation des variables, les templates au sens d'Ansible sont des fichiers un peu particuliers dont le contenu peut-être défini dynamiquement (par opposition notamment à l'utilisation de fichiers de configuration « statiques ») comme nous avons pu le voir dans la partie playbook.

-

Il faut également savoir qu'Ansible s'appuie sur le moteur de template Jinja2 issu du monde Python qui pourrait être comparé à Twig pour PHP, Pebble pour Java, Liquid pour RoR ou encore DotLiquid pour .Net.

-

Nous l'avons vu les variables peuvent être définies à divers endroits et peuvent ensuite être accessibles avec les doubles parenthèses {{ ... }}.

-

Imaginons que nous souhaitions personnaliser nos motd par exemple en fonction de l'environnement duquel fait partie notre hôte cible.

-

Définir et utiliser un nouveau template

-

Ansible prévoit par défaut l'utilisation d'un répertoire templates pour cette mission, que nous devrons donc créer (toujours à la racine de notre répertoire de travail). -Afin de stocker 2 fichiers nous créerons un répertoire dédié à motd qui contiendra donc production.j2 et staging.j2

-

Pour ces deux nouveaux fichiers nous ajouterons les contenus suivants:

-

Pour la production:

-
#!/bin/sh
-
-MOTD=$(cat <<'EOF'
-[38;5;28m                    ____[0m[0m
-[38;5;28m                 _.' :  `._[0m
-[38;5;28m             .-.'`.  ;   .'`.-.[0m
-[38;5;28m    __      / : ___\ ;  /___ ; \      __[0m
-[38;5;28m  ,'_ ""--.:__;".-.";: :".-.":__;.--"" _`,[0m
-[38;5;28m  :' `.t""--.. '[38;5;241m<[38;5;234m@[38;5;241m,[38;5;28m`;_  '[38;5;241m,[38;5;234m@[38;5;241m>[38;5;28m` ..--""j.' `;[0m
-[38;5;28m       `:-.._J '-.-'L__ `-- ' L_..-;'[0m
-[38;5;28m         "-.__ ;  .-"  "-.  : __.-"[0m
-[38;5;28m             L ' /.------.\ ' J[0m
-[38;5;28m              "-.   "--"   .-"[0m
-[38;5;230m             [0m[38;5;94m__[38;5;28m.l"-:_JL_;-";.[0m[38;5;94m__[0m
-[38;5;230m          .-j[0m[38;5;94m/'.[38;5;28m;  ;""""  /[0m[38;5;94m .'[0m[38;5;230m\"-.[0m
-[38;5;230m        .' /:[0m[38;5;94m`. "-.[38;5;28m:    :[0m[38;5;94m.-" .'[0m[38;5;230m;  `.[0m
-[38;5;230m     .-"  / ;[0m[38;5;94m  "-. "-..-" .-"[0m[38;5;230m  :    "-.[0m
-[38;5;230m  .+"-.  : : [0m[38;5;94m     "-.__.-"[0m[38;5;230m      ;-._   \[0m
-[38;5;230m  ; \  `.; ;                    : : "+. ;[0m
-[38;5;230m  :  ;   ; ;                    : ;  : \:[0m
-[38;5;230m : `."-; ;  ;                  :  ;   ,/;[0m
-[38;5;230m  ;    -: ;  :                ;  : .-"'  :[0m
-[38;5;230m  :\     \  : ;             : \.-"      :[0m
-[38;5;230m   ;`.    \  ; :            ;.'_..--  / ;[0m
-[38;5;230m   :  "-.  "-:  ;          :/."      .'  :[0m
-[38;5;230m     \       .-`.\        /t-""  ":-+.   :[0m
-[38;5;230m      `.  .-"    `l    [0m[38;5;28m__/ /[0m[38;5;94m`. :  ; ;[0m[38;5;230m \  ;[0m
-[38;5;230m        \   .-" .-"[0m[38;5;28m-.-"  .' .'[0m[38;5;94mj \  /[0m[38;5;230m   ;/[0m
-[38;5;230m         \ / .-"[0m[38;5;28m   /.     .'.' [0m[38;5;94m;_:'[0m[38;5;230m    ;[0m
-[38;5;230m          :-""[0m[38;5;28m-.`./-.'     /[0m[38;5;230m    `.___.'[0m
-[38;5;28m                \ `t  ._  /[0m
-[38;5;28m                 "-.t-._:'[0m
-
-{{ motd.message | center(42) }}
-
-EOF
-)
-
-printf "${MOTD}\n\n\n"
-

Pour la staging:

-
#!/bin/sh
-{% set message = motd.message -%}
-
-MOTD=$(cat <<'EOF'
- -{% for i in range(message | length) %}-{% endfor %}-
-< {{ message }} >
- -{% for i in range(message | length) %}-{% endfor %}-
-        \   ^__^
-         \  (oo)\_______
-            (__)\       )\/\\
-                ||----w |
-                ||     ||
-
-EOF
-)
-
-printf "${MOTD}\n\n\n"
-

Vous devriez à ce stade disposer d'une arborescence ressemblant à ça:

-
|-- example.yml
-|-- files
-|   `-- nginx
-|       `-- status.conf
-|-- group_vars
-|   |-- all.yml
-|   |-- production.yml
-|   `-- staging.yml
-|-- host_vars
-|   |-- vm-db-prod-01.yml
-|   |-- vm-db-staging-01.yml
-|   |-- vm-web-prod-01.yml
-|   `-- vm-web-staging-01.yml
-|-- inventories
-|   `-- hosts.yml
-`-- templates
-    `-- motd
-        |-- production.j2
-        `-- staging.j2
-

Après avoir ajouté le contenu de chacun de ces motd nous allons exploiter ces 2 templates en modifiant notre playbook de la façon suivante:

-
# MOTD
-    - name: MOTD > Installing template
-      ansible.builtin.template:
-        src: "motd/{{stage}}.j2"
-        dest: "/etc/update-motd.d/10-welcome"
-        owner: root
-        group: root
-        mode: "0755"
-

Vous l'aurez sans doute compris, nous allons déployer un motd différent en fonction de notre environnement, l'idée étant d'afficher de manière flagrante à la connexion si nous arrivons sur un environnement de production ou de staging.

-

Variabiliser ses fichiers de configuration

-

Avec cette première approche vous avez sans doute vu les possibilités qui s'offrent à nous, nous avons vu pour l'instant la possibilité de déposer des fichiers de manière « statique » à savoir sans données spécifiques à un hôte et un groupe. -C'est justement ce qu'apporte les templates nous allons pouvoir définir des variables dont la valeur dépendra de l'environnement d'exécution ou de la finalité de nos serveurs.

-

Introduisons à présent un service PHP-FPM qui nous permettra de faire tourner des applicatifs basés sur PHP.

-

Toujours dans notre playbooks webservers ajoutons une section pour PHP où nous gérons:

-
    -
  • l'installation du paquet;
  • -
  • l'ajout d'un fichier de configuration personnalisé;
  • -
  • le redémarrage du service.
  • -
-
# PHP
-    - name: Install PHP-FPM service
-      ansible.builtin.apt:
-        name: php-fpm
-        state: present
-    - name: PHP-FPM > Configuration
-      ansible.builtin.template:
-        src: "php/app.ini.j2"
-        dest: "/etc/php/8.2/fpm/app.ini"  
-
-  handlers:
-    - name: restart_php-fpm
-      ansible.builtin.service:
-        name: php8.2-fpm
-        state: restarted
-

Si jamais vous avez oublié comment fonctionnait les handlers (ou à quoi ils servent) vous pouvez vous référer à la partie playbooks.

-

Il nous restera à créer notre template dans le répertoire templates/php portant le nom app.ini.j2, le nommage importe peu mais il peut donner une idée de l'utilité du fichier en question, dans notre cas la double extension .ini.j2 nous permet d'indiquer qu'il s'agit d'un template Jinja2 et que son contenu est au format ini.

-

Profitons en pour introduire les structures de contrôle à l'intérieur de templates Jinja2.

-

Le contenu de notre fichier app.ini.j2 sera ainsi:

-
{# PHP custom configuration, handle by Ansible #} 
-{%- set config = php.config|default({}) -%}
-
-{% for key, value in config.items() %}
-  {{ key }} = {{ value }}
-{% endfor %}
-

On y remarquera trois choses:

-
    -
  • Les commentaires sont déclarés à l'aide de {#...#};
  • -
  • Il est possible de déclarer des variables à l'intérieur d'un template Jinja2 à l'aide de la structure {%- set variable_name = value -%};
  • -
  • On utilise pour l'exemple une structure itérative {% for %}...{% endfor %}.
  • -
-

Les structures de contrôles dans la syntaxe jinja2 sont exprimées à l'aide des marqueurs {% ... %}

-

Il nous reste a alimenter notre template à partir de nos fichiers de déclaration de variables, ici group_vars/webservers.yml qui contiendra:

-
php:
-  config:
-    error_reporting: 'E_ALL & ~E_DEPRECATED & ~E_STRICT'
-    display_errors: False
-    memory_limit: 256M
-

Les filtres Jinja2

-

Jinja2 propose nativement un certain nombre de « filtres » permettant de faire des manipulations basiques à l'intérieur de nos templates. -Afin d'appliquer un filtre à une valeur on utilise la notation |.

-

Exemple:

-
{{ "lyon" | capitalize }}
-

renverra

-
# output
-Lyon
-

Ci-dessous un playbook utilisant différents filtres (ne pas hésiter à tester dans un playbook dédié jinjaFilters.yml ;) )

-
- name: "Jinja Filters Playbook"
-  hosts: localhost
-  gather_facts: no
-  vars:
-    mandala: element
-
-  tasks:
-    - name: Mandala variable is mandory
-      ansible.builtin.debug:
-        msg: "{{ mandala | mandatory }}"
-
-    - name: Undefined variable have a default
-      ansible.builtin.debug:
-        msg: "{{ undefined_var | default('default') }}"
-
-    - name: Omitting a parameters
-      ansible.builtin.debug:
-        msg: "{{ va | default(omit) }}" #If omitted, prints a generic message.
-
-    - name: Flatten a list
-      ansible.builtin.debug:
-        msg: "{{ [3, [4, 2] ] | flatten }}"
-
-    - name: Join two list with '+'
-      ansible.builtin.debug:
-        msg: "{{ [3, 4] + [4, 2] }}"
-
-    - name: Join two list with | union()
-      ansible.builtin.debug:
-        msg: "{{ [3, 4] | union([4, 2]) }}"
-
-    - name: Hash a string
-      ansible.builtin.debug:
-        msg: "{{ 'secret' | hash }}"
-
-    - name: Hash a password
-      ansible.builtin.debug:
-        msg: "{{ 'secret' | password_hash }}"
-
-    - name: Combine hashes
-      ansible.builtin.debug:
-        msg: "{{ {'param1': ['value1', 'value3']} | combine({'param2': 'value2'}) }}"
-
-    - name: Url split
-      ansible.builtin.debug:
-        msg: "{{ 'https://user:password@www.example.com:9000/dir/index.html?query=term#fragment' | urlsplit }}"
-
-    - name: Display date time
-      ansible.builtin.debug:
-        msg: "{{ '%Y-%m-%d %H:%M:%S' | strftime }}"
-
-    - name: "Multi Filter : Play with datetime objet to get minutes from now to end of this course"
-      ansible.builtin.debug:
-        msg: "{{ ((('%Y-%m-%d %H:%M:%S' | strftime | to_datetime) -  ('%Y-%m-%d 18:00:00' | strftime | to_datetime)).total_seconds() / 60) | abs }}"
-

Conditionner l'exécution de ses tâches

-

Il est également possible de venir utiliser des structures conditionnelles à l'intérieur de nos playbooks et de par exemple, conditionner l'exécution de certaines tâches à un contexte particulier. -On peut ainsi imaginer retreintre l'utilisation des tâches utilisant le module apt aux seules distributions Debian.

-

Nous le ferions par exemple en ajoutant une condition sur toutes nos tâches faisant appel au module de la façon suivante:

-
pre_tasks:
-    - name: Updating APT cache index
-      ansible.builtin.apt:
-        update_cache: yes
-      when: ansible_distribution == "Debian"
-

On remarquera que la condition when contient une expression Jinja « brute » sans {{ ... }} -On pourra utiliser différents opérateurs parmi les suivants:

- -

Application pratique : Nous allons conditionner l'exécution de la partie PHP, on peut en effet imaginer avoir des serveurs web ne servant que du contenu statique et qui n'auront donc pas forcément besoin de PHP.

-

Modifions nos deux tâches concernant PHP comme suit (pour rappel dans webservers.yml):

-
...
-    # PHP
-    - name: Install PHP-FPM service
-      ansible.builtin.apt:
-        name: php-fpm
-        state: present
-      when: php.enabled
-      tags:
-        - php
-        - installation
-
-    - name: PHP-FPM > Configuration
-      ansible.builtin.template:
-        src: "php/app.ini.j2"
-        dest: "/etc/php/8.2/fpm/conf.d/app.ini"    
-      notify:
-          - restart_php-fpm
-      when: php.enabled
-      tags:
-        - php
-        - configuration
-...
-

Et notre fichier de variables de groupe group_vars/webservers.yml:

-
php:
-  enabled: false
-  ...
-

En jouant sur l'état de notre variable php.enabled nous pouvons donc activer / désactiver l'exécution de nos tâches PHP ce qui donnera:

-
- -
- Conditionnement d'exécution des tâches. -
-
-

Point de progression

-

Nous avons à présent entre nos mains la quasi totalité des concepts fondamentaux d'Ansible

- -

La prochaine étape sera orientée sur la réutilisation, l'optimisation et la structuration de ces concepts en introduisant la notion de roles/collections.

- -
- -
-
-

- Une typo ? - Modifier cet article sur Github -

-
-
-
-
-
- - - - - - diff --git a/pr/137/blog/cours/ansible/ansible-premiers-pas/index.html b/pr/137/blog/cours/ansible/ansible-premiers-pas/index.html deleted file mode 100644 index f6498aa3..00000000 --- a/pr/137/blog/cours/ansible/ansible-premiers-pas/index.html +++ /dev/null @@ -1,502 +0,0 @@ - - - - - - - Ansible - Découverte et premiers pas. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - Retour à l'accueil - - - - - - - - -
-
-
- - -
-
-
-
-
    -
  • - #Cours -
  • -
  • - #Ansible -
  • -
  • - #Automation -
  • -
-

Ansible - Découverte et premiers pas.

-

Dans ce premier cours à destination des étudiants et/ou néophytes, nous verrons ce qu'est Ansible ainsi qu'un exemple très simple de son utilisation.

-
-
    -
  1. - Préambule -
  2. -
  3. - Prérequis -
  4. -
  5. - Mise en route -
  6. -
  7. - Infrastructure -
  8. -
  9. - Environnement local -
      -
    1. - Se connecter avec le client SSH -
    2. -
    3. - Configuration du client SSH -
    4. -
    5. - Utilisation de l'agent SSH -
    6. -
    7. - Communication Ansible <> serveurs distants -
    8. -
    -
  10. -
  11. - Aller plus loin avec les sources: -
  12. -
- -
-
-

Préambule

-

Ce cours est utilisé dans le cadre de TP au sein de l'IUT Lyon 1. Il est notamment dispensé à des étudiants peu ou pas familiers avec les stratégies d'automatisation et de déploiement des infrastructures. -Bien que très axé débutants il peut également représenter une possibilité de monter « rapidement » pour certaines équipes sur les principes fondamentaux d'Ansible afin de disposer du bagage minimal nécessaire à son utilisation.

-

Il s'agit bien évidemment de supports à vocation pédagogique qui ne sont pas toujours transposables à une activité professionnelle.

-

Prérequis

-

Afin d'aborder les différents concepts du cours il est recommandé de disposer:

-
    -
  • D'au moins deux machines virtuelles accessibles via SSH (idéalement 4);
  • -
  • Docker et Docker compose installés sur la machine de travail (Docker Desktop pour Windows et OSX);
  • -
  • D'une installation d'Ansible récente (2.15.5), s'il est possible de l'installer localement je recommanderais plutôt d'utiliser le Lazy Ansible du projet Manala..
  • -
  • D'une paire de clés SSH que vous aurez pris soin de générer (voir ici) si vous n'en disposez pas déjà.
  • -
  • D'un répertoire de travail, de mon côté ça sera workspace/ansible (très original oui), son nom importe peu, l'idée est que vous sachiez vous y retrouver;
  • -
-

Pour ceux qui utilisent Windows, il est possible d'utiliser WSL pour faire fonctionner les conteneurs Docker, une machine virtuelle Linux fonctionne encore mieux, libre à vous d'utiliser l'un ou l'autre.

-

Mise en route

-

Première étape avant de pouvoir rentrer dans le vif du sujet, nous aurons besoin de mettre en place un environnement de travail dédié à nos travaux.

-

Infrastructure

-

Pour pouvoir configurer nos serveurs, il nous faudra... des serveurs, ou plutôt des machines virtuelles pour leur facilité à être arrêtées, détruites et reconstruites. -N'importe quel fournisseur de cloud public peut faire l'affaire, utilisez celui avec lequel vous avez le plus d'affinités.

-

Dans le cadre de l'IUT nous utiliserons OpenStack, solution OpenSource qui a fait ses preuves et qui plus est disponible dans l'enceinte de l'université, c'est également la solution technique utilisée par le Public Cloud d'OVHCloud. -C'est donc sur cette base que je présenterai les étapes suivantes, au demeurant, parfaitement transposables chez d'autres fournisseurs.

-

Nous travaillerons avec deux environnements distincts, « Staging » et « Production » qui embarqueront chacune une instance applicative (qui portera donc le code d'une application) et une instance destinée aux données (et donc chargée de faire fonctionner notre serveur de base de données). -Si vous êtes limité en terme de création d'instances, il est envisageable de n'avoir qu'une instance par environnement, celle-ci embarquant l'applicatif et les données.

-

Environnement local

-

Les étapes suivantes seront donc à exécuter à partir de votre machine.

-

Se connecter avec le client SSH

-

Considérant que vous remplissez les prérequis et que vous avez créé vos instances distantes nous allons pour commencer initier une « simple » connexion SSH vers notre instance.

-
ssh debian@XXX.XXX.XXX.XXX
-

Si vous rencontrez des soucis .. forbidden (exemple) ré-essayez en ajoutant explicitement le chemin vers la clé.

-
ssh -i ~/.ssh/ed25519 debian@XXX.XXX.XXX.XXX
-
-

Utilisateur sous Windows

-

- Pour rappel aux utilisateurs de Windows vous trouverez ce répertoire .ssh dans C:\Users\MonNomUtilisateur\ -

-
-

Configuration du client SSH

-

Afin d'éviter d'avoir à spécifier le chemin vers la clé à chaque connexion et afin d'affiner la configuration de notre client nous pouvons également définir un fichier ~/.ssh/config contenant les directives suivantes:

-
Host 192.168.140.*
-  Port 22
-  User debian
-  IdentityFile ~/.ssh/keyfile
-  IdentitiesOnly yes
-  ForwardAgent yes
-

Celles-ci sont relativement compréhensibles, précisons tout de même pour les deux dernières:

-
    -
  • IdentitiesOnly indique à SSH de n'envoyer au serveur QUE la clé définie à la directive IdentityFile quand bien même vous disposez d'autres clés dans votre répertoire ~/.ssh
  • -
  • ForwardAgent permet d'activer le transfert d'identité vers l'agent SSH du serveur
  • -
-

Cette configuration vous permet d'indiquer certaines directives de manière automatique pour un ou plusieurs hôtes distants, pour en savoir plus concernant les fichiers de configuration SSH vous pouvez aller jeter un oeil ici

-

Utilisation de l'agent SSH

-

La prochaine étape est l'utilisation d'un service spécifique à SSH, l'agent.

-

L'agent SSH sur la plupart des systèmes UNIX est lancé au démarrage de votre machine, toutefois si ça n'est pas le cas, il est possible de le démarrer avec la commande eval 'ssh-agent'. -Son rôle est de permettre de stocker de manière sécurisée votre/vos clés privées SSH (rappelez-vous c'est la partie que l'on ne partage pas !) mais également d'assurer le transfert de cette clé privée en toute sécurité vers les serveurs distants auxquels vous tenterez de vous connecter.

-

Ajouter une clé dans l'agent

-

L'ajout d'une clé dans un agent est trivial et se fait à l'aide de la commande ssh-add ~/.ssh/my_private_key.

-

Si vous avez protégé votre clé avec une phrase de passe elle vous sera demandée par l'agent au moment de son ajout. -Afin de vérifier que votre clé a bien été ajoutée à votre agent vous pouvez lister les clés contenues à l'intérieur avec la commande ssh-add -l qui devrait vous donner une sortie équivalente à la suivante:

-
rix@debian:~$ ssh-add -l 
-4096 SHA256:knyjFlzIWukj77PBs0V+mO4eKD9mnSITOkYfYvgvZcQ /home/rix/.ssh/gfaivre-iut (RSA)
-

Cette étape, complétée par la directive ForwardAgent contenue dans notre fichier de configuration SSH (pour rappel ~/.ssh/config) va nous permettre lorsque nous nous connectons à un serveur distant de transférer notre clé privée vers l'agent de ce même serveur.

-

De cette manière notre clé privée sera même disponible sur le serveur auquel nous nous connectons, nous aborderons l'utilité de cette configuration plus tard.

-

Communication Ansible <> serveurs distants

-

Notre environnement étant « prêt » testons à présent la bonne communication avec nos serveurs distants en utilisant le module ping d'Ansible.

-

À partir de ce moment et sauf instruction contraire nous partirons du principe que nous évoluons à l'intérieur de notre répertoire de travail (workspace/ansible donc ;)) pour saisir nos commandes et créer notre arborescence de projet.

-
-

Les modules Ansible

-

- Dans la terminologie Ansible, les « modules » sont des morceaux de code pouvant être utilisés soit directement dans la ligne de commande (avec l'option -m, soit dans une section task d'un « playbook »). Ils peuvent prendre en charge des arguments avec une syntaxe classique key=value. -

-
-

Pour pouvoir effectuer notre premier test nous allons donc créer un fichier que nous appellerons hosts.yml contenant (à adapter en fonction du réseau sur lequel sont déployées vos machines virtuelles):

-
all:
-  hosts:
-    ansible-vm-01:
-      ansible_host: 192.168.140.30
-      ansible_user: debian
-

Attention à l'indentation et faites attention de bien utiliser des espaces pour celle-ci.

-

Pour terminer nous lançons notre conteneur docker « lazy » avec un make sh et y exécutons la commande ansible -i hosts.yml all -m ping, qui utilise le module ping d'ansible pour vérifier que l'on arrive bien à se connecter à l'instance distante.

-

Ce qui nous donne:

-
- -
- Utilisation du module ping avec Ansible. -
-
-
-

Le module ping

-

- Bien que son nom puisse porter à confusion, il s'agit là d'un module propre à Ansible et qui n'a rien à voir avec la commande système du même nom. Pour rappel, la commande système envoie un paquet ICMP (ECHO_REQUEST) à une machine distante et attend en retour un paquet du même type (ECHO_RESPONSE) indiquant le bon état de la liaison réseau. - Le module Ansible quant à lui se connecte via SSH à la machine distante et y vérifie la bonne configuration de Python. -

-
-

Cette dernière étape me permet d'introduire un concept que nous verrons dans la section suivante, celui des inventaires !

-

Aller plus loin avec les sources:

- -
- -
-
-

- Une typo ? - Modifier cet article sur Github -

-
-
-
-
-
- - - - - - diff --git a/pr/137/blog/cours/cle-ssh-principes-de-base/index.html b/pr/137/blog/cours/cle-ssh-principes-de-base/index.html deleted file mode 100644 index 86c88f18..00000000 --- a/pr/137/blog/cours/cle-ssh-principes-de-base/index.html +++ /dev/null @@ -1,480 +0,0 @@ - - - - - - - Principes de base de l'utilisation de clés SSH. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - Retour à l'accueil - - - - - - - - -
-
-
- - -
-
-
-
-
    -
  • - #Cours -
  • -
  • - #Ssh -
  • -
  • - #HowTo -
  • -
-

Principes de base de l'utilisation de clés SSH.

-

Génération, utilisation et cas pratiques d'utilisation de clés SSH.

-
-
    -
  1. - Pré-requis -
  2. -
  3. - TL;DR -
  4. -
  5. - Génération d'une paire de clés -
  6. -
  7. - Se connecter à un serveur distant -
  8. -
  9. - Compléments -
  10. -
  11. - Aller plus loin avec les sources -
  12. -
- -
-
-

Une clé SSH est un moyen d'authentification vers un serveur SSH reposant sur les principes de cryptographie asymétrique et d'authentification défi / réponse.

-

Elle a deux avantages fondamentaux comparativement avec une authentification par couple identifiant/mot de passe:

-
    -
  • Permettre une authentification facilité (plus de mot de passe à mémoriser) et plus rapide (possibilité de rebond de serveur à serveur par exemple).
  • -
  • Se prémunir des attaques de type force brute
  • -
-

La très grande majorité des accès serveurs sont aujourd'hui basés sur leur utilisation, au dela de l'aspect fluidité et sécurité, elle ouvre également la possibilité d'authorisation multiple (sur plusieurs serveurs), de révocation et de signature des accès facilités.

-
-

Secure Shell (SSH)

-

- Secure Shell (SSH) est à la fois un programme informatique et un protocole de communication sécurisé. - Le protocole de connexion impose un échange de clés de chiffrement en début de connexion. Par la suite, tous les segments TCP sont authentifiés et chiffrés. Il devient donc impossible d'utiliser un sniffer pour voir ce que fait l'utilisateur. - Le protocole SSH a été conçu avec l'objectif de remplacer les différents protocoles non chiffrés comme rlogin, telnet, rcp et rsh. -

-
-

Pré-requis

-
    -
  • Avoir un client SSH installé (OpenSSH pour Linux/OSX et Windows à présent ou encore Putty pour Windows)
  • -
  • Une ligne de commande ( le truc noir dans lequel on tape du texte ;) )
  • -
-

TL;DR

-

Génération d'une paire de clés:

-
ssh-keygen -t ed25519 -a 150
-

Se connecter à un serveur distant

-
ssh -i ~/.ssh/id25519 user@server_address
-

Génération d'une paire de clés

-

Le principe de l'authentification par clés repose, comme explicité sur les différents liens ci-dessus, par la création d'une paire de clés asymétriques. -L'une de ces clés sera votre clé publique à déployer sur les machines auxquelles vous avez le droit de vous connecter, l'autre, votre clé privée. Et comme son nom l'indique, celle-ci est à vous et rien qu'à vous ; elle ne se partage pas. JAMAIS.

-

Deux notions de base avant de se lancer pour bien comprendre ce que l'on fait:

-
    -
  • Il existe plusieurs types d'algorithmes de signature numérique, les plus répandus étant RSA et Ed25519;
  • -
  • Il est possible de spécifier la longueur de vos clés, ce paramètre est essentiel à leur robustesse.
  • -
-

Il est recommandé, à la date de rédaction de cet article, d'utiliser l'algorithme Ed25519 qui a plusieurs avantages comparativement à RSA:

-
    -
  • Robustesse accrue;
  • -
  • Plus petite taille de clés;
  • -
  • Génération des clés plus rapide.
  • -
-
ssh-keygen -t ed25519 -a 150 -C "courriel@example.com"
-
- -
- Génération d'une paire de clé SSH -
-
-

L'option -C permet d'ajouter un commentaire à votre clé, pratique notamment pour identifier le propriétaire d'une clé publique coté serveur.

-
-

Phrase de passe

-

- Bien que facultative, il est « extrêmement vachement recommandé » de disposer d'une phrase de passe sur vos clés SSH (dans le cadre des cours et pour gagner du temps il est possible de s'en passer si vous n'utilisez pas votre clé en dehors de ceux-ci). -

-
-

Cette commande vous aura généré deux fichiers dans le répertoire ~/.ssh/ (sauf si vous l'avez modifié bien évidemment):

-
    -
  • id_ed25519.pub (comme son extension l'indique c'est votre clé publique);
  • -
  • id_ed25519 votre clé privée (on remarquera les droits qui lui sont appliqués 0600, en effet seul votre utilisateur doit y avoir accès).
  • -
-
-

Générer une clé RSA

-

- Ed25519 n'étant de temps en temps pas supporté (surtout par les anciens systèmes) il est parfois nécessaire de générer une paire de clé RSA (on remarquera la longueur de clé de 4096 bits recommandée à date de rédaction de l'article): - ssh-keygen -t rsa -a 150 -b 4096 -

-
-

Se connecter à un serveur distant

-

C'est un peu la finalité. -Imaginons un serveur pour lequel votre clé est autorisée à se connecter (pour rappel fichier authorized_keys), nous pouvons initier une connexion à l'aide de la commande:

-

ssh user@server_address

-

Cette commande aura donc pour effet « d'ouvrir » une connexion sur un serveur distant via le protocol SSH vous permettant de saisir des lignes de commande directement sur ce serveur et donc de l'administrer.

-
- -
- Ouverture d'une session sur un serveur distant -
-
-

Cette exemple montre l'ouverture d'une session avec l'utilisateur debian sur le serveur ayant pour adresse IP 146.59.243.95.

-

Plusieurs choses à retenir à cette étape:

-
    -
  • Par défaut ssh parcourt les clés SSH privées disponibles dans le répertoire ~/.ssh afin de les proposer au serveur auquel vous essayez de vous connecter.
  • -
  • Vous optenez en retour la première fois que vous vous connectez un message vous demandant de confirmer la connexion vers le serveur distant (Host key checking).
  • -
-

Compléments

-

Si vous disposez de plusieurs clés SSH et que vous ne souhaitez pas que l'ensemble de vos clés privées soient soumises au serveur distant vous pouvez spécifier quelle clé utiliser en utilisant l'option -i.

-
ssh -i ~/.ssh/id25519 debian@146.59.243.95
-

Il est possible d'utiliser des syntaxes différentes en fonction de votre fichier de configuration SSH.

-

Vous pouvez ainsi agir sur les comportements par défaut de votre client SSH et notamment sur la clé à utiliser en fonction de tel ou tel serveur.

-

Aller plus loin avec les sources

- -
- -
-
-

- Une typo ? - Modifier cet article sur Github -

-
-
-
-
-
- - - - - - diff --git a/pr/137/blog/cours/docker-avec-windows-et-wsl/index.html b/pr/137/blog/cours/docker-avec-windows-et-wsl/index.html deleted file mode 100644 index 1ef26cc5..00000000 --- a/pr/137/blog/cours/docker-avec-windows-et-wsl/index.html +++ /dev/null @@ -1,460 +0,0 @@ - - - - - - - Faire fonctionner des conteneurs Docker dans WSL. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - Retour à l'accueil - - - - - - - - -
-
-
- - -
-
-
-
-
    -
  • - #Cours -
  • -
  • - #Windows -
  • -
  • - #Docker -
  • -
  • - #Conteneur -
  • -
-

Faire fonctionner des conteneurs Docker dans WSL.

-

Avec l'intégration WSL2 de Windows et Docker Desktop, comment utiliser des conteneurs Docker dans une machine virtuelle WSL ?

-
-
    -
  1. - Pré-requis -
  2. -
  3. - Installer WSL 2 et lancer une machine virtuelle -
  4. -
  5. - Installer Docker Desktop -
      -
    1. - Configuration -
    2. -
    -
  6. -
  7. - Tester le fonctionnement de Docker dans une machine virtuelle WSL -
  8. -
  9. - Aller plus loin avec les sources -
  10. -
- -
-
-

Avec l'arrivée de Docker Desktop il est dorénavant aisé de faire « tourner » des conteneurs Docker sous Windows. -Il est par contre moins simple d'utiliser d'autres outils propres au monde UNIX que nous aurons besoin d'utiliser avec le Lazy Ansible de Manala (make par exemple).

-

Et comme nous préférons éviter d'installer trop de choses sur les machines hôtes nous opterons pour un fonctionnement qui reste relativement élégant à savoir lancer une machine WSL Debian et y faire tourner notre conteneur Docker. -De cette manière nous disposerons de l'ensemble de l'outillage Linux, sans avoir à l'installer sur notre poste.

-

Pré-requis

-
    -
  • Windows 10 au minimum
  • -
  • Installer terminal windows (pour le confort).
  • -
-

Installer WSL 2 et lancer une machine virtuelle

-
    -
  • S'assurer que l'on utilise bien la version 2 de WSL: wsl --set-default-version 2
  • -
  • Installer et lancer une machine virtuelle Debian: wsl --install -d Debian
  • -
-

Il faudra ensuite renseigner un nom d'utilisateur ainsi qu'un mot de passe (à ne pas perdre de préference). -Vous devriez au final obtenir un shell comme ci-dessous, félicitation vous êtes dans une machine virtuelle Debian WSL !

-
- Un shell WSL -
- Un shell WSL -
-
-

Installer Docker Desktop

-

La partie la plus simple, téléchargez et installez le ici: https://docs.docker.com/desktop/windows/wsl/#download

-

Configuration

-

Il y a quelques options à vérifier / activer pour un bon fonctionnement.

-
    -
  • Tout d'abord vérifier que le support de WSL 2 est activé dans les paramètres (Resources -> WSL integration);
  • -
-
- Les paramètres Docker Desktop -
- Les paramètres Docker Desktop -
-
-
    -
  • Ensuite vérifier que le support pour votre distribution est activé (Debian);
  • -
-

Tester le fonctionnement de Docker dans une machine virtuelle WSL

-

Il faudra pour cela quitter et relancer (Dans un PowerShell wsl -d Debian) votre machine virtuelle Debian. -Une fois à l'intérieur de celle-ci il sera nécessaire de donner les droits à votre utilisateur d'utiliser Docker en l'ajoutant au groupe du même nom.

-
usermod -a -G docker <username>
-

Pour terminer la commande docker ps devrait vous renvoyer l'écran ci-dessous:

-
- Ajouter un utilisateur au groupe docker -
- Ajouter un utilisateur au groupe docker -
-
-

Aller plus loin avec les sources

- -
- Crédits: photo de couverture par - - Jason Cooper - -
-
- -
-
-

- Une typo ? - Modifier cet article sur Github -

-
-
-
-
-
- - - - - - diff --git a/pr/137/blog/cours/utiliser-la-configuration-ssh-client/index.html b/pr/137/blog/cours/utiliser-la-configuration-ssh-client/index.html deleted file mode 100644 index 0a7fe3fe..00000000 --- a/pr/137/blog/cours/utiliser-la-configuration-ssh-client/index.html +++ /dev/null @@ -1,561 +0,0 @@ - - - - - - - Utiliser le fichier de configuration SSH pour ses connexions distantes. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - Retour à l'accueil - - - - - - - - -
-
-
- - -
-
-
-
-
    -
  • - #Cours -
  • -
  • - #Ssh -
  • -
  • - #HowTo -
  • -
-

Utiliser le fichier de configuration SSH pour ses connexions distantes.

-

Apprendre à utiliser le fichier de configuration SSH pour organiser ses options de connexion.

-
-
    -
  1. - Pré-requis -
  2. -
  3. - TL;DR -
  4. -
  5. - Pourquoi utiliser un fichier de configuration ? -
  6. -
  7. - Le fichier de configuration -
  8. -
  9. - Les instructions de configuration -
  10. -
  11. - Priorité des instructions -
  12. -
  13. - Séparer et inclure ses fichiers de configuration -
  14. -
  15. - Les instructions les plus courantes / utiles -
  16. -
  17. - Multiplexer ses connexions SSH -
      -
    1. - Exemple de fichiers de configuration avec multiplexage -
    2. -
    -
  18. -
  19. - Aller plus loin avec les sources -
  20. -
- -
-
-

Si vous vous connectez à plusieurs hôtes distants il y a fort à parier que vous ne mémorisez pas les subtilités de connexion propre à chacun d'entre eux (utilisateur spécifique, port non standard, options spécifiques…).

-

On a eu, il est vrai, l'occasion de croiser des façons originales de le faire, notamment à base d'alias Bash mais ça reste assez artisanal alors même que tout est prévu et qu'OpenSSH permet de définir un fichier de configuration propre à chaque utilisateur du système ;)

-

Cet article, principalement à destination des étudiant·e·s et des nouveaux arrivants dans le domaine saura sans doute également servir de pense bête aux confirmé·e·s !

-

Pré-requis

-

Ils sont plus que pauvre puisqu'un client OpenSSH et une machine distante à laquelle se connecter suffiront.

-

TL;DR

-

Contenu d'un fichier config d'un client SSH pour référence:

-
Host *
-    User rix
-    IdentityFile ~/.ssh/ed_25519.key
-    IdentityFile ~/.ssh/id_rsa.key
-    IdentitiesOnly yes
-    ForwardAgent yes
-

Pourquoi utiliser un fichier de configuration ?

-

Comme abordé dans l'introduction, il existe autant de manières de se connecter à un serveur avec SSH qu'il n'en existe (de serveurs), les principaux bénéfices que l'on peut retirer de cette utilsation:

-
    -
  • Ajouter de la cohérence dans votre façon de vous connecter à vos différentes machines ( et on aime ça la cohérence ! ) il est en effet possible de créer une configuration spécifique à un serveur ou alors propre à différentes machines partageant les mêmes spécifités de connexion.
  • -
  • Faciliter les connexions multiples, en créant des configurations aux contextes différents. Il est par exemple possible d'avoir une configuration utilisant une clé différente pour un groupe de machines données.
  • -
-

Le fichier de configuration

-

En « standard » vous trouverez un dossier appelé .ssh dans le répertoire utilisateur de votre système (/home/username/ sur un système de type UNIX ou C:\Users\username\sous Windows). C'est ici que nous allons créé un fichier appelé tout simplement config (sans extension).

-
-

Configuration globale

-

- Il est possible d'appliquer un comportement global au client SSH (C'est à dire pour tous les utilisateurs du système) en utilisant le fichier /etc/ssh/ssh_config. -

-
-

Si le répertoire .ssh n'existe pas (il est créé automatiquement lorsque vous créez une nouvelle clé par exemple) vous pouvez le créer comme suit:

-
mkdir -p ~/.ssh && chmod 0700 ~/.ssh
-

Pour ensuite créer le fichier config

-
touch ~/.ssh/config && chmod 0600 ~/.ssh/config
-
-

Les droits

-

- Attention SSH est très sensible (à juste titre) aux droits appliqués aux fichiers qu'il doit utiliser. - Le répertoire .ssh tout comme le fichier config ne doit être accessible, lisible et modifiable qu'à l'utilisateur propriétaire. -

-
-

Les instructions de configuration

-

Le fichier config est basé sur un système de paires clé/valeur organisées par section, une structure minimal d'un fichier de configuration pourrait être la suivante:

-
Host server-hostname-1
-    KEY value
-    KEY value
-

Allons plus loin avec une configuration:

-
Host server-hostname-1
-    HostName server.tld
-    User rix
-    IdentityFile ~/.ssh/ed_25519.key
-

La directive Host permet d'indiquer à la fois une nouvelle section mais également le « pattern » qui permettra au client de savoir quand appliquer la configuration. -Dans ce premier exemple c'est assez simple et spécifique puisque notre bloc s'appliquera à la chaine server-hostname-1

-

Ainsi lorsque nous taperons ssh server-hostname-1 notre client saura à quel hote se connecter, avec quel utilisateur et quelle clé. -L'équivalent sans fichier de configuration serait ssh -i ~/.ssh/ed_25519.key rix@server.tld

-

L'utilisation d'un fichier de configuration prend tout son sens lorsque l'on souhaite appliquer des comportements spécifiques à un ensemble de machines.

-

Il est ainsi possible d'utiliser des « pattern » (et de les enchaîner) en utilisant des opérateurs:

-
    -
  • Host * par exemple s'appliquera à tous les hôtes puisque le caractère * correspond à aucun ou plusieurs caractères.
  • -
  • 192.168.140.* il est possible de composer, dans ce cas vous appliquerez votre configuration à l'ensemble des hôtes dont l'adresse fait partie du sous réseau 192.168.140.0/24
  • -
  • ? permet de restreindre une expression à un seul caractère. Ainsi Host 172.16.1.? correspondra à tous les hôtes ayant en dernier octet un chiffre compris entre 0 et 9.
  • -
  • ! En début de chaîne permet d'exclure une correspondance. Ainsi 172.16.1.* !172.16.1.20 s'appliquera à tous les hôtes du sous réseau 172.16.1.0/24 à l'exception de 172.16.1.20.
  • -
-
-

Organisation et structure du fichier

-

- Le fichier de configuration SSH n'impose pas d'indentation, il est toutefois fortement recommandé de l'organiser afin de faciliter sa lecture et sa maintenance. - Il faut également noter que les instructions sont appliquées dans leur ordre d'apparition il est donc préférable de commencer par les sections très spécifiques et de terminer par les plus génériques. -

-
-

Priorité des instructions

-

Il est possible en fonction d'où elles sont indiquées de surcharger certaines options de connexion, ainsi votre client SSH considérera par ordre de priorité:

-
    -
  • Les options spécifiées dans la ligne de commande
  • -
  • Les options définies dans le fichier config du compte utilisateur (~/.ssh/config)
  • -
  • Les options définies dans le fichier générique /etc/ssh/ssh_config
  • -
-

Ainsi en reprendant notre section précédente:

-
Host server-hostname-1
-    HostName server.tld
-    User rix
-    IdentityFile ~/.ssh/ed_25519.key
-

Il est possible de vous connecter avec un autre utilisateur que celui défini dans votre fichier en surchargeant la clé User, soit avec ssh root@server-hostname-1 ou encore ssh -o "User=root" server-hostname-1.

-

Séparer et inclure ses fichiers de configuration

-

Il est également possible, notamment lorsque le contenu des fichiers devient conséquent ou tout simplement pour organiser ses configurations entre différents contextes (clients, perso, pro...), de séparer la configuration dans plusieurs fichiers.

-

Il est ainsi possible d'avoir autant de fichiers de configuration que de contextes pour ensuite les inclure dans notre fichier principal.

-

Exemple:

-
# Contenu de ~/.ssh/config
-Host server-hostname-1
-    HostName server.tld
-    User rix
-    IdentityFile ~/.ssh/ed_25519.key
-
-Include ~/.ssh/config_alternative
-
# Contenu de ~/.ssh/config_alternative
-Host 192.168.140.*
-    User debian
-    IdentityFile ~/.ssh/id_rsa.key
-    AddKeysToAgent yes
-    UseKeychain yes
-

Les instructions les plus courantes / utiles

-

Le fichier config supporte nombre d'options de configuration, celles-ci sont consultables ici. -Il va de soi que dans l'activité quotidienne, les mêmes instructions sont souvent utilisées, ci-dessous une liste des plus courantes:

-
    -
  • IdentityFile: Nous l'avons vu précédemment, elle permet d'indiquer la clé à utiliser pour la section définie;
  • -
  • ForwardAgent: Un grand classique, permet de « faire suivre » comme son nom l'indique au serveur distant, votre clé privée de manière à ce que celui-ci la stocke dans son propre agent SSH. Cette option permet ensuite de se connecter à d'autres serveurs « par rebond », c'est ce principe qui est notamment mis en oeuvre par les « bastions » SSH;
  • -
  • IdentitiesOnly: Important si vous utilisez l'option précédente, permet de ne transmettre que la ou les clé(s) spécifiée(s) avec l'option IdentityFile précédente, par défaut SSH envoie toutes les clés privées qu'il trouvera dans votre trousseau;
  • -
  • StrictHostKeyChecking: Permet de vérifier la signature du serveur auquel on se connecte, toujours à Yes sauf dans le cas de figure que l'on présente plus bas.
  • -
-
-

Désactiver StrictHostKeyChecking

-

- Précaution d'usage, à ne faire que si vous savez réellement ce que vous faites ;) - Le seul exemple qui me vient à l'esprit pouvant nécessiter de désactiver cette option est celui de séances de cours / TP durant lesquelles nous avons souvent besoin de créer / détruire des instances qui peuvent potentillement récupérer les mêmes adresses IPs. -

-
-

Pour éviter d'avoir régulièrement l'erreur, WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! il est possible de spécifier:

-
StrictHostKeyChecking no
-UserKnownHostsFile /dev/null
-

Multiplexer ses connexions SSH

-

Le principe du multiplexage réside dans le fait de « partager » la même connexion entre les différentes sessions ouvertes sur une même machine. -Cette stratégie permet, dans le cas d'SSH de réutiliser une connexion TCP déjà ouverte vers un serveur distant.

-

Le but ? S'épargner le délai d'ouverture d'une connexion TCP ainsi que celui de la réauthentification. -Cette fonctionnalité peut-être particulièrement utile lors du transfert de nombreux fichiers d'une machine à une autre, elle réside principalement en l'utilisation de 3 options de configuration:

-
    -
  • ControlMaster: Permet d'indiquer à SSH la stratégie à adopter lorsqu'il détecte une possibilité de réutilisation d'une connexion ouverte (Fixée à no par défaut);
  • -
  • ControlPersist: Permet d'indiquer comment SSH doit gérer la fermeture de la connexion initiale à la machine distante (celle qui a entrainée l'ouverture de la socket partagée);
  • -
  • ControlPath: Le chemin vers la socket utilisée pour le partage de connexion, cette option supporte les tokens %h %p et %r dont la combinaison est très fortement recommandée. Plus d'informations sur les tokens supportés par SSH (https://man.openbsd.org/ssh_config#TOKENS).
  • -
-

Exemple de fichiers de configuration avec multiplexage

-
Host server-hostname-1
-    HostName server.tld
-    User rix
-    IdentityFile ~/.ssh/ed_25519.key
-    ControlPath ~/.ssh/controlmasters/%C
-    ControlMaster auto
-    ControlPersist 10m
-
-

Le token %C

-

- On remarquera son utilisation dans l'exemple ci-dessus. Il s'agit du hash SHA1 des tokens %l%h%p%r (Respectivement le nom d'hôte local (%l), le nom d'hôte distant (%h), le port de connexion distant (%p) et pour finir le nom d'utilisateur distant utilisé (%r)). - L'utilisation du token %C assurant à la fois, l'unicité de la connexion et l'obfuscation de ses détails sur le système de fichiers. -

-
-

Aller plus loin avec les sources

- -
- Crédits: photo de couverture par - - Mohammad Rahmani - -
-
- -
-
-

- Une typo ? - Modifier cet article sur Github -

-
-
-
-
-
- - - - - - diff --git a/pr/137/blog/index.html b/pr/137/blog/index.html deleted file mode 100644 index d19769d1..00000000 --- a/pr/137/blog/index.html +++ /dev/null @@ -1,788 +0,0 @@ - - - - - - - Le blog de l'équipe Rix - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - Retour à l'accueil - - - - - - - - -
-
-
- - - -
-
-
-
- - - -
-
-
-
- - - - - - diff --git a/pr/137/blog/linux/construire-image-debian-raspberry/index.html b/pr/137/blog/linux/construire-image-debian-raspberry/index.html deleted file mode 100644 index fbb0648d..00000000 --- a/pr/137/blog/linux/construire-image-debian-raspberry/index.html +++ /dev/null @@ -1,580 +0,0 @@ - - - - - - - Construire une image Debian pour Raspberry Pi - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - Retour à l'accueil - - - - - - - - -
-
-
- - -
-
-
-
-
    -
  • - #Devops -
  • -
  • - #Linux -
  • -
  • - #Build -
  • -
  • - #Raspberry -
  • -
-

Construire une image Debian pour Raspberry Pi

-

Construire une image source Debian à déployer sur Raspberry Pi en remplacement de Raspberry Pi OS (anciennement Raspbian).

-
-
    -
  1. - TL;DR -
  2. -
  3. - Mise en contexte -
  4. -
  5. - Pourquoi reconstruire et pourquoi Debian ? -
  6. -
  7. - Pré-requis -
  8. -
  9. - Let's go ! -
      -
    1. - Configuration de l'image -
    2. -
    3. - Construction de l'image -
    4. -
    5. - Installation de l'image -
    6. -
    -
  10. -
  11. - Pour aller plus loin -
  12. -
- -
-
-

Je vous vois venir ! Quelle idée de vouloir construire sa propre image ? -Alors même que Raspberry fourni un OS ET un utilitaire permettant de créer des cartes SD « bootables » rapidement et quasi sans douleur !

-

TL;DR

-

Pour les opérations à suivre utilisez une Debian Bulleyes (de préférence VM)

-
    -
  1. Récupérer le dépôt avec les outils pour construire l'image; -
    git clone --recursive https://salsa.debian.org/raspi-team/image-specs.git
    -cd image-specs
  2. -
  3. Installer les différents paquets nécessaires à l'opération; -
    apt install -y vmdb2 dosfstools qemu-utils qemu-user-static debootstrap binfmt-support time kpartx bmap-tools python3
    -apt install -y fakemachine
  4. -
  5. Construire l'image (Pour l'exemple à destination d'un Raspberry Pi 4): make raspi_4_bullseye.img;
  6. -
  7. Écrire cette image sur une carte SD: dd if=raspi_4_bullseye.img of=/dev/XXX bs=64k oflag=dsync status=progress.
  8. -
-

Mise en contexte

-

Oui mais…

-

Avant d'aller plus loin il faut savoir que nous utilisons en interne plusieurs Raspberry Pi afin de faire tourner des services qui nous permettent de piloter la gestion du réseau local de nos bureaux et/ou d'y fournir des services basiques et non critiques (Serveurs DNS locaux, VPN, métriques, monitoring…).

-

Nous avons également une petite flotte de ces machines qui nous permettent d'aller enseigner « quelques trucs » à des étudiants (du réseau, du provisioning, linux…) en se reposant dessus.

-

Alors bien évidemment le besoin ne tombe pas du ciel, et même si l'exercice reste formateur et amusant (si, si) le but n'est bien évidemment pas « juste ludique ». -Raspberry Pi OS a en fait ses limites, particulièrement lorsqu'on va l'utiliser sur d'anciens Raspberry Pi (notamment des 2 ou précédents).

-

Pour terminer il faut savoir que Raspbian (dérivé de Debian) a été créé en premier lieu parce que jusqu'à 2018 il n'était pas possible de démarrer un kernel linux sur Raspberry Pi mais également parce que Raspbian arrivait avec des composants non libres contraires à la philosophie Debian.

-

Pourquoi reconstruire et pourquoi Debian ?

-
    -
  • En tout premier lieu parce que nous avons besoin d'avoir accès facilement à certains paquets non disponibles sur Raspberry Pi OS dans les versions dont nous avons besoin;
  • -
  • En second lieu parce que l'intégration de dépôts externes sur une base Raspberry Pi OS est parfois assez chaotique, notamment dû aux différentes architectures utilisées par les processeurs ARM des Raspberry Pi et de leur disponibilité (ou pas) dans ces mêmes dépôts.
  • -
-

On se retrouve notamment par défaut (il est possible d'aller chercher d'autres versions d'OS dans l'utilitaire de création d'images) avec une version de Raspberry Pi OS commune à toutes les versions et donc 32 bits

-

Pour rappel les premières architectures 64 bits des Raspberry Pi sont arrivées avec la version 3. -Debian fait le choix de coller exactement aux différentes archi, mais au prix d'une image pour chacune, celles-ci sont d'ailleurs disponibles ici.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Version Raspberry PiDebianRaspberry Pi OS
0 / 1armel, 32 bitarmhf, 32 bit
2armhf, 32 bitarmhf, 32 bit
3arm64, 64 bitarmhf, 32 bit
4arm64, 64 bitarmhf, 32 bit
-

Il existe d'autres versions d'OS disponibles en version serveur ou desktop (Ubuntu, Manjaro…) mais comme nous utilisons les roles Ansible du projet Manala pour provisionner l'ensemble de nos machines et afin d'assurer au maximum la compatibilité (et nous éviter de la maintenance), nous préferons rester cohérents et utiliser Debian.

-

Pour terminer, les images pré-construites Debian peuvent carrément faire l'affaire mais certains choix de configuration ne nous vont pas et nous avons besoin que nos images soient prêtes à être « provisionnées » et donc disposer:

-
    -
  • de certains paquets;
  • -
  • de comptes utilisateurs spécifiques;
  • -
  • et bien évidemment de nos clés SSH pour l'authentification.
  • -
-

Pré-requis

-

Ils sont peu nombreux, tant la construction d'images est rendue très simple par les contributeurs à la version Raspberry Pi de Debian.

-

Vous aurez besoin:

- -

Let's go !

-

En tout premier lieu, une fois sur votre VM Debian Bullseye, on clone le répertoire d'outils:

-
git clone --recursive https://salsa.debian.org/raspi-team/image-specs.git
-cd image-specs
-

Nous aurons également besoin de quelques paquets pour la construction:

-
apt install -y vmdb2 dosfstools qemu-utils qemu-user-static debootstrap binfmt-support time kpartx bmap-tools python3
-apt install -y fakemachine
-

Le fichier makefile fournit vous permet ensuite, au choix:

-
    -
  • -

    De construire une image spécifique à votre Raspberry Pi en utilisant une configuration par défaut:

    -

    make raspi_<model>_<release>.img

    -
      -
    • <model> vaut pour la version de votre Raspberry (1,2,3 ou 4 avec 1 utilisé pour les versions Pi 0, 0w and 1);
    • -
    • <release> pour la version de Debian que vous souhaitez construire.
    • -
    -

    Dans notre cas une Bullseye pour un Raspberry Pi 2: make raspi_2_bullseye.img

    -
  • -
  • -

    De construire une image spécifique à partir de VOTRE configuration (et c'est ce choix qui nous intéresse)

    -
  • -
-

Configuration de l'image

-

Alors bien évidemment nous ne partirons pas d'une page blanche mais du fichier de configuration généré par l'outil grâce à la commande:

-

make raspi_2_bullseye.yaml

-

Nous obtenons un fichier de configuration par défaut (celui utilisé pour la construction de l'image avec la commande précédente). -Il est possible de l'éditer directement mais préférable de le nommer différemment histoire de ne pas malencontreusement écraser nos prochaines modifications en rejouant la commande précédente.

-

cp raspi_2_bullseye.yaml rix_raspi_2_bullseye.yaml

-

En ce qui nous concerne nous y opérerons plusieurs modifications:

-
    -
  • L'ajout d'un compte utilisateur non privilégié:
  • -
-
- chroot: tag-root
-    shell: |
-      useradd -g100 -Gsudo -m -s /bin/bash rix
-
    -
  • L'ajout des clés publiques SSH
  • -
-
- create-dir: /home/rix/.ssh
-    uid: 1000
-    gid: 100
-
- create-file: /home/rix/.ssh/authorized_keys
-    contents: |+
-      ssh-ed25519 AAAAC3XXXXXXXXXXXXXXXXXXXX
-
    -
  • L'ajout des droits sudo à notre utilisateur
  • -
-
- create-file: /etc/sudoers.d/rix-default
-    contents: |+
-      rix ALL=(ALL) NOPASSWD: ALL
-
    -
  • La suppression de la connexion root locale sans mot de passe.
  • -
-
- # Allow root logins locally with no password
-- sed -i 's,root:[^:]*:,root::,' "${ROOT?}/etc/shadow"
-
-

La directive chroot

-

- Elle est essentielle dès lors que vous souhaitez exécuter une commande dans le contexte du système en cours de construction. -

-
-

Construction de l'image

-

Une fois l'ensemble de nos instructions ajoutées à notre fichier nous pouvons démarrer la construction de notre image:

-
vmdb2 --rootfs-tarball=rix_raspi_2_bullseye.tar.gz --output \
-rix_raspi_2_bullseye.img rix_raspi_2_bullseye.yaml --log rix_raspi_bullseye.log
-

Cette commande devrait nous donner en sortie 3 fichiers:

-
    -
  • Un fichier de logs rix_raspi_bullseye.log
  • -
  • Un fichier image rix_raspi_2_bullseye.img
  • -
  • Un fichier image compressé rix_raspi_2_bullseye.tar.gz
  • -
-

Installation de l'image

-

L'installation de l'image est ensuite assez standard, une fois votre carte SD montée:

-
    -
  • Soit via bmaptool à partir de l'image compressée;
  • -
-

bmaptool copy rix_raspi_2_bullseye.tar.gz /dev/mmcblk0

-
    -
  • Soit via un bon vieux dd:
  • -
-

dd if=rix_raspi_2_bullseye.img of=/dev/XXX bs=64k oflag=dsync status=progress

-
-

- Attention à la « cible » de l'écriture, en cas de mauvais volume sélectionné vous écraserez son contenu. -

-
-

Vous voilà paré·e·s pour déployer du Raspberry Pi en série sur une base Debian !

-

Pour aller plus loin

-

Sources:

- -
- Crédits: photo de couverture par - - Jainath Ponnala - -
-
- -
-
-

- Une typo ? - Modifier cet article sur Github -

-
-
-
-
-
- - - - - - diff --git a/pr/137/blog/linux/integrer-icloud-gnome-calendar/index.html b/pr/137/blog/linux/integrer-icloud-gnome-calendar/index.html deleted file mode 100644 index 40106e2f..00000000 --- a/pr/137/blog/linux/integrer-icloud-gnome-calendar/index.html +++ /dev/null @@ -1,419 +0,0 @@ - - - - - - - Intégrer des agendas iCloud à Gnome Calendar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - Retour à l'accueil - - - - - - - - -
-
-
- - -
-
-
-
-
    -
  • - #Linux -
  • -
  • - #Icloud -
  • -
  • - #Calendar -
  • -
  • - #Gnome -
  • -
-

Intégrer des agendas iCloud à Gnome Calendar

-

Intégrer (relativement) facilement des agendas iCloud à Gnome Calendar

-
- -
-
-

Contrairement à ce que certains pensent on peut être utilisateur d'un iPhone (ou d'un iPad) sans avoir de mac.

-

Et oui c'est mon cas d'ailleurs ;) -Autant je ne changerais pas mon iPhone contre un Android (même si j'ai aussi un XPeria avec SailfishOS) autant je n'échangerais pas plus mon Linux contre un OSX.

-

Oui mais… bien qu'il devrait être très facile de synchroniser ses agendas iCloud avec Gnome (étant donné qu'iCloud propose un format CalDAV), la réalité n'est pas tout aussi simple !

-

Petit guide pour épargner les arrachages de cheveux pour y parvenir.

-

Les pré-requis

-
    -
  • Un compte iCloud, et oui…
  • -
  • Avoir créé un mot de passe d'application spécifique à votre client à partir de votre AppleID (C'est tout bien expliqué ici)
  • -
  • Installer Evolution sur votre machine (pas de panique c'est temporaire 😉).
  • -
-

Configurer vos agendas dans Evolution

-
-
-

En tout premier lieu, ouvrez l'onglet « Agenda » (vous serez pas défaut sur la vue « courriel ») une fois sur la partie agenda, créer en un nouveau comme ci-contre.

-
-
- Créer un nouvel agenda dans Evolution. -
-
-
- Informations de connexion à iCloud. -
-

Renseignez ensuite vos informations et suivez les étapes ci-contre.

-

-

    -
  • Sélectionnez le type d'agenda: CalDAV;
  • -
  • URL: https://caldav.icloud.com (Elle sera par la suite remplacée par l'adresse de l'agenda sélectionné);
  • -
  • Dans le champs utilisateur, votre Apple ID (En général votre adresse de courriel);
  • -
  • Appuyez enfin sur « Rechercher les agendas » vous devriez avoir la liste des agendas dont vous disposez sur iCloud, sélectionnez l'agenda que vous voulez synchroniser;
  • -
  • Vous pouvez ensuite sélectionner la couleur de votre agenda (ou conserver celle par défaut).
  • -
- -
-
-

Pour terminer, libre à vous d'activer les options afin de rendre vos agendas disponibles hors ligne et de sélectionner votre agenda par défaut.

-

ATTENTION les étapes ci-dessus devront être répétées pour CHACUN des agendas dont vous disposez, je n'ai pas trouvé mieux pour l'instant. -Une fois vos agendas synchronisés vous pouvez fermer Evolution, l'agenda de Gnome devrait automatiquement ajouter les agendas que vous venez de configurer.

-
- Crédits: photo de couverture par - - Estée Janssens - -
-
- -
-
-

- Une typo ? - Modifier cet article sur Github -

-
-
-
-
-
- - - - - - diff --git a/pr/137/blog/linux/osx-to-linux-part-1/index.html b/pr/137/blog/linux/osx-to-linux-part-1/index.html deleted file mode 100644 index 70a2b9d1..00000000 --- a/pr/137/blog/linux/osx-to-linux-part-1/index.html +++ /dev/null @@ -1,459 +0,0 @@ - - - - - - - D'OSX à Linux (en milieu professionnel) - Partie 1 - Le quotidien - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - Retour à l'accueil - - - - - - - - -
-
-
- - -
-
-
-
-
    -
  • - #Linux -
  • -
  • - #Osx -
  • -
-

D'OSX à Linux (en milieu professionnel) - Partie 1 - Le quotidien

-

Passer d'OSX à Linux en milieu professionnel, le quotidien, équivalence d'applications, fonctionnement, astuces.

-
-
    -
  1. - Introduction -
  2. -
  3. - Les clients de courriels -
  4. -
  5. - Les messageries (perso) -
  6. -
  7. - Les navigateurs -
  8. -
  9. - La bureautique -
      -
    1. - Documents et tableurs -
    2. -
    3. - PDF -
    4. -
    -
  10. -
  11. - Les agendas -
  12. -
- -
-
-

Parce que le choix (ou la nécessité) de changer de système d'exploitation remet en cause notre utilisation quotidienne et nos réflèxes applicatifs, il me parait louable de partager MA façon d'utiliser Linux après de nombreuses années avec OSX et quelles solutions j'ai « trouvé » pour reprendre mes marques notamment en terme d'équivalence d'applications et d'utilisation au quotidien.

-

Billet à destination de celles et ceux qui hésitent encore à franchir le pas de peur de se retrouver perdu·e.

-

Introduction

-

En préambule et pour mieux cerner certaines contraintes que j'ai pu avoir ou choix que j'ai pu faire:

-
    -
  • J'utilise Linux à titre pro et perso depuis plus de 20 ans (J'ai commencé avec Debian/Potato);
  • -
  • Je ne prêche pour aucune distribution en particulier, sauf côté serveur, mais ça n'est pas le sujet; -
    Il existe aujourd'hui suffisamment de distributions « matures » pour que chacun y trouve son bonheur, il n'y a pas de « meilleure » distro il y a celle qui vous convient. En ce qui me concerne j'ai eu de la Debian (beaucoup), de la Fedora et de l'Ubuntu (un peu pour les deux) et ça fait maintenant 3 ans que j'utilise Manjaro après un rapide passage sur Antergos;
  • -
  • J'ai passé pas loin de 10 ans avec OSX coté pro avec ses bons et ses mauvais côtés et même si je ne m'y retrouve plus aujourd'hui, c'est toujours à mon sens un bon OS;
  • -
  • J'utilise Gnome comme environnement graphique;
  • -
  • Je dispose de plusieurs adresses de messagerie dont pas mal sont gérées par ProtonMail;
  • -
  • Mon quotidien est celui d'un SysAdmin/Ops/SRE (Je ne sais plus où on en est dans les appellations) bref tout ce qui va toucher à l'administration système et à l'outillage autour.
  • -
-

Je séparerai cet article en plusieurs parties en fonction des différentes utilisations:

-
    -
  • Le quotidien (qui pourra intéresser tout un chacun);
  • -
  • Le spécifique (qui relève de mon utilisation);
  • -
  • Le pro (très focalisé sur le métier).
  • -
  • Le cosmétique
  • -
-

Sans plus attendre un premier chapitre qui va se concentrer sur l'utilisation que chacun d'entre nous peut avoir d'une machine.

-

Les clients de courriels

-

L'une des premières utilisation, les courriels !
-J'utilise uniquement les clients de courriel et quasiment jamais un navigateur pour consulter ma messagerie. Avec OSX j'utilisais l'excellent Postbox.

-

Du côté de linux j'ai retenu deux candidats:

-
    -
  • L'incontournable Thunderbird qui reste, fonctionnellement et de très loin le plus complet. Son ergonomie est toutefois très datée et on ne va pas se le cacher, l'organisation de ses menus est tout de même un sacré bordel ! À son crédit c'est un des rares à supporter GPG nativement.
  • -
  • Celui que j'utilise depuis quelques temps, Mailspring qui dispose d'une interface beaucoup plus simple et accessible et fonctionne très bien avec le bridge de chez ProtonMail.
  • -
-
- L'interface de Mailspring -
- L'interface de Mailspring -
-
-

À noter que j'attends la refonte annoncée de Thunderbird avec impatience tant le fonctionnel de Mailspring reste « relativement » limité.
-(Mise à jour du 18 Novembre 2022: Quelques écrans de la nouvelle interface ont fait leur apparition, ils semblent augurer d'un gros et bon travail d'UI.)

-

Les messageries (perso)

-

Au niveau des messageries j'utilise principalement Signal et Whatsapp (pour les proches), les deux disposent d'intégration des clients comme je l'avais sous OSX rien à signaler à ce niveau on est ISO !

-

Les navigateurs

-

Probablement la brique la plus standardisée à l'heure actuelle, on retrouve exactement la même chose côté Linux c'est donc plus l'affinité de chacun qui va jouer. -De mon côté j'en utilise deux principalement Brave (sur une base chromium donc) et Librewolf (sur une base Firefox).

-

À ces derniers vient se greffer TorBrowser.

-

La bureautique

-

Documents et tableurs

-

Ah ! La bureautique…

-

Point de friction lié principalement à la comptabilité avec les formats propriétaires, je n'ai plus rencontré de « vrai » problème depuis un moment. -J'utilise OnlyOffice Desktop pour l'édition de document de type Word et Excel et ça juste marche !

-

Point plus embêtant si vous avez des imprimantes réseau un peu anciennes il est probable qu'il vous soit compliqué d'imprimer sans faire un effort de configuration (je ne l'ai pas fait, je n'imprime quasiment jamais).

-

PDF

-

Du côté des PDFs, le visionneur de documents de Gnome fait très bien le boulot pour de la lecture.
-Au niveau de l'édition c'est un domaine ou il faut encore bricoler par rapport à OSX qui est très bon nativement pour manipuler les PDFs. -J'utilise donc Xournal++ dès lors que je dois y insérer des images ou autres éléments mais il s'agit d'un projet qui semble être à l'abandon et pour lequel je n'ai pas encore cherché/trouvé d'alternative viable.

-
- La visionneuse de Gnome -
- La visionneuse de Gnome -
-
-

Les agendas

-

Là encore l'offre est plétorique, après avoir pas mal utilisé Lightning (Thunderbird) je suis revenu sur Gnome Calendar qui fait le boulot.

-

ATTENTION toutefois il n'intègre pas la mécanique d'invitations, si vous planifiez beaucoup de réunions il vaut sans doute mieux rester sur Lightning.

-

Pour ceux qui disposent d'agendas iCloud (j'en fais partie) leur ajout à Lightning passe par l'utilisation de l'extension TBSync quant à Gnome Calendar il faut passer par une gymnastique assez lourde que je décris ici.

-
- -
-
-

- Une typo ? - Modifier cet article sur Github -

-
-
-
-
-
- - - - - - diff --git a/pr/137/blog/page/2/index.html b/pr/137/blog/page/2/index.html deleted file mode 100644 index cfe18da7..00000000 --- a/pr/137/blog/page/2/index.html +++ /dev/null @@ -1,481 +0,0 @@ - - - - - - - Le blog de l'équipe Rix - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - Retour à l'accueil - - - - - - - - -
-
-
- - - -
- -
-
- - - - - - diff --git a/pr/137/blog/post-mortem/sre-interpretation-incident/index.html b/pr/137/blog/post-mortem/sre-interpretation-incident/index.html deleted file mode 100644 index 1ba6c38b..00000000 --- a/pr/137/blog/post-mortem/sre-interpretation-incident/index.html +++ /dev/null @@ -1,545 +0,0 @@ - - - - - - - Interprétation et diagnostic d'un incident en production. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - Retour à l'accueil - - - - - - - - -
-
-
- - -
-
-
-
-
    -
  • - #Sre -
  • -
  • - #Devops -
  • -
  • - #Incident -
  • -
  • - #PostMortem -
  • -
-

Interprétation et diagnostic d'un incident en production.

-

Interprétation pas à pas d'un incident sur infrastructures applicatives, cas pratique d'un incident vécu.

-
-
    -
  1. - Préambule -
  2. -
  3. - Contexte -
  4. -
  5. - Déroulé de l'incident -
      -
    1. - Diagnostic infra -
    2. -
    3. - Diagnostic applicatif -
    4. -
    5. - Analyse des métriques -
    6. -
    7. - Analyse des requêtes SQL -
    8. -
    9. - Le fin mot de l'histoire -
    10. -
    -
  6. -
  7. - Conclusion -
  8. -
  9. - Ce qu'il faut en retenir -
  10. -
- -
-
-

Préambule

-

Premier article concernant la résolution d'incidents, toujours à but de formation. -N'ayant jamais vu beaucoup d'articles traitant du sujet il nous parait intéressant d'expliquer et commenter les post-mortems qu'il nous arrive d'envoyer à nos clients. -C'est aussi, à notre avis, une bonne façon (si ce n'est la meilleure) d'expliquer pourquoi le DevOps est important dans les métiers SRE, à quoi il sert et comment nous utilisons les outils que l'on met en place.

-

Gardez à l'esprit que chaque applicatif est unique (même si l'on retrouve des comportements type) et que l'un des meilleurs indices d'anomalie et « l'écart par rapport à la moyenne normale », autrement dit un comportement qui diffère fortement de ce que vous avez l'habitude de voir.

-

Pour finir, c'est l'exercice le moins « confortable » du métier. -En fonction de sa gravité, l'incident infra peut passer inaperçu comme impacter de manière significative l'activité d'un client, l'expérience des équipes dans ce cas de figure est souvent un facteur clé pour la rapidité de la résolution.

-

Contexte

-

Le contexte est assez classique pour de l'application web.

-
    -
  • Un applicatif métier « relativement » pas mal utilisé en journée recevant environ 240 requêtes/s (Réparties à 80% sur l'API, les 20% restant étant dédié à du back-office);
  • -
  • 6 frontaux web (Nginx / PHP-FPM);
  • -
  • 1 répartiteur de charge managé;
  • -
  • 1 répartiteur de charge SQL MaxScale;
  • -
  • 2 instances MariaDB en configuration « Primary / Replica ».
  • -
-

En complément:

-
    -
  • L'API répond principalement à une application mobile;
  • -
  • Le back-office déclenche l'envoi de notifications (~ 800 000/jour) vers ces mêmes applications.
  • -
-

Déroulé de l'incident

-
    -
  • Durée totale de l'incident: 1h20
  • -
  • Impact: Significatif
  • -
  • Type: Indisponibilité complète
  • -
-

Comme tous les incidents, nous sommes alertés par nos sondes infra:

-
-
-

Il n'est pas rare d'avoir des alertes isolées dues à une défaillance d'un hyperviseur ou à une panne réseau, dans notre cas de figure l'effondrement de l'infrastructure est très rapide (inférieure à 3 minutes), aucun doute donc sur « un gros pépin ».

-

Les alertes infras se terminent par une alerte StatusCake sur le « endpoint » applicatif HTTP configuré.

-
-
- Déclenchement des alertes. -
-
-

Nous notifions à notre client le début de l'incident et le début d'analyse pour remédiation.

-

Diagnostic infra

-

Elles sont systématiques en cas d'incident et consistent en une checklist assez simple:

-
    -
  • Vérification de l'état des instances;
  • -
  • Vérification d'un incident déclaré chez le fournisseur de cloud (ici OVHCloud);
  • -
  • Vérification de la connectivité des réseaux privés.
  • -
-

En seconde étape nous procédons à une première analyse des « graphs » sur quelques unes des instances applicatives:

-
- -
- Effondrement des processus PHP -
-
-

Pour autant les instances n'apparaissent pas « chargées » outre mesure.

-
- -

Sur l'ensemble des instances nous ne constatons pas d'augmentation de la consommation des ressources, de même le traffic n'explose pas, nous ne sommes donc pas dans le cas d'un afflux massif de connexions (légitimes ou non d'ailleurs), ni d'une charge anormale du à un script mal conçu.

-
-
- -

Nous disposons toutefois d'un premier indice au niveau réseau, puisque nous constatons une augmentation des connexions « ouvertes » et une chute flagrante des connexions en attente de fermeture.

-
-

Nous avons également constaté que les médias applicatifs sont bien disponibles et que nous n'avons pas de coupure vers les espaces de stockage distants.

-

À ce stade nous avons donc une infra fonctionnelle mais une application « dans les choux ». -Nous tentons une première approche en redémarrant les services PHP-FPM sur l'ensemble des instances, ceux-ci se retrouvent rapidement à nouveau saturés et hors service, comportement qui semble confirmer un souci d'ordre applicatif.

-

Diagnostic applicatif

-

Il est temps d'aller jeter un oeil aux logs applicatifs qui donnent rapidement un résultat puisque nous avons l'erreur suivante:

-
SQLSTATE[HY000]: General error: 2006 MySQL server has gone away at XXXXXXX
-

Nous avons donc bien une perturbation au niveau de la connectivité entre les instances applicatives et les instances de base de données, reste à déterminer la raison !

-

À ce stade nous prenons contact avec l'équipe de développement applicatif afin de disposer de sa connaissance métier et fonctionnelle.

-

Analyse des métriques

-

Nous poursuivons nos investigations à l'aide de nos métriques infra / applicatif plus spécifiquement sur le réseau privé, zone d'échange entre les instances applives et données pour y trouver ceci:

-
- -
- 1.- Bande passante - Instance applicative -
-
-
- -
- 2.- Bande passante - Instance base de données -
-
-
- -
- 3.- Bande passante - Instance base de données (Saturation de la bande passante) -
-
-

Enfin du concret ! On constate à l'aide de ces deux graphs une volumétrie de données d'échange anormalement élevée entre les instances applicatives et les instances de base de données.

-

Constat:

-
    -
  • En entrée des instances applicatives (1) nous avons un débit x20 qui passe de 2~2,5MB/s en moyenne à 45MB/s pour ensuite se stabiliser à hauteur de 20~25MB/s;
  • -
  • En sortie des instances de base de données (2) un débit moyen qui explose (x7) pour s'installer à hauteur de 150 MB/s.
  • -
-

On constate surtout que cette volumétrie vient saturer la bande passante avec un bel « effet plafond » du graph concerné (3).

-

Nous avons donc la cause de l'incident à savoir, la saturation de la bande passante entre les instances applicatives et les instances de base de données, ce qui conduit à une grosse attente au niveau PHP-FPM qui se retrouve dans l'incapacité de prendre de nouvelles connexions entrantes.

-

Au passage, on voit ici l'importance de « caper » le nombre de connexions maximum que l'on autorise à PHP-FPM, dans le cas ou ce travail n'est pas et/ou mal fait ou si aucun seuil n'est fixé, nous pouvons avoir potentiellement une perte complète de la connectivité à l'instance applicative qui peut impacter de manière significative le temps de rétablissement.

-

Nous avons l'origine du blocage, il nous faut à présent comprendre d'où cela vient, nous passons donc du côté serveur de base de données.

-

Analyse des requêtes SQL

-

Afin d'identifier d'éventuelles requêtes problématiques nous jouons un SHOW PROCESSLIST sur notre serveur de base de données et isolons les requêtes en cours d'exécution depuis plusieurs secondes. -En rejouant l'une de ces requêtes nous constatons qu'elle est anormalement volumineuse (taille supérieure à 5Mo).

-

Plus spécifiquement elle « ramène » un champ d'une table bien précise et après échange avec les équipes applicatives il correspond à l'ajout d'un « relativement nouveau » fonctionnel, relativement parce qu'il a tout de même quelques semaines mais n'a, à priori pas été utilisé tout de suite par les utilisateurs finaux.

-

Le champ en question permet de stocker du contenu libre saisi par l'utilisateur à l'aide d'un éditeur de contenu (À ce moment là, certain(e)s d'entre vous doivent avoir une idée de ce qu'il se passe ;)).

-

Nous décidons de passer la publication concernée en « draft »... ce qui est sans influence directe sur la saturation de la bande passante.

-

Nous élargissons notre champ de recherche, cette fois-ci en recherchant les lignes disposant d'un champ de contenu dont la taille est supérieure au Mo (SELECT id ... WHERE CHAR_LENGTH(content) > 10000), pour finalement identifier une centaine de lignes présentant la même problématique.

-

Vu la gravité de la situation (indisponibilité complète de l'application) et en concertation avec les équipes applicatives nous « dépublions » l'ensemble des contenus problématiques ce qui a pour effet de faire revenir les échanges de données à un seuil normal et de facto rendre à nouveau disponible l'applicatif.

-

Dans le même temps les équipes applicatives auront préparé un « quick fix » désactivant la fonctionnalité, le temps de la retravailler.

-

Le fin mot de l'histoire

-

Il s'avère que l'application proposait un module de publication à ses utilisateurs embarquant un éditeur de contenus riches qui autorisait l'insertion d'images directement dans les contenus HTML (sans passer par un stockage sur le système de fichiers et donc directement en base de données). -Cerise sur le gateau, nous sommes en 2023 et les images « brutes » pèsent souvent plusieurs Mo.

-

Conclusion

-

Ce cas est une parfaite illustration de ce que l'on peut avoir comme incident avec une application web, bien que l'origine soit assez sournoise, en effet il n'est pas déclenché par une mise à jour récente mais par un fonctionnel développé il y a plusieurs semaines, mais mis en avant que récemment. -De plus, en première lecture, il n'y a pas d'incident apparent, ni charge anormale des instances, ni pépin réseau.

-

Les logs applicatifs permettent de s'orienter vers une cause probable de l'indisponibilité mais ne sont pas suffisants pour identifier de manière précise son origine, c'est leur corrélation avec la lecture des métriques qui permet de gagner énormément de temps sur le diagnostic et ainsi cerner le problème.

-

Les équipes applicatives sont un renfort précieux, pour dans un premier temps, fournir les informations de dernières mises à jour ou d'arrivée de nouveau fonctionnel (et surtout de mettre en relation un fonctionnel et un schéma de données) et dans un second temps pour corriger (même temporairement) le code, afin d'éviter une rechute inévitable.

-

Ce qu'il faut en retenir

-
    -
  • Éviter de stocker des médias en base de données qui plus est dans un champ « texte »
  • -
  • Méfiez-vous des éditeurs de contenus riches et prenez le temps de bien les intégrer / configurer. Entre les potentiels dépôts de fichiers sauvages qui peuvent mettre en cause la sécurité applicative, l'injection de scripts et l'ajout de médias non optimisés c'est une source d'ennuis sans fin
  • -
  • Monitorez vos infras !
  • -
  • Intégrer les équipes applicatives au diagnostic
  • -
  • Si vous en avez la possibilité, faites des tirs de charge !
  • -
  • « Profile ! Don't assume.»
  • -
-
- Crédits: photo de couverture par - - ThisisEngineering RAEng - -
-
- -
-
-

- Une typo ? - Modifier cet article sur Github -

-
-
-
-
-
- - - - - - diff --git a/pr/137/blog/rss.xml b/pr/137/blog/rss.xml deleted file mode 100644 index 9dbeb60c..00000000 --- a/pr/137/blog/rss.xml +++ /dev/null @@ -1,145 +0,0 @@ - - - - Le blog de l'équipe Rix - Découvrez nos articles techniques (ou non), rédigés par les membres de l'équipe Rix ! - Rix - L’utilisation des flux RSS de rix.fr est réservée à un usage strictement personnel, non professionnel et non collectif. Toute autre exploitation doit faire l’objet d’une autorisation et donner lieu au versement d’une rémunération. Contact : contact@rix.fr - https://rix-fr.github.io/rix/pr/137/blog - Thu, 14 Mar 2024 12:50:24 +0000 - fr - - https://rix-fr.github.io/rix/pr/137/apple-touch-icon.png - Le blog de l'équipe Rix - https://rix-fr.github.io/rix/pr/137/blog - - - - Ansible - Roles et collections, savoir factoriser. - Mon, 26 Feb 2024 00:00:00 +0000 - - - - https://rix-fr.github.io/rix/pr/137/blog/cours/ansible/ansible-les-roles - https://rix-fr.github.io/rix/pr/137/blog/cours/ansible/ansible-les-roles - - Ansible - Roles et collections, savoir factoriser. - - cours - ansible - automation - playbook - collection - role - - - Ansible - Les variables - Mon, 29 Jan 2024 00:00:00 +0000 - - - - https://rix-fr.github.io/rix/pr/137/blog/cours/ansible/ansible-les-variables - https://rix-fr.github.io/rix/pr/137/blog/cours/ansible/ansible-les-variables - - Ansible - Les variables - - cours - ansible - automation - variables - - - Ansible - Les playbooks - Mon, 29 Jan 2024 00:00:00 +0000 - - - - https://rix-fr.github.io/rix/pr/137/blog/cours/ansible/ansible-les-playbooks - https://rix-fr.github.io/rix/pr/137/blog/cours/ansible/ansible-les-playbooks - - Ansible - Les playbooks - - cours - ansible - automation - playbook - - - Ansible - Les inventaires statiques - Mon, 27 Nov 2023 00:00:00 +0000 - - - - https://rix-fr.github.io/rix/pr/137/blog/cours/ansible/ansible-les-inventaires-statiques - https://rix-fr.github.io/rix/pr/137/blog/cours/ansible/ansible-les-inventaires-statiques - - Ansible - Les inventaires statiques - - cours - ansible - automation - - - Ansible - Découverte et premiers pas. - Thu, 23 Nov 2023 00:00:00 +0000 - - - - https://rix-fr.github.io/rix/pr/137/blog/cours/ansible/ansible-premiers-pas - https://rix-fr.github.io/rix/pr/137/blog/cours/ansible/ansible-premiers-pas - - Ansible - Découverte et premiers pas. - - cours - ansible - automation - - - Ansible - Un environnement de travail clé en main avec Lazy Ansible. - Wed, 22 Nov 2023 00:00:00 +0000 - - - - https://rix-fr.github.io/rix/pr/137/blog/cours/ansible/ansible-environnement-cle-en-main - https://rix-fr.github.io/rix/pr/137/blog/cours/ansible/ansible-environnement-cle-en-main - - Ansible - Un environnement de travail clé en main avec Lazy Ansible. - - cours - ansible - automation - manala - - - Principes de base de l'utilisation de clés SSH. - Fri, 20 Oct 2023 00:00:00 +0000 - - - - https://rix-fr.github.io/rix/pr/137/blog/cours/cle-ssh-principes-de-base - https://rix-fr.github.io/rix/pr/137/blog/cours/cle-ssh-principes-de-base - - Principes de base de l'utilisation de clés SSH. - - cours - ssh - how-to - - - Interprétation et diagnostic d'un incident en production. - Mon, 16 Oct 2023 00:00:00 +0000 - - - - https://rix-fr.github.io/rix/pr/137/blog/post-mortem/sre-interpretation-incident - https://rix-fr.github.io/rix/pr/137/blog/post-mortem/sre-interpretation-incident - - Interprétation et diagnostic d'un incident en production. - ThisisEngineering RAEng, https://unsplash.com/@thisisengineering - - sre - devops - incident - post-mortem - - - diff --git a/pr/137/blog/styleguide/example/index.html b/pr/137/blog/styleguide/example/index.html deleted file mode 100644 index 138bb233..00000000 --- a/pr/137/blog/styleguide/example/index.html +++ /dev/null @@ -1,741 +0,0 @@ - - - - - - - Petit guide de style du blog - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - Retour à l'accueil - - - - - - - - -
-
-
- - -
-
-
-
-
    -
  • - #Tag1 -
  • -
  • - #Tag2 -
  • -
-

Petit guide de style du blog

-

Tour d'horizon de ce qu'on a pour faire de beaux articles. Et quelques bonnes pratiques de rédaction.

-
-
    -
  1. - Style -
      -
    1. - Les titres -
    2. -
    -
  2. -
  3. - h2 laceat quas odio atque molestiae -
      -
    1. - h3 laceat quas odio atque molestiae -
    2. -
    3. - Le sommaire -
    4. -
    5. - Les éléments typographiques -
    6. -
    7. - Les images -
    8. -
    9. - Images alignées à gauche / à droite -
    10. -
    11. - Le code -
    12. -
    13. - Bonus -
    14. -
    -
  4. -
  5. - Quelques règles typographiques -
      -
    1. - Ponctuation -
    2. -
    3. - Unités -
    4. -
    5. - Utiliser les bonnes abréviations -
    6. -
    7. - Faut-il un point à la fin d'une abréviation ? -
    8. -
    9. - Nombres -
    10. -
    11. - Listes -
    12. -
    13. - D'autres petites règles bien utiles -
    14. -
    15. - L'écriture inclusive -
    16. -
    17. - Pour aller plus loin -
    18. -
    -
  6. -
- -
-
-

Style

-

Les titres

-

1 page = 1 titre principal h1.

-

Dans le blog, le h1 est le titre de l'article. Dans le corps de l'article, on commence donc par des h2.

-

h2 laceat quas odio atque molestiae

-

h3 laceat quas odio atque molestiae

-

h4 laceat quas odio atque molestiae

-
h5 laceat quas odio atque molestiae
-
h6 laceat quas odio atque molestiae
-

Le sommaire

-

Le sommaire permet d'afficher les h2 et les h3 présents dans l'article. Selon le besoin, précisez le niveau de titre à faire figurer au sommaire.

-
tableOfContent: true
-# identique à:
-# tableOfContent: 2
-

ou

-
tableOfContent: 3
-

Les éléments typographiques

-

Nous avons des paragraphes, des liens, parfois du code inline.

-
    -
  • des listes de choses
  • -
  • des listes de choses
  • -
  • des listes de choses
  • -
-
-

Nous avons aussi des citations. -- Jane Doe

-
-

Un coup sur deux, on a un style différent de citation, sinon on s'ennuie.

-
-

Quoi ? Un deuxième style de citation ? Eos officia, vel corporis eaque architecto eveniet voluptatibus, ullam impedit excepturi quis quidem sint facere laboriosam harum error esse iusto. Asperiores, placeat. -John Doe

-
-

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod -tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, -quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo -consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse -cillum dolore eu fugiat nulla pariatur.

-
-

Titre

-

- Nous avons des admonition pour les informations à faire ressortir. -

-
-

Excepteur sint occaecat cupidatat non -proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

-
-

Titre

-

- Le même composant dans le style "success". -

-
-

Excepteur sint occaecat cupidatat non -proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

-
-

Titre

-

- Le même composant dans le style "danger". -

-
-

Les images

-

Une image (qui a du sens, ça n'inclut pas les gifs rigolos) a toujours une légende, et si possible on crédite son auteur·ice.

-
- -
- Légende de l'image - Crédit photo : Auteur -
-
-
<figure>
-    <img src="./../../images/blog/styleguide/photo.png" alt="photo de ...">
-    <figcaption>
-      <span class="figure__legend">Photo de ...</span>
-      <span class="figure__credits">Crédit photo : <a href="">Nom de l'auteur</a></span>
-    </figcaption>
-</figure>
-

Images alignées à gauche / à droite

-
-
-

Lorem ipsum, dolor sit amet consectetur adipisicing elit. Non natus laborum optio provident, ad dolore molestiae ea, labore quo mollitia eaque iste accusantium similique fugit voluptatem nisi asperiores facilis consectetur.

-

Lorem ipsum, dolor sit amet consectetur adipisicing elit. Non natus laborum optio provident, ad dolore molestiae ea, labore quo mollitia eaque iste accusantium similique fugit voluptatem nisi asperiores facilis consectetur.

-
-
- -
- Légende - Crédit photo : Auteur -
-
-
-
- -
-

Lorem ipsum, dolor sit amet consectetur adipisicing elit. Non natus laborum optio provident, ad dolore molestiae ea, labore quo mollitia eaque iste accusantium similique fugit voluptatem nisi asperiores facilis consectetur.

-

Lorem ipsum, dolor sit amet consectetur adipisicing elit. Non natus laborum optio provident, ad dolore molestiae ea, labore quo.

-
-
-
<div class="side-image">
-  <div class="side-image__content">
-    <p>Lorem ipsum, dolor sit amet consectetur adipisicing elit. Non natus laborum optio provident, ad dolore molestiae ea, labore quo mollitia eaque iste accusantium similique fugit voluptatem nisi asperiores facilis consectetur.</p>
-  </div>
-  <figure>
-      <img src="https://upload.wikimedia.org/wikipedia/commons/b/bc/Juvenile_Ragdoll.jpg">
-      <figcaption>
-        <span class="figure__legend">Légende</span>
-        <span class="figure__credits">Crédit photo</span>
-      </figcaption>
-  </figure>
-</div>
-

ou si l'image n'a pas besoin de légende / crédit :

-
<div class="side-image">
-  <img src="content/images/blog/styleguide/exemple-image.jpg">
-  <div class="side-image__content">
-    <p>Lorem ipsum, dolor sit amet consectetur adipisicing elit. Non natus laborum optio provident, ad dolore molestiae ea, labore quo mollitia eaque iste accusantium similique fugit voluptatem nisi asperiores facilis consectetur.</p>
-    <p>Lorem ipsum, dolor sit amet consectetur adipisicing elit. Non natus laborum optio provident, ad dolore molestiae ea, labore quo.</p>
-  </div>
-</div>
-

Pour les autres images, utilisez simplement la syntaxe Markdown classique (cf sections suivantes).

-

Images retina

-

Les images inclues dans un contenu sont automatiquement adaptée et fournies en version rétina et non-rétina lorsque cela le permet.

-
Depuis la racine du projet
-Image d'exemple depuis la racine du projet -
![Image d'exemple depuis la racine du projet](content/images/blog/styleguide/exemple-image.jpg)
-
Avec un chemin relatif au contenu (recommandé)
-Image d'exemple en relatif -
![Image d'exemple en relatif](./../../images/blog/styleguide/exemple-image-relative.jpg)
-
-

- Les GIFs ne peuvent être redimensionnés mais peuvent tout de même être référencés -

-
-Test gif -
![Test gif](./../../images/blog/styleguide/exemple-gif.gif)
-

Le code

-

Pensez à préciser dans le markdown le langage dans lequel est votre code, si vous voulez des couleurs ! 🌈

-
<html>
-  <head></head>
-  <body>
-    Oups
-  </body>
-</html>
-
<html>
-  <head></head>
-  <body>
-    C'est mieux
-  </body>
-</html>
-

Bonus

-

Comme toujours, on essaie tant que possible de choisir des photos libres de droit et d'en créditer les auteurs. Quelques sites de photos libres de droit : Unsplash (chouchou ❤️), Pexels, etc.

-

Pour créditer l'auteur de la photo de couverture, renseignez la clé credits dans le header de l'article :

-
credits: { name: 'Jane Doe', url: 'https://unsplash.com/@janedoe' }
-

Quelques règles typographiques

-

Ponctuation

-
    -
  • Les signes simples comme , et . ne sont précédés d'aucune espace ;
    -Exemple : Je suis contente, aujourd'hui il fait grand soleil, ça faisait longtemps que ça n'était pas arrivé.
  • -
  • Les signes doubles comme ! , ? , ; , :, «, » sont toujours entourés de deux espaces ;
    -Exemple : Bonjour, comment vas-tu ? Je suis contente de te revoir !
  • -
  • Cette règle ne fonctionne pas en anglais où le signe double n'a pas d'espace avant (Hello!).
  • -
  • Attention à bien utiliser les vrais points de suspension et non 3 points à la suite ... . Sur Mac, ⌥ alt + .
  • -
  • Les points de suspension sont suivis d'une espace ;
  • -
  • Préférez les guillemets français pour vos citations : « ». Sur Mac, ⌥ alt + è et ⌥ alt + ⇧ maj + è.
  • -
-

Unités

-
    -
  • Toutes les unités suivant une valeur doivent avoir une espace insécable qui les précède ;
    -Exemple : 10 % et non 10%, 10 h et non 10h, 10 € et non 10€, 10 km et non 10km.
  • -
  • En français, cela marche avec absolument toutes les unités. On écrira donc plutôt "10 km / h" et non "10km/h" ;
  • -
  • Cette règle ne fonctionne pas en anglais où l'on accole l'unité à la valeur (10$ ou $10) ;
  • -
  • Les abréviations d'unités ne sont jamais mises au pluriels : 10 kms, 10 cms.
  • -
-

Utiliser les bonnes abréviations

-

Souvent, les abréviations officielles sont assez méconnues. En voici quelques-unes :

-
    -
  • M. et non Mr;
  • -
  • Mme ;
  • -
  • Mlle et non Melle ;
  • -
  • 10 min et non 10 mn;
  • -
  • 10 h et non 10 hr;
  • -
  • 1er, 1re, 2e, 3e, 4e et non 1ère, 2eme ou 2ème ;
  • -
  • 15 Mo, 15 Go, 15 To et non 15mb, 15gb, 15tb.
  • -
-

Faut-il un point à la fin d'une abréviation ?

-

Une abréviation est suivie d’un point, sauf :

-
    -
  • les abréviations des unités de mesure, pour lesquelles le point n’est jamais utilisé ;
  • -
  • les abréviations construites en conservant la dernière lettre du mot : « bd » pour boulevard. -Autre cas particulier : il faut inclure un espace dans l'abréviation de Nota Bene, N. B.
  • -
-

Nombres

-

Le séparateur de millier est l’espace insécable, le séparateur de décimale est la virgule.
-Exemple : « Le solde est de 3 586,12 euros ».

-

Listes

-

Listes à puces

-

Les items d'une liste à puces commencent toujours avec une majuscule et finissent par un point-virgule, sauf le dernier qui se termine par un point.

-

Exemple :
-Pour se sentir mieux :

-
    -
  • Pensez à faire des pauses plusieurs fois dans la journée ;
  • -
  • Levez les yeux de votre écran plusieurs fois par heure ;
  • -
  • Évitez de consommer trop d'excitants (café, thé, etc.).
  • -
-

N. B. : la règle étant à la base pour l'édition de documents imprimés, il est admis pour les présentations et interfaces web de ne pas surcharger et de ne pas suivre la règle des ponctuations de liste. Mais si vous souhaitez en mettre, c'est cette règle qu'il faut suivre.

-

Listes numérotées

-

Les items d'une liste numérotée commencent toujours avec une majuscule et finissent par un point.

-

Exemple :
-Les valeurs d'Elao sont :

-
    -
  1. L'humain avant tout.
  2. -
  3. Rester humbles et apprendre de nos erreurs.
  4. -
  5. S’ouvrir, partager, ne rien garder pour soi.
  6. -
-

L'emploi du "etc"

-

Quand on fait une liste qui se termine par "etc", celui-ci est précédé d'une virgule et suivi d'un point. Il n'est JAMAIS suivi de points de suspension "etc...".
-Exemple : « Pensez à acheter des fruits : pommes, bananes, clémentines, etc. »

-

D'autres petites règles bien utiles

-
    -
  • Dans le web, l'usage du souligné est utilisé strictement pour signifier un lien. Pour mettre l'emphase sur un mot, préférez le gras.
  • -
  • Il est inutile de mettre un point final . à un titre ;
  • -
  • Il est inutile de mettre deux points : après un titre ou un label de formulaire, puisqu'ils introduisent toujours leur sujet, c'est redondant ;
  • -
  • Les et ne doivent jamais être précédés d'une virgule, sauf dans des cas exceptionnels comme l'énumération ;
  • -
  • L’usage du mot « Éditer » pour « Modifier » est incorrect. Éditer, c’est « publier, diffuser », non « corriger » ;
  • -
  • L'usage du mot « Adresser » pour « Traiter » est incorrect. En français, « adresser » signifie « envoyer », « émettre des paroles », ou « diriger quelqu’un vers la personne qui convient », par exemple adresser un malade à un spécialiste. On ne dira donc pas « Adresser un problème/sujet » mais plutôt « Traiter », « Aborder », « S'attaquer à » ;
  • -
  • L'adjectif « Transverse » est un anglicisme. On lui préfère sa traduction française « Transversal » ;
  • -
  • Les guillemets servent à citer quelqu’un et c’est tout, jamais à insister sur un mot ni à couvrir une approximation ; -Exemple : gérer un projet en mode “agile” ou “classique” => gérer un projet en mode agile ou classique ;
  • -
  • Accentuez les majuscules ! Cela rend la lecture plus facile. Sur Mac, il suffit d'activer le capslock avant d'appuyer sur la touche à accentuer.
  • -
-

L'écriture inclusive

-

Si vous souhaitez être inclusif·ve dans votre rédaction, voici quelques solutions possibles pour que cela reste lisible en fonction du contexte :

-

Doubler au féminin la formule masculine

-

Exemple 1 : « Chaque employé et employée doit faire sa demande de congés sur Lucca. »
-Exemple 2 : « Bonjour à toutes et à tous ! »

-

Utiliser le point médian

-

Exemple 1 : « Chaque employé·e doit faire sa demande de congés sur Lucca. »
-Exemple 2 : « Bonjour à tou·te·s ! »

-

Pour faire un point médian :
-Sur Mac : ⌥ alt + ⇧ maj + F ;
-Sur PC : Alt+0183 ou Alt+00B7.

-

Utiliser des formules non genrées (épicène)

-

Exemple 1 : « L'ensemble de l'équipe doit faire sa demande de congés sur Lucca. »
-Exemple 2 : « Bonjour tout le monde ! »

-

Il est à votre discrétion d'utiliser la formule la plus adaptée en fonction du contexte.

-

Pour aller plus loin

-

Quelques ressources intéressantes :

- -
- Crédits: photo de couverture par - - Jon Tyson - -
-
- -
-
-

Commenter

-

- Des commentaires ? - - Poursuivons la discussion sur twitter ! - -

-
-

- Une typo ? - Modifier cet article sur Github -

-
-
-
-
-
- - - - - - diff --git a/pr/137/browserconfig.xml b/pr/137/browserconfig.xml deleted file mode 100644 index 73cb7330..00000000 --- a/pr/137/browserconfig.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - #ff4344 - - - diff --git a/pr/137/build/305.8dbcaf70.css b/pr/137/build/305.8dbcaf70.css deleted file mode 100644 index a8b9b85e..00000000 --- a/pr/137/build/305.8dbcaf70.css +++ /dev/null @@ -1 +0,0 @@ -@charset "UTF-8";*{box-sizing:border-box}html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;font-family:sans-serif}body{margin:0;padding:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}a{background-color:transparent}small{font-size:80%}img{border:none}svg:not(:root){overflow:hidden}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace}button,input,optgroup,select,textarea{margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}legend{border:0;padding:0}textarea{overflow:auto}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}p{margin:unset}@font-face{font-family:Montserrat Regular;font-style:normal;font-weight:400;src:url(/rix/pr/137/build/fonts/montserrat-regular.4ada7f2f.woff2) format("woff2"),url(/rix/pr/137/build/fonts/montserrat-regular.4a3d6361.woff) format("woff")}@font-face{font-family:Montserrat Medium;font-style:normal;font-weight:400;src:url(/rix/pr/137/build/fonts/montserrat-medium.cf734a37.woff2) format("woff2"),url(/rix/pr/137/build/fonts/montserrat-medium.2451e96b.woff) format("woff")}@font-face{font-family:Outfit Semi Bold;font-style:normal;font-weight:400;src:url(/rix/pr/137/build/fonts/outfit-semi-bold.007337b1.woff2) format("woff2"),url(/rix/pr/137/build/fonts/outfit-semi-bold.58a47c5f.woff) format("woff")}@font-face{font-family:"DM Serif Display";font-style:normal;font-weight:400;src:url(/rix/pr/137/build/fonts/dm-serif-display-regular.f7535417.woff2) format("woff2"),url(/rix/pr/137/build/fonts/dm-serif-display-regular.7265fe7d.woff) format("woff")}@font-face{font-display:block;font-family:icomoon;font-style:normal;font-weight:400;src:url(/rix/pr/137/build/fonts/icomoon.81a5b7b9.eot);src:url(/rix/pr/137/build/fonts/icomoon.81a5b7b9.eot) format("embedded-opentype"),url(/rix/pr/137/build/fonts/icomoon.450cbdb1.ttf) format("truetype"),url(/rix/pr/137/build/fonts/icomoon.32b7badb.woff) format("woff"),url(/rix/pr/137/build/images/icomoon.00ceb532.svg) format("svg")}.icon,address{font-style:normal}.icon{speak:never;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:icomoon!important;font-variant:normal;font-weight:400;line-height:1;text-transform:none}.icon--arrow-right:before{content:"\e905"}.icon--arrow-left:before{content:"\e90b"}.icon--chevron:before{content:"\e902"}.icon--close:before{content:"\e900"}.icon--hamburger:before{content:"\e901"}.icon--location:before{content:"\e903"}.icon--message:before{content:"\e904"}.icon--github:before{content:"\e906"}.icon--linkedin:before{content:"\e907"}.icon--twitter:before{content:"\e908"}.icon--quotations-left:before{content:"\e909"}.icon--quotations-right:before{content:"\e90a"}.icon--website:before{content:"\e90c"}.icon--info:before{content:"\e926"}.icon--danger:before{content:"\e927"}.icon--success:before{content:"न"}.header{background:#fff;border-top:30px solid #07162f;height:155px}.header img{margin:0}.header .container{align-items:center;display:flex;padding:0 20px 20px}.header .logo{position:relative;top:-1px;width:170px}@media (max-width:1220px){.header{height:125px}.header .logo{width:115px}}html{scroll-behavior:smooth}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:#07162f;display:flex;flex-direction:column;font-family:Montserrat Regular;font-size:16px;height:100vh;line-height:1.9}main{background:url(/rix/pr/137/build/images/background.fe096fd5.jpg) no-repeat;background-position:bottom;background-size:contain;flex:1;padding-bottom:350px}b,strong{font-family:Montserrat Medium;font-weight:unset}.container{margin:0 auto;max-width:1440px;padding:0 125px;width:100%}.twitter-tweet{margin:10px auto}@media (max-width:760px){main{background:url(/rix/pr/137/build/images/background-mobile.90615db1.jpg) no-repeat}.container{padding:0 20px}}.nav{margin-left:auto}.nav ul{display:flex;list-style:none;margin:0;padding:0}.nav__item{align-items:center;display:flex;margin:0 20px}.nav__item:after,.nav__item:before{content:none}.nav__item a{color:#07162f;font-family:Outfit Semi Bold;font-size:1.125rem;text-decoration:none}.nav__item a span{padding:7px 2px;position:relative}.nav__item a span:after{background:#eb5050;bottom:-5px;content:"";height:2px;left:0;opacity:0;position:absolute;transition:.15s ease-out;width:100%}.nav__item a:hover span:after{bottom:0;opacity:1}.nav__item--icon a{align-items:center;display:flex}.nav__item--icon a svg{margin-right:9px;width:25px}.nav__item--icon a svg circle,.nav__item--icon a svg path{transition:stroke .3s ease,fill .3s ease}.nav__item--icon a:active span:after,.nav__item--icon a:focus span:after,.nav__item--icon a:hover span:after{display:none}.nav__item--icon a:active svg path,.nav__item--icon a:focus svg path,.nav__item--icon a:hover svg path{stroke:#eb5050}.nav__item--icon a:active svg circle,.nav__item--icon a:focus svg circle,.nav__item--icon a:hover svg circle{fill:#eb5050}.nav__item--active a span:after{background:#99b7d1;bottom:0;opacity:1}@media (max-width:1220px){.nav__item{margin:0 15px}.nav__item--icon svg{display:none}}@media (max-width:995px){.nav{display:none}}.nav-mobile{display:none}@media (max-width:995px){.nav-mobile ul{display:flex;flex:1;flex-direction:column;margin:0;padding:50px 0 0}.nav-mobile__header{align-items:center;border-top:30px solid #fff;display:flex;padding:0 20px}.nav-mobile__item{display:flex;font-size:1.9375rem;margin:0 0 10px;padding:0 0 0 45px}.nav-mobile__item:after,.nav-mobile__item:before{content:none}.nav-mobile__item a{color:#fff;font-family:Outfit Semi Bold;padding:10px 0;text-decoration:none}.nav-mobile__item a:hover{color:#fff}.nav-mobile__item--active a{border-bottom:2px solid #eb5050;position:relative}.nav-mobile__item--icon{border-top:1px solid #99b7d1;height:100px;margin:auto 0 0;padding:0}.nav-mobile__item--icon a{align-items:center;display:flex;justify-content:center;width:100%}.nav-mobile__item--icon svg{margin-right:15px;width:40px}.nav-mobile--open{background:#07162f;bottom:0;display:flex;flex-direction:column;left:0;position:fixed;right:0;top:0;z-index:9999999}}.nav-toggle{display:none}@media (max-width:995px){.nav-toggle{align-items:center;background:transparent;border:1px solid #99b7d1;border-radius:0;display:flex;font-size:1.25rem;height:52px;justify-content:center;margin-left:auto;width:52px}.nav-toggle--open{color:#07162f}.nav-toggle--close{color:#99b7d1}}.footer{background:#07162f;color:#fff;line-height:1.6;padding:100px 0 160px;z-index:1}.footer .container{padding:0 20px}.footer img{margin:0}.footer a{color:hsla(0,0%,100%,.8);font-family:Montserrat Regular;text-decoration:none}.footer a:active,.footer a:focus,.footer a:hover{color:#fff}.footer__contact{display:flex;justify-content:space-between;margin-bottom:100px}.footer__contact .catchphrase{flex:1;font-size:3rem;line-height:1.2;max-width:660px}.footer__contact .catchphrase .h1{color:#fff;font-family:"DM Serif Display";font-size:4rem;line-height:1.2;margin:0 0 24px}.footer__contact .catchphrase strong{color:#45d5d0}.footer__brand{border-top:2px solid #fff;color:#fff;position:relative}.footer__brand img{width:170px}.footer__brand span{left:105px;position:absolute;top:65px}.footer__links{align-items:flex-end;display:flex;margin-bottom:75px;margin-top:-30px;padding-left:105px;position:relative;z-index:1}.footer__links ul{flex:1;list-style:none;margin:0;padding:0}.footer__links li{margin:0 0 20px;padding-left:30px;position:relative}.footer__links li:after,.footer__links li:before{height:16px;position:absolute;top:4px;width:16px}.footer__links li:before{background:#203860;border-radius:50%;content:"";left:0}.footer__links li:after{align-items:center;color:#fff;content:"\e902";display:flex;font-family:icomoon;font-size:.4375rem;justify-content:center;left:0}.footer__links li a{font-family:Montserrat Regular;text-decoration:none}.footer__links li a:active,.footer__links li a:focus,.footer__links li a:hover{color:#fff}.footer__links a{font-family:Outfit Semi Bold;margin-bottom:45px}.footer__links a:active,.footer__links a:focus,.footer__links a:hover{color:#eb5050}.footer__links .pages{display:flex;width:300px}.footer__links .services{align-items:flex-start;display:flex;flex:1;flex-direction:column}.footer__links .services>div{display:flex;width:100%}.footer__links .services ul{margin:0 10px}.footer__links .services ul:first-of-type{margin-left:0}.footer__links .services ul:last-of-type{margin-right:0}.footer__legals{text-align:center}@media (max-width:1280px){.footer__contact{flex-direction:column}.footer__contact .catchphrase{font-size:1.875rem;margin-bottom:60px;max-width:unset}}@media (max-width:1220px){.footer__links{align-items:flex-start}.footer__links .pages{margin-top:65px;width:400px}.footer__links .services>div{flex-direction:column}.footer__links .services ul{margin:0}}@media (max-width:995px){.footer__contact .catchphrase{font-size:2.25rem}.footer__contact .catchphrase .h1{font-size:3rem}.footer__brand img{width:115px}.footer__brand span{left:71px;top:50px}.footer__links{flex-direction:column;margin-top:0;padding-left:0}.footer__links a{font-size:1.25rem}.footer__links ul a{font-size:1rem}.footer__links .pages{margin-top:45px;width:unset}.footer__links .services{margin-top:35px}.footer__links .services a{margin-bottom:25px}.footer__legals a{white-space:nowrap}}:root{--area-b-height:76px;--area-b-width:1000px;--area-d-width:30px}.beveled-wrapper{margin-bottom:-1300px;overflow:hidden;padding-top:var(--area-b-height);position:relative;top:-1300px}.beveled-wrapper__gradient{background:linear-gradient(90deg,transparent calc(50% + 719px),#fff calc(50% + 719px) 100%)}.beveled-wrapper__gradient .shape{background:url(/rix/pr/137/build/images/bevel.4a9689a3.svg) no-repeat;background-position:100% 0!important;background-size:cover!important;padding-top:200px;position:relative}.beveled-wrapper__gradient .shape:before{background:url(/rix/pr/137/build/images/bevel-right.29104ed1.svg) no-repeat;background-size:cover;content:"";height:var(--area-b-height);position:absolute;right:calc(var(--area-b-width)*-1 + var(--area-d-width));top:calc(var(--area-b-height)*-1 + 1px);width:var(--area-b-width)}.beveled-wrapper__gradient .shape .h2--large{margin:0}.beveled-wrapper__gradient .content{background:#fff;display:flex;flex-direction:column}.beveled-wrapper--wolf .beveled-wrapper__gradient .shape{background:url(/rix/pr/137/build/images/bevel-wolf.23f6cdd6.svg) no-repeat}@media (max-width:1440px){.beveled-wrapper .beveled-wrapper__gradient .shape,.beveled-wrapper--wolf .beveled-wrapper__gradient .shape{background:url(/rix/pr/137/build/images/bevel.4a9689a3.svg) no-repeat}.beveled-wrapper__gradient{background:transparent}.beveled-wrapper__gradient .shape:before{display:none}}@media (max-width:1220px){.beveled-wrapper{margin-bottom:-1200px;top:-1200px}.beveled-wrapper__gradient .shape{padding-top:130px}}@media (max-width:995px){.beveled-wrapper{margin-bottom:-1400px;margin-left:15px;top:-1400px}}@media (max-width:760px){.beveled-wrapper{margin-bottom:-1300px;top:-1300px}.beveled-wrapper .shape{padding-top:100px}}@media (max-width:380px){.beveled-wrapper .shape{padding-top:100px}}.author{align-items:center;color:#07162f;display:flex;flex-wrap:wrap;font-size:1.125rem}.author img{border-radius:50%;margin:0 15px 0 0;max-width:unset;width:50px}.author__image{position:relative}.author__image img{border:3px solid #fff;border-radius:50%;height:60px;margin:0 15px 0 0;max-width:unset;width:60px}.author__info{display:flex;flex-direction:column;line-height:1.3}.author__info strong{font-family:Outfit Semi Bold}.author__social{display:flex;flex-wrap:wrap;margin-top:10px;min-width:100%}.author__social a{align-items:center;border:1px solid #07162f;border-radius:50%;color:#07162f;display:flex;height:40px;justify-content:center;margin:0 6px 5px;text-decoration:none;transition:color .15s ease-in,background .15s ease-in;width:40px}.author--multi{align-items:flex-start;flex-direction:column}.author--multi .author__image{height:60px;left:-45px;margin-bottom:10px;position:relative}.author--multi .author__image img:first-child{left:45px;position:absolute}.author--multi .author__image img:nth-child(2){left:90px;position:absolute}.author--multi .author__image img:nth-child(3){left:135px;position:absolute}.author--multi .author__image img:nth-child(4){left:180px;position:absolute}.author--multi .author__image img:nth-child(5){left:225px;position:absolute}.author--multi .author__info+.author__info{margin-left:0;margin-top:20px}.alert{display:flex;font-family:Montserrat Medium;padding:20px 30px}.alert .icon{font-size:1.5625rem;margin-right:15px}.alert p:last-of-type{margin-bottom:0}.alert--error{background:#eb5050;color:hsla(0,0%,100%,.9)}.alert--info{background:#99b7d1;color:hsla(0,0%,100%,.9)}.admonition{background:#45d5d0;color:#07162f;font-family:monospace;font-size:1.125rem;line-height:1.6;margin:60px 0;padding:45px 60px 30px;position:relative}.admonition:after,.admonition:before{position:absolute}.admonition:before{background:#45d5d0;border-radius:50px;content:"";height:58px;left:31px;top:-29px;width:58px}.admonition:after{color:#fff;content:"\e926";font-family:icomoon;font-size:3.75rem;left:30px;line-height:1;position:absolute;top:-30px}.admonition p{margin:0 0 10px!important}.admonition p:last-of-type{margin:0}.admonition a{border-bottom-color:#07162f;font-family:monospace}.admonition a:active,.admonition a:focus,.admonition a:hover{color:#07162f}.admonition-title{font-family:monospace;font-size:1.25rem;font-weight:700}.admonition.success,.admonition.success:before{background:#3ec789}.admonition.success:after{content:"\e928"}.admonition.success a{border-bottom-color:#0d2c1e;color:#0d2c1e}.admonition.success a:active,.admonition.success a:focus,.admonition.success a:hover{color:#0d2c1e}.admonition.danger,.admonition.danger:before{background:#eb5050}.admonition.danger:after{content:"\e927"}.admonition.danger a{border-bottom-color:#07162f;color:#07162f}.admonition.danger a:active,.admonition.danger a:focus,.admonition.danger a:hover{color:#07162f}.article-overview{display:flex;margin-bottom:80px}@media (max-width:1220px){.article-overview{flex-direction:column}}.article-info{border:1px solid #99b7d1;flex:1;height:100%;margin:0 0 0 -1px}.article-info .author{border-bottom:1px solid #99b7d1;padding:30px 35px}.article-info__date{display:flex;flex-wrap:wrap;padding:30px 35px}.article-info__date span{display:flex;flex-direction:column;margin-bottom:20px;margin-right:35px}.article-info__date strong{font-family:Outfit Semi Bold;margin-top:2px}@media (max-width:1220px){.article-info{border:1px solid #99b7d1;margin:-1px 0 0}.article-info__date{padding-top:25px}}.article-footer{background:#fff;border:1px solid #99b7d1;display:flex;flex-wrap:wrap;margin:0 0 20px;padding:45px 75px}.article-footer .author{margin-bottom:20px;min-width:180px;padding:0;width:33.3333333333%}.article-footer .author:nth-last-of-type(-n+3){margin-bottom:0}.article-footer .author__details{display:flex}@media (max-width:760px){.article-footer{flex-direction:column;padding:25px 30px}.article-footer .author:nth-last-of-type(-n+3){margin-bottom:20px}.article-footer .author:last-of-type{margin-bottom:0}}.miniature-list{display:flex;flex-wrap:wrap;list-style:none;margin:0;padding:0}.banner{background-image:url(/rix/pr/137/build/images/banner-home.e928350e.webp);background-position:top;background-repeat:no-repeat;background-size:cover;display:flex;height:1850px;position:relative}.banner:before{background:linear-gradient(180deg,transparent,#fff);bottom:0;content:"";height:200px;left:0;position:absolute;right:0}.banner h1{max-width:950px}.banner p{max-width:700px}.banner .button{margin-top:35px}.page-article .banner,.page-blog .banner,.page-case-study .banner,.page-casebook .banner{height:1550px}.banner--about{background-image:url(/rix/pr/137/build/images/banner-about.cf7414b4.webp)}.banner--blog{background-image:url(/rix/pr/137/build/images/banner-blog.777e5df6.webp)}.banner--case-study{background-image:url(/rix/pr/137/build/images/banner-case-study.c6780f13.webp)}.banner--home{background-image:url(/rix/pr/137/build/images/banner-home.e928350e.webp)}.banner--services{background-image:url(/rix/pr/137/build/images/banner-services.6d7424af.webp)}@media (max-width:995px){.page-home .banner{height:1950px}.banner--about{background-image:url(/rix/pr/137/build/images/banner-about-mobile.ac090375.webp)}.banner--blog{background-image:url(/rix/pr/137/build/images/banner-blog-mobile.dd80f874.webp)}.banner--case-study{background-image:url(/rix/pr/137/build/images/banner-case-study-mobile.9a484e1a.webp)}.banner--home{background-image:url(/rix/pr/137/build/images/banner-home-mobile.2fa58f16.webp)}.banner--services{background-image:url(/rix/pr/137/build/images/banner-services-mobile.cde6025f.webp)}}@media (max-width:760px){.page-article .banner,.page-blog .banner,.page-case-study .banner{height:1350px}.page-casebook .banner{height:1400px}}.button{align-items:center;background:#eb5050;color:#fff;display:inline-flex;font-family:Outfit Semi Bold;justify-content:center;line-height:1.4;min-height:48px;overflow:hidden;padding:15px 35px;position:relative;text-decoration:none;z-index:1}.button:before{background:#07162f;content:"";height:200px;position:absolute;transform:translateY(70%);transition:transform .6s;width:300px;z-index:-1}.button:hover{color:#fff}.button:hover:before{transform:translateY(0) rotate(10deg)}.button-ghost{background:transparent;border:1px solid #99b7d1;padding:15px 35px 15px 60px}.button-ghost:before{background:#eb5050}.button-ghost:after{content:"\e905";font-family:icomoon;left:25px;position:absolute;top:50%;transform:translateY(-50%);transition:transform .4s linear}.button-ghost span{transition:transform .3s linear}.button-ghost:active,.button-ghost:focus,.button-ghost:hover{border:1px solid #eb5050}.button-ghost:active:after,.button-ghost:focus:after,.button-ghost:hover:after{transform:translate(10px,-50%)}.button-ghost:active span,.button-ghost:focus span,.button-ghost:hover span{transform:translateX(5px)}.button-ghost--light{color:#fff}.button-ghost--light:after{color:#eb5050}.button-ghost--light:active,.button-ghost--light:focus,.button-ghost--light:hover{color:#fff}.button-ghost--dark,.button-ghost--light:active:after,.button-ghost--light:focus:after,.button-ghost--light:hover:after{color:#07162f}.button-ghost--dark:after{color:#eb5050}.button-ghost--dark:active,.button-ghost--dark:focus,.button-ghost--dark:hover{color:#07162f}.button-ghost--dark:active:after,.button-ghost--dark:focus:after,.button-ghost--dark:hover:after{color:#fff}@media (max-width:380px){.button{padding:15px 20px}.button-ghost:after{display:none}}.comment{border:1px solid #99b7d1;margin:20px 0;min-width:100%;padding:35px 75px;width:100%}.comment p{margin-bottom:0}.comment .title{font-family:Outfit Semi Bold;font-size:2rem;margin:20px 0 10px}.comment .tweet-container{margin-top:40px}@media (max-width:760px){.comment{padding:25px 30px}}@media (max-width:380px){.comment .tweet-container{display:none}}blockquote{border:1px solid #99b7d1;font-size:2.25rem;line-height:1.4;margin:80px 0;padding:46px 80px;position:relative}blockquote:after,blockquote:before{background-color:#fff;color:#45d5d0;font-family:icomoon;padding:0 8px;position:absolute}blockquote:before{content:"\e909";top:-34px}blockquote:after{bottom:-32px;content:"\e90a";right:90px}blockquote p{margin:0}blockquote cite{display:block;margin-top:15px}@media (max-width:995px){blockquote{font-size:1.5rem;padding:35px 30px}blockquote:before{top:-23px}blockquote:after{bottom:-23px}}.miniature{background:#fff;border:1px solid #99b7d1;display:flex;flex-direction:column;margin:0 20px 40px;min-width:calc(33.33333% - 30px);padding:0;width:calc(33.33333% - 30px)}.miniature:after,.miniature:before{content:none!important}.miniature:nth-child(3n+1){margin-left:0}.miniature:nth-child(3n+3){margin-right:0}.miniature:last-of-type{margin-bottom:40px}.miniature a{display:flex;flex-direction:column;height:100%;text-decoration:none}.miniature a:focus .miniature__image:before,.miniature a:hover .miniature__image:before{background-color:rgba(0,0,0,.2)}.miniature a:focus .miniature__image .image,.miniature a:hover .miniature__image .image{transform:scale(1.02);transform-origin:center}.miniature a .sliding-button{border-top:1px solid #99b7d1;margin-top:auto}.miniature a .sliding-button:before,.miniature a .sliding-button__content{transform:translateY(0) rotate(0deg)}.miniature__content{display:flex;flex:1;flex-direction:column;padding:30px 30px 80px;position:relative;text-decoration:none}.miniature__content .h3{color:#07162f;line-height:1.5;margin:0 0 15px}.miniature__image{height:350px;overflow:hidden;position:relative}.miniature__image:before{background-color:transparent;bottom:0;content:"";left:0;position:absolute;right:0;top:0;transition:background-color .2s ease-in-out;z-index:1}.miniature__image .image{background-position:50%;background-repeat:no-repeat;background-size:cover;display:block;height:100%;position:relative;transition:transform .2s ease-in-out}.miniature__name{color:#eb5050;font-family:Outfit Semi Bold;font-size:1.25rem}@media (max-width:1440px){.miniature{min-width:calc(50% - 20px);width:calc(50% - 20px)}.miniature:nth-child(3n+1),.miniature:nth-child(3n+3){margin-left:20px;margin-right:20px}.miniature:nth-child(odd){margin-left:0}.miniature:nth-child(2n+2){margin-right:0}}@media (max-width:995px){.miniature{margin:0 0 40px!important;min-width:100%;width:100%}.miniature__image{height:260px}}.miniature-inline{background:#fff;margin:0 0 40px;padding:0;width:100%}.miniature-inline:after,.miniature-inline:before{display:none!important}.miniature-inline a{border:1px solid #99b7d1;display:flex;text-decoration:none;width:100%}.miniature-inline a:active .miniature-inline__image:before,.miniature-inline a:focus .miniature-inline__image:before,.miniature-inline a:hover .miniature-inline__image:before{background-color:rgba(0,0,0,.2)}.miniature-inline a:active .miniature-inline__image .image,.miniature-inline a:focus .miniature-inline__image .image,.miniature-inline a:hover .miniature-inline__image .image{transform:scale(1.02);transform-origin:center}.miniature-inline a:active .sliding-button,.miniature-inline a:focus .sliding-button,.miniature-inline a:hover .sliding-button{border-top:1px solid #99b7d1}.miniature-inline a:active .sliding-button:before,.miniature-inline a:active .sliding-button__content,.miniature-inline a:focus .sliding-button:before,.miniature-inline a:focus .sliding-button__content,.miniature-inline a:hover .sliding-button:before,.miniature-inline a:hover .sliding-button__content{transform:translateY(0) rotate(0deg)}.miniature-inline__title{color:#07162f;font-size:2.25rem;line-height:1.5;margin:0 0 15px}.miniature-inline__name{color:#eb5050;font-family:Outfit Semi Bold;font-size:1.75rem;font-weight:400;line-height:1.7}.miniature-inline__image{overflow:hidden;position:relative}.miniature-inline__image:before{background-color:transparent;bottom:0;content:"";left:0;position:absolute;right:0;top:0;transition:background-color .2s ease-in-out;z-index:1}.miniature-inline__image .image{background-position:50%;background-repeat:no-repeat;background-size:cover;display:block;height:100%;margin:0;min-height:350px;min-width:575px;transition:transform .2s ease-in-out;width:575px}.miniature-inline__content{display:flex;flex:1;flex-direction:column;padding:30px 30px 80px;position:relative}@media (max-width:1220px){.miniature-inline a{flex-direction:column}.miniature-inline__image .image{min-width:100%!important;width:100%!important}}@media (max-width:995px){.miniature-inline__image .image{min-height:260px}}@media (max-width:760px){.miniature-inline__title{font-size:1.75rem}.miniature-inline__name{font-size:1.25rem}}.client-list{display:flex;flex-wrap:wrap;list-style:none;margin:65px 0 0}.client-list__item{margin:0;padding:0 20px;width:50%}.client-list__item:after,.client-list__item:before{content:none}.client-list__item:nth-of-type(odd){padding-left:0}.client-list__item:nth-of-type(2n){padding-right:0}.client-list__item .image{background-position:0;background-repeat:no-repeat;background-size:contain;display:block;height:35px;margin-bottom:16px;width:100%}@media (max-width:995px){.client-list{flex-direction:column;margin:0}.client-list__item{padding:0;width:100%}}.contact-infos{background:#fff;border:1px solid #99b7d1;max-width:575px}.contact-infos__item{border-bottom:1px solid #99b7d1;display:flex;font-size:1.125rem;padding:35px 60px;position:relative}.contact-infos__item:last-of-type{border:none}.contact-infos__item .dl,.contact-infos__item dl{margin:0 0 0 70px}.contact-infos__item .dt,.contact-infos__item dt{font-family:Outfit Semi Bold;font-size:1.5rem;line-height:1.4}.contact-infos__item .dd,.contact-infos__item dd{display:block;margin:0;padding:0}.contact-infos__item svg{left:35px;position:absolute;top:50%;transform:translateY(-50%);width:35px}.contact-infos__item a{color:#07162f}.contact-infos__item a .mail-icon__letter{transition:transform .15s ease-out}.contact-infos__item a:active .contact-icon circle:first-of-type,.contact-infos__item a:focus .contact-icon circle:first-of-type,.contact-infos__item a:hover .contact-icon circle:first-of-type{animation:fade 2s infinite both}.contact-infos__item a:active .contact-icon circle:nth-of-type(2),.contact-infos__item a:focus .contact-icon circle:nth-of-type(2),.contact-infos__item a:hover .contact-icon circle:nth-of-type(2){animation:fade 2s infinite both;animation-delay:.4s}.contact-infos__item a:active .contact-icon circle:nth-of-type(3),.contact-infos__item a:focus .contact-icon circle:nth-of-type(3),.contact-infos__item a:hover .contact-icon circle:nth-of-type(3){animation:fade 2s infinite both;animation-delay:.8s}.contact-infos__item a:active .mail-icon__letter,.contact-infos__item a:focus .mail-icon__letter,.contact-infos__item a:hover .mail-icon__letter{transform:translateY(-4px)}@keyframes fade{0%,40%,to{opacity:0;transform:translateZ(80px)}20%{opacity:1;transform:translateZ(0)}}.contact-tiles__item{background:#eb5050;color:#fff;display:flex;margin-bottom:20px;max-width:480px;padding:30px 35px 35px 0;width:100%}.contact-tiles__item:last-of-type{margin-bottom:0}.contact-tiles__item>span{align-items:flex-start;display:flex;flex-direction:column;padding-left:35px}.contact-tiles__item a{font-family:Montserrat Medium;position:relative}.contact-tiles__item .icon{align-items:center;border-right:2px solid #fff;display:flex;font-size:2.5rem;justify-content:center;min-width:120px;width:120px}.contact-tiles__item .title{font-family:Outfit Semi Bold;font-size:1.5rem;line-height:1.2;margin-bottom:12px}@media (max-width:1280px){.contact-tiles{align-items:flex-start;display:flex;justify-content:space-around}.contact-tiles__item{margin:0 10px}.contact-tiles__item:first-of-type{margin-left:0}.contact-tiles__item:last-of-type{margin-right:0}}@media (max-width:995px){.contact-tiles{flex-direction:column}.contact-tiles__item{margin:0 0 20px}.contact-tiles__item>span{padding-left:30px}.contact-tiles__item .icon{min-width:80px;width:80px}}@media (max-width:380px){.contact-tiles__item .icon{display:none}}.values{display:flex;list-style:none;margin:50px 0;padding:0}.values__item{margin:0 15px;min-width:calc(33.33333% - 15px);width:calc(33.33333% - 15px)}.values__item:after,.values__item:before{content:none}@media (max-width:1220px){.values{flex-direction:column;margin-left:20px}.values__item{margin:0 0 40px;min-width:100%;width:100%}}.about-values{background:#07162f url(/rix/pr/137/build/images/glow.10e86a4f.png) no-repeat;background-position:0 100%;background-size:550px;color:#fff;margin:100px 0 200px;position:relative}.about-values:after,.about-values:before{background-size:cover!important;content:"";position:absolute;width:100%}.about-values:before{background:url(/rix/pr/137/build/images/top.0eb15c3f.svg) no-repeat;background-position:100% 100%;height:100px;top:-100px}.about-values:after{background:url(/rix/pr/137/build/images/bottom.69774221.svg) no-repeat;background-position:bottom;bottom:-200px;height:200px}.about-values h2{color:#fff;margin-left:124px}.about-values .grid{background-image:url(/rix/pr/137/build/images/grid.6e242290.svg);background-position:bottom;background-repeat:no-repeat;background-size:contain;height:900px}.about-values ul{display:flex;flex-wrap:wrap;justify-content:flex-end;list-style:none;margin:0;padding:85px 195px 0}.about-values ul li{height:350px;padding:30px 35px;width:400px}.about-values ul li:after,.about-values ul li:before{content:none}@media (max-width:1440px){.about-values{background-size:350px;padding-bottom:135px}.about-values:after{bottom:-100px;height:100px}.about-values .grid{background:none;height:unset}.about-values ul{flex-direction:column;padding:0 125px}.about-values ul li{height:unset;margin:0 0 80px;padding:0;width:unset}}@media (max-width:995px){.about-values h2{margin:0 40px 80px}.about-values ul{padding:0 40px}}.open-source{align-items:center;background:#07162f url(/rix/pr/137/build/images/grid.cd7dfaaa.svg) no-repeat;background-position:bottom;background-size:contain;color:#fff;display:flex;margin:0 0 50px;padding:130px 100px 100px 150px;position:relative}.open-source:after,.open-source:before{content:"";position:absolute}.open-source:before{background:url(/rix/pr/137/build/images/triangle.db3b1be9.svg) no-repeat;background-position:0 100%;background-size:cover;height:100px;left:0;margin-left:-1px;margin-right:-1px;margin-top:-1px;top:0;width:calc(100% + 2px)}.open-source:after{background:url(/rix/pr/137/build/images/github.ae472559.svg) no-repeat;background-position:bottom;background-size:contain;height:100px;left:150px;top:60px;width:95px}.open-source__title{margin-right:120px!important;margin-top:20px!important;padding:0;white-space:nowrap}.open-source__content .button{margin-top:20px}@media (max-width:1220px){.open-source{align-items:flex-start;flex-direction:column;padding-bottom:40px;padding-left:40px;padding-right:40px}.open-source:before{top:-40px}.open-source:after{left:40px;top:30px}.open-source__title{margin-bottom:40px;margin-right:0}}@media (max-width:760px){.open-source{left:20px;position:relative}}@media (max-width:380px){.open-source{left:0;width:calc(100% + 20px)}}.services-list{border:1px solid #99b7d1;margin:0 0 60px;position:relative}.services-list:first-of-type{left:-125px}.services-list:last-of-type{right:-125px}.services-list ul{display:flex;list-style:none;margin:0;padding:0}.services-list__title{border-bottom:1px solid #99b7d1;padding:20px 50px 25px}.services-list__title h3{color:#07162f;font-family:Outfit Semi Bold;font-size:2.25rem;font-weight:400;line-height:1.25;margin:0}.services-list__title h3>span{color:#eb5050;display:block;font-family:Outfit Semi Bold;font-size:1.75rem}.services-list__item{border-right:1px solid #99b7d1;margin-bottom:0;min-width:33.3333333333%;padding-left:0;position:relative;width:33.3333333333%}.services-list__item:after,.services-list__item:before{content:none}.services-list__item:last-of-type{border-right:none}.services-list__item img{margin:0;width:95px}.services-list__item a{color:#07162f;display:block;height:100%;padding:0 50px 160px;text-decoration:none}.services-list__item a:active .sliding-button,.services-list__item a:focus .sliding-button,.services-list__item a:hover .sliding-button{border-top:1px solid #99b7d1}.services-list__item a:active .sliding-button:before,.services-list__item a:active .sliding-button__content,.services-list__item a:focus .sliding-button:before,.services-list__item a:focus .sliding-button__content,.services-list__item a:hover .sliding-button:before,.services-list__item a:hover .sliding-button__content{transform:translateY(0) rotate(0deg)}.services-list__item h4{color:#07162f;font-family:Outfit Semi Bold;font-size:1.5rem;line-height:1.3;margin:40px 0 15px}@media (max-width:1440px){.services-list:first-of-type,.services-list:last-of-type{left:unset;right:unset}}@media (max-width:1280px){.services-list{border:none}.services-list ul{flex-direction:column}.services-list__title{border:1px solid #99b7d1;border-bottom:none}.services-list__item{border:1px solid #99b7d1!important;margin:0 0 10px;min-width:100%;width:100%}.services-list__item a{padding:0 50px 80px}}@media (max-width:995px){.services-list__item img{width:60px}.services-list__item a{padding:0 25px 80px}}.sliding-button{background:#fff;bottom:0;display:flex;flex-wrap:wrap;height:80px;left:0;overflow:hidden;position:absolute;right:0}.sliding-button:before{background-color:#fff;content:"";height:80px;position:absolute;transform:translateY(160%) rotate(10deg);transition:transform .6s;width:500px}.sliding-button img{border-bottom:1px solid #99b7d1;border-right:1px solid #99b7d1;margin:0;padding:0 20px;width:50%}.sliding-button img:nth-of-type(2n){border-right:none}.sliding-button img:last-of-type,.sliding-button img:nth-last-of-type(2){border-bottom:none}.sliding-button__content{align-items:center;background:#fff;display:flex;font-family:Outfit Semi Bold;height:80px;justify-content:flex-end;left:0;padding:0 40px;position:absolute;top:0;transform:translateY(160%) rotate(10deg);transition:transform .35s;width:100%}.sliding-button__content .icon{color:#eb5050;margin-right:15px}.sliding-button--replaced{border-top:1px solid #99b7d1}.sliding-button--replaced .sliding-button__content{border-top:1px solid transparent}.sliding-button--large,.sliding-button--large .sliding-button__content,.sliding-button--large:before{height:160px}@media (max-width:1280px){.sliding-button{display:none}.sliding-button--mobile{border-top:none!important;display:flex}.sliding-button--mobile .sliding-button__content{transform:none;transition:none}}.signature{display:flex;justify-content:space-between}.signature textarea{height:200px;padding:10px;width:100%}.signature__preview{margin-top:25px;padding-right:95px}.signature__code{flex:1}.signature__code p{margin:0 0 15px}@media (max-width:1220px){.signature{flex-direction:column-reverse}}.ecosystem{position:relative}.ecosystem>img{left:400px;position:absolute;top:275px;width:330px}.ecosystem__list{display:flex;flex-wrap:wrap;list-style:none;margin:0;padding:0;position:relative;z-index:1}.ecosystem__list li{background:#fff;border:1px solid #d7e4ef;margin-bottom:20px;min-height:300px;padding:30px 35px;width:370px}.ecosystem__list li:after,.ecosystem__list li:before{content:none}.ecosystem__list li img{width:50px}.ecosystem__list li strong{font-size:1.5rem}.ecosystem__list li h4{color:#07162f;font-family:Outfit Semi Bold;font-size:1.5rem;line-height:1.3;margin:40px 0 15px}.ecosystem__list .analysis{margin-left:200px}.ecosystem__list .detection{margin-left:40px}.ecosystem__list .automation{margin-left:45px}.ecosystem__list .supervision{margin-left:300px}.ecosystem__list .backup{margin-left:225px}@media (max-width:1440px){.ecosystem>img{margin-bottom:-20px;margin-left:auto;position:static;width:270px}.ecosystem__list li{min-height:unset;width:100%}.ecosystem__list .analysis,.ecosystem__list .automation,.ecosystem__list .backup,.ecosystem__list .detection,.ecosystem__list .supervision{margin-left:0}}@media (max-width:1220px){.ecosystem>img{width:200px}}.tags-list{display:flex;flex-wrap:wrap;margin:0;padding:0}.miniature .tags-list,.miniature-inline .tags-list{margin-bottom:35px}.tags-list__item{background-color:#d7e4ef;border:none!important;color:#07162f;display:flex;font-family:Outfit Semi Bold;font-size:.875rem;margin:5px!important;padding:7px 10px!important}.tags-list__item:after,.tags-list__item:before{display:none}.tags-list__item a{text-decoration:none}.timeline{display:flex;list-style:none;margin:50px 0;padding:0}.timeline__item{margin:0 80px;max-width:330px;padding-top:68px;position:relative}.timeline__item:after,.timeline__item:before{content:"";position:absolute}.timeline__item:before{background-image:url(/rix/pr/137/build/images/dot.ec074665.svg);background-repeat:repeat-x;height:2px;left:100px;top:32px;width:100%}.timeline__item:after{background-image:url(/rix/pr/137/build/images/circle.aa70b6bc.svg);height:65px;top:0;width:65px}.timeline__item:last-of-type:before{display:none}.timeline__item .title{font-size:1.5rem}@media (max-width:995px){.timeline{flex-direction:column}.timeline__item{margin:0 0 50px}.timeline__item:before{display:none}.timeline__item:last-of-type{margin:0}}.technologies-list{margin:0 0 60px;position:relative}.technologies-list ul{display:flex;flex-wrap:wrap;margin:0;padding:0}.technologies-list ul>li:after,.technologies-list ul>li:before{content:none}.technologies-list__title{padding-bottom:20px}.technologies-list__item{border:1px solid #99b7d1;border-left:none;margin-bottom:15px;min-width:33.3333333333%;padding-left:0;position:relative;text-align:center;width:33.3333333333%}.technologies-list__item:nth-of-type(3n+1){border-left:1px solid #99b7d1}.technologies-list__item:last-of-type{margin-bottom:15px}.technologies-list__item a{color:#99b7d1;display:block;font-family:Outfit Semi Bold;height:100%;text-decoration:none}.technologies-list__item a>span{display:block;padding:20px 0}.technologies-list__item img{border-bottom:1px solid #99b7d1;padding:0 50px;width:100%}@media (max-width:995px){.technologies-list__item{border-left:1px solid #99b7d1;width:100%}.technologies-list__item img{padding:0 100px}}.table-of-contents{border:1px solid #99b7d1;color:#eb5050;display:flex;flex-direction:column;font-family:Montserrat Medium;margin:0;padding:30px 45px;width:calc(100% - 330px)}.table-of-contents__item{padding:0 15px}.table-of-contents__item a{text-decoration:none}.table-of-contents__sub-level{list-style-type:lower-alpha}@media (max-width:1220px){.table-of-contents{padding:20px 25px;width:100%}}.profile{background:#fff;border:1px solid #99b7d1;display:flex;flex-direction:column;padding-left:0}.profile__image{background:#d7e4ef 0/contain no-repeat url(/rix/pr/137/build/images/default.c762a445.png);height:195px;margin-bottom:20px}.profile__name{font-family:Outfit Semi Bold;font-size:1.5rem}.profile__job,.profile__name{line-height:1.4;padding:0 40px}.profile__job{color:#eb5050;font-size:1.25rem}.profile__socials{display:flex;margin-top:20px}.profile__socials a{border-right:1px solid #99b7d1;border-top:1px solid #99b7d1;color:#eb5050;flex:1;font-size:1.0625rem;padding:7px 0;text-align:center;text-decoration:none}.profile__socials a:last-of-type{border-right:none}.profile__socials a:active,.profile__socials a:focus,.profile__socials a:hover{background-color:#d7e4ef}.profile-list{align-items:flex-start;display:flex;flex-wrap:wrap;list-style:none;margin:70px -125px;padding:0}.profile-list>li{margin:0 20px 30px;min-width:calc(25% - 40px);width:calc(25% - 40px)}.profile-list>li:after,.profile-list>li:before{content:none}@media (max-width:1280px){.profile-list>li{display:none;min-width:calc(33.33% - 40px);width:calc(33.33% - 40px)}.profile-list .experience,.profile-list .profile{display:flex}}@media (max-width:995px){.profile-list>li{min-width:calc(50% - 40px);width:calc(50% - 40px)}}@media (max-width:760px){.profile-list{margin:70px 0 0}.profile-list>li{margin:0 0 30px;min-width:100%;width:100%}}.pagination{align-items:center;background:#fff;display:flex;justify-content:center;margin:0;padding:20px 0}.pagination__item{border-bottom:1px solid #99b7d1;border-top:1px solid #99b7d1;margin:0;padding-left:0}.pagination__item:after,.pagination__item:before{display:none}.pagination__item:nth-of-type(2){padding-left:20px}.pagination__item:nth-last-of-type(2){padding-right:20px}.pagination__item a{align-items:center;color:#07162f;display:flex;font-family:Outfit Semi Bold;font-size:1.125rem;height:48px;justify-content:center;margin:0;position:relative;text-decoration:none;width:48px;z-index:1}.pagination__item a:after{background:transparent;border-radius:50%;content:"";height:25px;pointer-events:none;position:absolute;transition:background .1s ease-in;width:25px;z-index:-1}.pagination__item a:active,.pagination__item a:focus,.pagination__item a:hover{color:#fff}.pagination__item a:active:after,.pagination__item a:focus:after,.pagination__item a:hover:after{background:#07162f;border-radius:50%}.pagination__item--active a{pointer-events:none}.pagination__item--active a:after{background:#d7e4ef;border-radius:unset}.pagination__item--nav{border-left:1px solid #99b7d1;border-right:1px solid #99b7d1}.pagination__item--nav a{color:#eb5050;font-size:1.25rem;transition:background .1s ease-in}.pagination__item--nav a:after{display:none}.pagination__item--nav a:active,.pagination__item--nav a:focus,.pagination__item--nav a:hover{background:#eb5050;color:#fff}@media (max-width:760px){.pagination__item:nth-of-type(2){padding-left:10px}.pagination__item:nth-last-of-type(2){padding-right:10px}.pagination__item a{height:50px;width:35px}.pagination__item--nav a{width:40px}}.experience{background:#fff;border:1px solid #99b7d1;border-top:13px solid #07162f;color:#07162f;display:flex;flex-direction:column;min-height:370px;padding:70px 0;position:relative;text-align:center;white-space:nowrap}.experience strong{font-family:"DM Serif Display";font-size:2.5rem;line-height:1.1}.experience strong span{font-size:7.5rem}.experience--dark{background:#07162f;border:none;border-top:13px solid #d7e4ef;color:#fff}.experience--dark:before{background:url(/rix/pr/137/build/images/expertise.9d4f8cd1.svg) no-repeat;background-position:top;background-size:contain;border-radius:0;content:""!important;height:55px;left:unset;position:absolute;right:35px;top:-1px;width:45px}.preview{background:url(/rix/pr/137/build/images/preview.1e8ad7d0.svg) no-repeat;background-position:top;background-size:cover;color:#fff;padding:128px 55px 55px;position:absolute;right:125px;top:-275px;width:575px}.preview .preview__content{text-align:center}.preview .button{margin:0 auto;max-width:400px;width:100%}.preview .button:before{width:750px}.preview__image{background:no-repeat;background-position:50%;background-size:cover;height:350px;margin-bottom:74px}@media (max-width:1220px){.preview{max-width:100%;position:unset;width:100%}}.miniature-highlight{display:flex;flex-direction:column;margin:0 0 40px;padding:0;width:100%}.miniature-highlight a:focus .miniature-highlight__image:before,.miniature-highlight a:hover .miniature-highlight__image:before{background-color:rgba(0,0,0,.2)}.miniature-highlight a:focus .miniature-highlight__image .image,.miniature-highlight a:hover .miniature-highlight__image .image{transform:scale(1.02);transform-origin:center}.miniature-highlight__image{min-height:350px;min-width:575px;overflow:hidden;position:relative;width:575px}.miniature-highlight__image:before{background-color:transparent;bottom:0;content:"";left:0;position:absolute;right:0;top:0;transition:background-color .2s ease-in-out;z-index:1}.miniature-highlight__image .image{background-position:50%;background-repeat:no-repeat;background-size:cover;display:block;height:100%;transition:transform .2s ease-in-out}.miniature-highlight__content{display:flex;text-decoration:none}.miniature-highlight__content h2{margin:10px 0 15px}.miniature-highlight__content .details{padding:60px 80px 20px}.miniature-highlight__content .date{color:#eb5050;font-family:Outfit Semi Bold;font-size:1rem}.miniature-highlight__tags{border-top:1px solid #99b7d1;padding:25px 450px 25px 80px}.miniature-highlight__author{background:#fff;border-left:1px solid #99b7d1;border-top:1px solid #99b7d1;bottom:0;min-width:370px;padding:30px;position:absolute;right:0;z-index:2}@media (max-width:1220px){.miniature-highlight__content{flex-direction:column-reverse}.miniature-highlight__content .details{padding:30px 30px 0}.miniature-highlight__image{min-height:260px;min-width:100%;width:100%}.miniature-highlight__image .image{height:100%;width:100%}.miniature-highlight__tags{padding:15px 30px}.miniature-highlight__author{border-left:none;min-width:100%;position:static}}.side-image{align-items:center;display:flex;margin:0 0 35px}.side-image figure,.side-image>img{margin:0 50px!important;max-width:calc(50% - 100px);width:unset!important}.side-image__content{flex:1}@media (max-width:1280px){.side-image{flex-direction:column}.side-image figure,.side-image>img{margin:50px 0!important;max-width:100%}}a{text-decoration:underline}a,a:active,a:focus,a:hover{color:#07162f}code,code [class*=language-],code[class*=language-],pre,pre [class*=language-],pre[class*=language-]{word-wrap:normal;background:#d7e4ef;color:#07162f;font-family:monospace;font-size:1.125rem;-webkit-hyphens:none;hyphens:none;line-height:1.5;overflow:auto;padding:0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;text-align:left;white-space:pre;word-break:normal;word-spacing:normal}.code-multiline{font-size:1.125rem;margin:30px 0!important;padding:20px 30px!important}.code-inline{background:#d7e4ef;border-radius:5px;font-family:monospace;padding:2px 4px;white-space:pre-wrap}.code-inline,.token.cdata,.token.class-name,.token.delimiter,.token.doctype,.token.function,.token.function-name,.token.inserted,.token.operator,.token.prolog,.token.property,.token.punctuation,.token.selector{color:#07162f}.token.atrule,.token.attr-name,.token.builtin,.token.constant,.token.deleted,.token.important,.token.keyword,.token.namespace,.token.symbol,.token.tag,.token.variable{color:#eb5050}.token.attr-value,.token.boolean,.token.char,.token.entity,.token.number,.token.regex,.token.string,.token.url{color:#294262}.token.block-comment,.token.block-comment *,.token.comment,.token.comment *{border:none;color:#616161!important}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.content{background:#d7e4ef;display:initial}img,video{display:block;margin:35px auto;max-width:100%}figure{margin-bottom:50px}figure img{display:block;margin:0 auto 20px;max-width:100%}figcaption{display:flex;flex-wrap:wrap;justify-content:center;margin:0 auto;max-width:600px}figcaption a{border:none}figcaption a,figcaption a:active,figcaption a:focus,figcaption a:hover{color:#07162f}.figure__credits{padding-left:30px;position:relative}.figure__credits:before{background-color:#07162f;content:"";height:1px;left:8px;position:absolute;top:50%;transform:translateY(-50%);width:12px}p{margin:0 0 35px}.h1,.h2,.h3,.h4,.h5,.h6{display:block}.h1,h1{font-size:4.5rem;font-weight:400;line-height:1.25;margin:75px 0 35px}.h1,.h1 *,h1,h1 *{font-family:"DM Serif Display"}.h1 span,h1 span{color:#eb5050;display:block;font-family:Outfit Semi Bold;font-size:3.3125rem}.h1 strong,h1 strong{color:#eb5050}.h2,h2{font-family:Outfit Semi Bold;font-size:2.25rem;font-weight:400;line-height:1.3;margin:30px 0}.h3,h3{font-size:1.75rem;line-height:1.7;margin:30px 0 23px}.h3,.h4,.h5,.h6,h3,h4,h5,h6{color:#eb5050;font-family:Outfit Semi Bold;font-weight:400}.h4,.h5,.h6,h4,h5,h6{font-size:1.25rem;line-height:2.4;margin:23px 0}.h2--large{font-family:"DM Serif Display";font-size:4.5rem;font-weight:400;line-height:1.2}.h2--large>span{display:block;font-size:1.75rem;line-height:1.8}.h2--large>span,.h3--small{color:#eb5050;font-family:Outfit Semi Bold}.h3--small{font-size:1rem;font-weight:400;line-height:1.7;margin-bottom:0}.h3--dark{color:#07162f;font-family:Outfit Semi Bold;font-size:1.75rem;margin-top:0}.image-title{margin:0 0 25px}.image-title img{height:55px;margin:0 0 15px}.image-title__content{color:#07162f;font-family:Outfit Semi Bold;font-size:1.5rem;line-height:1.3;margin:0}.image-title--light .image-title__content{color:#fff}.case-study__title{margin-bottom:40px!important}.anchor-title{scroll-margin-top:130px}.anchor-title a{text-decoration:none}.anchor-title a:after{bottom:-10px;color:#294262;content:"#";left:10px;opacity:0;position:relative;transform:translateY(-10px);transition:opacity .15s ease-in,bottom .15s ease-in}.anchor-title a:active:after,.anchor-title a:focus:after,.anchor-title a:hover:after{bottom:0;opacity:1}@media (max-width:995px){.h1,h1{font-size:3rem;margin:15px 0 30px}.h2,.h3,h2,h3{font-size:1.5rem}.h2--large{font-size:2.25rem}.h3--dark{font-size:1.5rem}.h2--large>span,.h2>span{font-size:1.25rem}.case-study__title{font-size:2.25rem}.anchor-title{scroll-margin-top:50px}}@media (max-width:380px){.h1,h1{font-size:2.1875rem}}ul{list-style:none;margin:35px 0;padding:0}ul li{margin-bottom:3px;padding-left:30px;position:relative}ul li:last-of-type{margin-bottom:0}ul li:after,ul li:before{height:16px;position:absolute;top:4px;width:16px}ul li:before{background:#d7e4ef;border-radius:50%;content:"";left:0}ul li:after{align-items:center;color:#07162f;content:"\e902";display:flex;font-family:icomoon;font-size:.4375rem;justify-content:center;left:1px}.screen-reader{height:1px;left:-10000px;overflow:hidden;position:absolute;top:auto;width:1px}.page-home main{padding-bottom:0}.home-services>p{margin-bottom:100px;max-width:690px}.home-ecosystem{background:url(/rix/pr/137/build/images/waves.be5ec2dc.svg) no-repeat;background-position:50%;background-size:1650px;position:relative}.home-ecosystem:before{background:linear-gradient(180deg,#fff 40%,hsla(0,0%,100%,.5) 70%,hsla(0,0%,100%,0));content:"";height:200px;left:0;position:absolute;top:0;width:100%;z-index:0}.home-ecosystem h2{position:relative;z-index:1}.home-clients{background:#fff;margin-left:0;padding-top:117px}@media (max-width:760px){.home-ecosystem{background-position:top;background-size:105%}.home-ecosystem:after,.home-ecosystem:before{display:none}}.page-article main{background-image:none;padding-bottom:75px}.page-article h2:first-of-type{margin-top:0;padding-top:0}.page-article .alert{margin:0 0 40px}.page-blog main{background-image:none}.page-blog .content{padding-bottom:50px}.page-blog .content ul>li:first-of-type{border:1px solid #99b7d1}.page-blog .content ul>li:after,.page-blog .content ul>li:before{content:none!important}.page-blog .miniature:nth-child(3n+1),.page-blog .miniature:nth-child(3n+3){margin-left:20px;margin-right:20px}.page-blog .miniature:nth-child(3n+5),.page-blog .miniature:nth-child(5){margin-left:0}.page-blog .miniature:nth-child(3n+7),.page-blog .miniature:nth-child(7){margin-right:0}@media (max-width:1440px){.page-blog .miniature:nth-child(2n+2),.page-blog .miniature:nth-child(3n+5),.page-blog .miniature:nth-child(3n+7),.page-blog .miniature:nth-child(5),.page-blog .miniature:nth-child(7),.page-blog .miniature:nth-child(odd){margin-left:20px;margin-right:20px}.page-blog .miniature:nth-child(2n+5),.page-blog .miniature:nth-child(5){margin-left:0}.page-blog .miniature:nth-child(2n+6),.page-blog .miniature:nth-child(6){margin-right:0}}@media (max-width:1280px){.sliding-button--mobile{border-top:1px solid #99b7d1!important}}.page-casebook main{background-image:none}.page-casebook .h3{margin-bottom:20px}@media (max-width:995px){.page-casebook .content{padding-bottom:50px}}.page-case-study main{background-image:none}.page-case-study .beveled-wrapper__gradient .content{padding-bottom:55px;position:relative}.page-case-study .beveled-wrapper__gradient .content ul{margin-top:0}.page-case-study .beveled-wrapper__gradient .content figure{margin-left:-125px}.page-case-study .beveled-wrapper__gradient .content figure img{margin:0 auto 20px}.page-case-study .beveled-wrapper__gradient .content figure figcaption{text-align:center}.page-case-study .beveled-wrapper__gradient .case-study__header{width:50%}.page-case-study .beveled-wrapper__gradient .case-study__header ul>li{padding-left:0}.page-case-study .beveled-wrapper__gradient .case-study__header ul>li h3{margin-bottom:0}.page-case-study .beveled-wrapper__gradient .case-study__header ul>li:after,.page-case-study .beveled-wrapper__gradient .case-study__header ul>li:before{content:none}.page-case-study .beveled-wrapper__gradient .case-study__image{height:743px}.page-case-study iframe{margin:0 auto}@media (max-width:1220px){.page-case-study .beveled-wrapper__gradient .case-study__header{width:100%}}@media (max-width:995px){.page-case-study .beveled-wrapper__gradient .content figure{margin:0}}.page-services .miniature__content h3{color:#07162f;margin:0 0 29px}.page-services__images{border:1px solid #99b7d1;border-top:none;display:flex;height:125px}.page-services__images>span{align-items:center;background-color:#fff;border-right:1px solid #99b7d1;display:flex;flex:1;justify-content:center;padding:20px}.page-services__images>span:last-of-type{border-right:none}.page-services__images>span img{max-height:100%}.page-services__content{background:#fff;border:1px solid #99b7d1;margin:60px 0 0;padding:0 80px 70px}.page-services__content h3{color:#07162f}.page-services__content img{margin:0}@media (max-width:760px){.page-services .h3,.page-services h3{font-size:1.5rem}.page-services__content{padding:0 25px}.page-services__content img{width:60px}}.page-contact main{background-position:top;background-size:cover}.page-contact h1 span{display:block}.page-legals h2:first-of-type{margin:0}.page-error main{background-position:top;background-size:cover}.error__title span{color:#eb5050;display:block;font-family:Outfit Semi Bold;font-size:3.3125rem}.error__action{margin:35px 0 0}.error__action .button{margin-right:35px}.error__action .button:last-of-type{margin:0}@media (max-width:1280px){.page-error main{background-image:none}.page-error .error{align-items:center;display:flex;flex-direction:column;text-align:center}}@media (max-width:995px){.page-error .error__action{display:flex;flex-direction:column}.page-error .error__action .button{margin:0 0 35px}}.page-signature main{background-image:none} \ No newline at end of file diff --git a/pr/137/build/70.671fda9c.js b/pr/137/build/70.671fda9c.js deleted file mode 100644 index 246d8a7b..00000000 --- a/pr/137/build/70.671fda9c.js +++ /dev/null @@ -1 +0,0 @@ -(self.webpackChunkrix_website=self.webpackChunkrix_website||[]).push([[70],{8360:(t,e,r)=>{"use strict";r.d(e,{x:()=>tt});class n{constructor(t,e,r){this.eventTarget=t,this.eventName=e,this.eventOptions=r,this.unorderedBindings=new Set}connect(){this.eventTarget.addEventListener(this.eventName,this,this.eventOptions)}disconnect(){this.eventTarget.removeEventListener(this.eventName,this,this.eventOptions)}bindingConnected(t){this.unorderedBindings.add(t)}bindingDisconnected(t){this.unorderedBindings.delete(t)}handleEvent(t){const e=function(t){if("immediatePropagationStopped"in t)return t;{const{stopImmediatePropagation:e}=t;return Object.assign(t,{immediatePropagationStopped:!1,stopImmediatePropagation(){this.immediatePropagationStopped=!0,e.call(this)}})}}(t);for(const t of this.bindings){if(e.immediatePropagationStopped)break;t.handleEvent(e)}}get bindings(){return Array.from(this.unorderedBindings).sort(((t,e)=>{const r=t.index,n=e.index;return rn?1:0}))}}class i{constructor(t){this.application=t,this.eventListenerMaps=new Map,this.started=!1}start(){this.started||(this.started=!0,this.eventListeners.forEach((t=>t.connect())))}stop(){this.started&&(this.started=!1,this.eventListeners.forEach((t=>t.disconnect())))}get eventListeners(){return Array.from(this.eventListenerMaps.values()).reduce(((t,e)=>t.concat(Array.from(e.values()))),[])}bindingConnected(t){this.fetchEventListenerForBinding(t).bindingConnected(t)}bindingDisconnected(t){this.fetchEventListenerForBinding(t).bindingDisconnected(t)}handleError(t,e,r={}){this.application.handleError(t,`Error ${e}`,r)}fetchEventListenerForBinding(t){const{eventTarget:e,eventName:r,eventOptions:n}=t;return this.fetchEventListener(e,r,n)}fetchEventListener(t,e,r){const n=this.fetchEventListenerMapForEventTarget(t),i=this.cacheKey(e,r);let o=n.get(i);return o||(o=this.createEventListener(t,e,r),n.set(i,o)),o}createEventListener(t,e,r){const i=new n(t,e,r);return this.started&&i.connect(),i}fetchEventListenerMapForEventTarget(t){let e=this.eventListenerMaps.get(t);return e||(e=new Map,this.eventListenerMaps.set(t,e)),e}cacheKey(t,e){const r=[t];return Object.keys(e).sort().forEach((t=>{r.push(`${e[t]?"":"!"}${t}`)})),r.join(":")}}const o=/^((.+?)(@(window|document))?->)?(.+?)(#([^:]+?))(:(.+))?$/;function s(t){return"window"==t?window:"document"==t?document:void 0}function a(t){return t.replace(/(?:[_-])([a-z0-9])/g,((t,e)=>e.toUpperCase()))}function c(t){return t.charAt(0).toUpperCase()+t.slice(1)}function u(t){return t.replace(/([A-Z])/g,((t,e)=>`-${e.toLowerCase()}`))}const l={a:t=>"click",button:t=>"click",form:t=>"submit",details:t=>"toggle",input:t=>"submit"==t.getAttribute("type")?"click":"input",select:t=>"change",textarea:t=>"input"};function h(t){throw new Error(t)}function d(t){try{return JSON.parse(t)}catch(e){return t}}class f{constructor(t,e){this.context=t,this.action=e}get index(){return this.action.index}get eventTarget(){return this.action.eventTarget}get eventOptions(){return this.action.eventOptions}get identifier(){return this.context.identifier}handleEvent(t){this.willBeInvokedByEvent(t)&&this.invokeWithEvent(t)}get eventName(){return this.action.eventName}get method(){const t=this.controller[this.methodName];if("function"==typeof t)return t;throw new Error(`Action "${this.action}" references undefined method "${this.methodName}"`)}invokeWithEvent(t){const{target:e,currentTarget:r}=t;try{const{params:n}=this.action,i=Object.assign(t,{params:n});this.method.call(this.controller,i),this.context.logDebugActivity(this.methodName,{event:t,target:e,currentTarget:r,action:this.methodName})}catch(e){const{identifier:r,controller:n,element:i,index:o}=this,s={identifier:r,controller:n,element:i,index:o,event:t};this.context.handleError(e,`invoking action "${this.action}"`,s)}}willBeInvokedByEvent(t){const e=t.target;return this.element===e||(e instanceof Element&&this.element.contains(e)?this.scope.containsElement(e):this.scope.containsElement(this.action.element))}get controller(){return this.context.controller}get methodName(){return this.action.methodName}get element(){return this.scope.element}get scope(){return this.context.scope}}class p{constructor(t,e){this.mutationObserverInit={attributes:!0,childList:!0,subtree:!0},this.element=t,this.started=!1,this.delegate=e,this.elements=new Set,this.mutationObserver=new MutationObserver((t=>this.processMutations(t)))}start(){this.started||(this.started=!0,this.mutationObserver.observe(this.element,this.mutationObserverInit),this.refresh())}pause(t){this.started&&(this.mutationObserver.disconnect(),this.started=!1),t(),this.started||(this.mutationObserver.observe(this.element,this.mutationObserverInit),this.started=!0)}stop(){this.started&&(this.mutationObserver.takeRecords(),this.mutationObserver.disconnect(),this.started=!1)}refresh(){if(this.started){const t=new Set(this.matchElementsInTree());for(const e of Array.from(this.elements))t.has(e)||this.removeElement(e);for(const e of Array.from(t))this.addElement(e)}}processMutations(t){if(this.started)for(const e of t)this.processMutation(e)}processMutation(t){"attributes"==t.type?this.processAttributeChange(t.target,t.attributeName):"childList"==t.type&&(this.processRemovedNodes(t.removedNodes),this.processAddedNodes(t.addedNodes))}processAttributeChange(t,e){const r=t;this.elements.has(r)?this.delegate.elementAttributeChanged&&this.matchElement(r)?this.delegate.elementAttributeChanged(r,e):this.removeElement(r):this.matchElement(r)&&this.addElement(r)}processRemovedNodes(t){for(const e of Array.from(t)){const t=this.elementFromNode(e);t&&this.processTree(t,this.removeElement)}}processAddedNodes(t){for(const e of Array.from(t)){const t=this.elementFromNode(e);t&&this.elementIsActive(t)&&this.processTree(t,this.addElement)}}matchElement(t){return this.delegate.matchElement(t)}matchElementsInTree(t=this.element){return this.delegate.matchElementsInTree(t)}processTree(t,e){for(const r of this.matchElementsInTree(t))e.call(this,r)}elementFromNode(t){if(t.nodeType==Node.ELEMENT_NODE)return t}elementIsActive(t){return t.isConnected==this.element.isConnected&&this.element.contains(t)}addElement(t){this.elements.has(t)||this.elementIsActive(t)&&(this.elements.add(t),this.delegate.elementMatched&&this.delegate.elementMatched(t))}removeElement(t){this.elements.has(t)&&(this.elements.delete(t),this.delegate.elementUnmatched&&this.delegate.elementUnmatched(t))}}class m{constructor(t,e,r){this.attributeName=e,this.delegate=r,this.elementObserver=new p(t,this)}get element(){return this.elementObserver.element}get selector(){return`[${this.attributeName}]`}start(){this.elementObserver.start()}pause(t){this.elementObserver.pause(t)}stop(){this.elementObserver.stop()}refresh(){this.elementObserver.refresh()}get started(){return this.elementObserver.started}matchElement(t){return t.hasAttribute(this.attributeName)}matchElementsInTree(t){const e=this.matchElement(t)?[t]:[],r=Array.from(t.querySelectorAll(this.selector));return e.concat(r)}elementMatched(t){this.delegate.elementMatchedAttribute&&this.delegate.elementMatchedAttribute(t,this.attributeName)}elementUnmatched(t){this.delegate.elementUnmatchedAttribute&&this.delegate.elementUnmatchedAttribute(t,this.attributeName)}elementAttributeChanged(t,e){this.delegate.elementAttributeValueChanged&&this.attributeName==e&&this.delegate.elementAttributeValueChanged(t,e)}}class g{constructor(t,e){this.element=t,this.delegate=e,this.started=!1,this.stringMap=new Map,this.mutationObserver=new MutationObserver((t=>this.processMutations(t)))}start(){this.started||(this.started=!0,this.mutationObserver.observe(this.element,{attributes:!0,attributeOldValue:!0}),this.refresh())}stop(){this.started&&(this.mutationObserver.takeRecords(),this.mutationObserver.disconnect(),this.started=!1)}refresh(){if(this.started)for(const t of this.knownAttributeNames)this.refreshAttribute(t,null)}processMutations(t){if(this.started)for(const e of t)this.processMutation(e)}processMutation(t){const e=t.attributeName;e&&this.refreshAttribute(e,t.oldValue)}refreshAttribute(t,e){const r=this.delegate.getStringMapKeyForAttribute(t);if(null!=r){this.stringMap.has(t)||this.stringMapKeyAdded(r,t);const n=this.element.getAttribute(t);if(this.stringMap.get(t)!=n&&this.stringMapValueChanged(n,r,e),null==n){const e=this.stringMap.get(t);this.stringMap.delete(t),e&&this.stringMapKeyRemoved(r,t,e)}else this.stringMap.set(t,n)}}stringMapKeyAdded(t,e){this.delegate.stringMapKeyAdded&&this.delegate.stringMapKeyAdded(t,e)}stringMapValueChanged(t,e,r){this.delegate.stringMapValueChanged&&this.delegate.stringMapValueChanged(t,e,r)}stringMapKeyRemoved(t,e,r){this.delegate.stringMapKeyRemoved&&this.delegate.stringMapKeyRemoved(t,e,r)}get knownAttributeNames(){return Array.from(new Set(this.currentAttributeNames.concat(this.recordedAttributeNames)))}get currentAttributeNames(){return Array.from(this.element.attributes).map((t=>t.name))}get recordedAttributeNames(){return Array.from(this.stringMap.keys())}}function v(t,e,r){y(t,e).add(r)}function b(t,e,r){y(t,e).delete(r),function(t,e){const r=t.get(e);null!=r&&0==r.size&&t.delete(e)}(t,e)}function y(t,e){let r=t.get(e);return r||(r=new Set,t.set(e,r)),r}class w{constructor(){this.valuesByKey=new Map}get keys(){return Array.from(this.valuesByKey.keys())}get values(){return Array.from(this.valuesByKey.values()).reduce(((t,e)=>t.concat(Array.from(e))),[])}get size(){return Array.from(this.valuesByKey.values()).reduce(((t,e)=>t+e.size),0)}add(t,e){v(this.valuesByKey,t,e)}delete(t,e){b(this.valuesByKey,t,e)}has(t,e){const r=this.valuesByKey.get(t);return null!=r&&r.has(e)}hasKey(t){return this.valuesByKey.has(t)}hasValue(t){return Array.from(this.valuesByKey.values()).some((e=>e.has(t)))}getValuesForKey(t){const e=this.valuesByKey.get(t);return e?Array.from(e):[]}getKeysForValue(t){return Array.from(this.valuesByKey).filter((([e,r])=>r.has(t))).map((([t,e])=>t))}}class O{constructor(t,e,r){this.attributeObserver=new m(t,e,this),this.delegate=r,this.tokensByElement=new w}get started(){return this.attributeObserver.started}start(){this.attributeObserver.start()}pause(t){this.attributeObserver.pause(t)}stop(){this.attributeObserver.stop()}refresh(){this.attributeObserver.refresh()}get element(){return this.attributeObserver.element}get attributeName(){return this.attributeObserver.attributeName}elementMatchedAttribute(t){this.tokensMatched(this.readTokensForElement(t))}elementAttributeValueChanged(t){const[e,r]=this.refreshTokensForElement(t);this.tokensUnmatched(e),this.tokensMatched(r)}elementUnmatchedAttribute(t){this.tokensUnmatched(this.tokensByElement.getValuesForKey(t))}tokensMatched(t){t.forEach((t=>this.tokenMatched(t)))}tokensUnmatched(t){t.forEach((t=>this.tokenUnmatched(t)))}tokenMatched(t){this.delegate.tokenMatched(t),this.tokensByElement.add(t.element,t)}tokenUnmatched(t){this.delegate.tokenUnmatched(t),this.tokensByElement.delete(t.element,t)}refreshTokensForElement(t){const e=this.tokensByElement.getValuesForKey(t),r=this.readTokensForElement(t),n=function(t,e){const r=Math.max(t.length,e.length);return Array.from({length:r},((r,n)=>[t[n],e[n]]))}(e,r).findIndex((([t,e])=>{return n=e,!((r=t)&&n&&r.index==n.index&&r.content==n.content);var r,n}));return-1==n?[[],[]]:[e.slice(n),r.slice(n)]}readTokensForElement(t){const e=this.attributeName;return function(t,e,r){return t.trim().split(/\s+/).filter((t=>t.length)).map(((t,n)=>({element:e,attributeName:r,content:t,index:n})))}(t.getAttribute(e)||"",t,e)}}class E{constructor(t,e,r){this.tokenListObserver=new O(t,e,this),this.delegate=r,this.parseResultsByToken=new WeakMap,this.valuesByTokenByElement=new WeakMap}get started(){return this.tokenListObserver.started}start(){this.tokenListObserver.start()}stop(){this.tokenListObserver.stop()}refresh(){this.tokenListObserver.refresh()}get element(){return this.tokenListObserver.element}get attributeName(){return this.tokenListObserver.attributeName}tokenMatched(t){const{element:e}=t,{value:r}=this.fetchParseResultForToken(t);r&&(this.fetchValuesByTokenForElement(e).set(t,r),this.delegate.elementMatchedValue(e,r))}tokenUnmatched(t){const{element:e}=t,{value:r}=this.fetchParseResultForToken(t);r&&(this.fetchValuesByTokenForElement(e).delete(t),this.delegate.elementUnmatchedValue(e,r))}fetchParseResultForToken(t){let e=this.parseResultsByToken.get(t);return e||(e=this.parseToken(t),this.parseResultsByToken.set(t,e)),e}fetchValuesByTokenForElement(t){let e=this.valuesByTokenByElement.get(t);return e||(e=new Map,this.valuesByTokenByElement.set(t,e)),e}parseToken(t){try{return{value:this.delegate.parseValueForToken(t)}}catch(t){return{error:t}}}}class x{constructor(t,e){this.context=t,this.delegate=e,this.bindingsByAction=new Map}start(){this.valueListObserver||(this.valueListObserver=new E(this.element,this.actionAttribute,this),this.valueListObserver.start())}stop(){this.valueListObserver&&(this.valueListObserver.stop(),delete this.valueListObserver,this.disconnectAllActions())}get element(){return this.context.element}get identifier(){return this.context.identifier}get actionAttribute(){return this.schema.actionAttribute}get schema(){return this.context.schema}get bindings(){return Array.from(this.bindingsByAction.values())}connectAction(t){const e=new f(this.context,t);this.bindingsByAction.set(t,e),this.delegate.bindingConnected(e)}disconnectAction(t){const e=this.bindingsByAction.get(t);e&&(this.bindingsByAction.delete(t),this.delegate.bindingDisconnected(e))}disconnectAllActions(){this.bindings.forEach((t=>this.delegate.bindingDisconnected(t))),this.bindingsByAction.clear()}parseValueForToken(t){const e=class{constructor(t,e,r){this.element=t,this.index=e,this.eventTarget=r.eventTarget||t,this.eventName=r.eventName||function(t){const e=t.tagName.toLowerCase();if(e in l)return l[e](t)}(t)||h("missing event name"),this.eventOptions=r.eventOptions||{},this.identifier=r.identifier||h("missing identifier"),this.methodName=r.methodName||h("missing method name")}static forToken(t){return new this(t.element,t.index,function(t){const e=t.trim().match(o)||[];return{eventTarget:s(e[4]),eventName:e[2],eventOptions:e[9]?(r=e[9],r.split(":").reduce(((t,e)=>Object.assign(t,{[e.replace(/^!/,"")]:!/^!/.test(e)})),{})):{},identifier:e[5],methodName:e[7]};var r}(t.content))}toString(){const t=this.eventTargetName?`@${this.eventTargetName}`:"";return`${this.eventName}${t}->${this.identifier}#${this.methodName}`}get params(){return this.eventTarget instanceof Element?this.getParamsFromEventTargetAttributes(this.eventTarget):{}}getParamsFromEventTargetAttributes(t){const e={},r=new RegExp(`^data-${this.identifier}-(.+)-param$`);return Array.from(t.attributes).forEach((({name:t,value:n})=>{const i=t.match(r),o=i&&i[1];o&&Object.assign(e,{[a(o)]:d(n)})})),e}get eventTargetName(){return(t=this.eventTarget)==window?"window":t==document?"document":void 0;var t}}.forToken(t);if(e.identifier==this.identifier)return e}elementMatchedValue(t,e){this.connectAction(e)}elementUnmatchedValue(t,e){this.disconnectAction(e)}}class k{constructor(t,e){this.context=t,this.receiver=e,this.stringMapObserver=new g(this.element,this),this.valueDescriptorMap=this.controller.valueDescriptorMap,this.invokeChangedCallbacksForDefaultValues()}start(){this.stringMapObserver.start()}stop(){this.stringMapObserver.stop()}get element(){return this.context.element}get controller(){return this.context.controller}getStringMapKeyForAttribute(t){if(t in this.valueDescriptorMap)return this.valueDescriptorMap[t].name}stringMapKeyAdded(t,e){const r=this.valueDescriptorMap[e];this.hasValue(t)||this.invokeChangedCallback(t,r.writer(this.receiver[t]),r.writer(r.defaultValue))}stringMapValueChanged(t,e,r){const n=this.valueDescriptorNameMap[e];null!==t&&(null===r&&(r=n.writer(n.defaultValue)),this.invokeChangedCallback(e,t,r))}stringMapKeyRemoved(t,e,r){const n=this.valueDescriptorNameMap[t];this.hasValue(t)?this.invokeChangedCallback(t,n.writer(this.receiver[t]),r):this.invokeChangedCallback(t,n.writer(n.defaultValue),r)}invokeChangedCallbacksForDefaultValues(){for(const{key:t,name:e,defaultValue:r,writer:n}of this.valueDescriptors)null==r||this.controller.data.has(t)||this.invokeChangedCallback(e,n(r),void 0)}invokeChangedCallback(t,e,r){const n=`${t}Changed`,i=this.receiver[n];if("function"==typeof i){const n=this.valueDescriptorNameMap[t],o=n.reader(e);let s=r;r&&(s=n.reader(r)),i.call(this.receiver,o,s)}}get valueDescriptors(){const{valueDescriptorMap:t}=this;return Object.keys(t).map((e=>t[e]))}get valueDescriptorNameMap(){const t={};return Object.keys(this.valueDescriptorMap).forEach((e=>{const r=this.valueDescriptorMap[e];t[r.name]=r})),t}hasValue(t){const e=`has${c(this.valueDescriptorNameMap[t].name)}`;return this.receiver[e]}}class A{constructor(t,e){this.context=t,this.delegate=e,this.targetsByName=new w}start(){this.tokenListObserver||(this.tokenListObserver=new O(this.element,this.attributeName,this),this.tokenListObserver.start())}stop(){this.tokenListObserver&&(this.disconnectAllTargets(),this.tokenListObserver.stop(),delete this.tokenListObserver)}tokenMatched({element:t,content:e}){this.scope.containsElement(t)&&this.connectTarget(t,e)}tokenUnmatched({element:t,content:e}){this.disconnectTarget(t,e)}connectTarget(t,e){var r;this.targetsByName.has(e,t)||(this.targetsByName.add(e,t),null===(r=this.tokenListObserver)||void 0===r||r.pause((()=>this.delegate.targetConnected(t,e))))}disconnectTarget(t,e){var r;this.targetsByName.has(e,t)&&(this.targetsByName.delete(e,t),null===(r=this.tokenListObserver)||void 0===r||r.pause((()=>this.delegate.targetDisconnected(t,e))))}disconnectAllTargets(){for(const t of this.targetsByName.keys)for(const e of this.targetsByName.getValuesForKey(t))this.disconnectTarget(e,t)}get attributeName(){return`data-${this.context.identifier}-target`}get element(){return this.context.element}get scope(){return this.context.scope}}class S{constructor(t,e){this.logDebugActivity=(t,e={})=>{const{identifier:r,controller:n,element:i}=this;e=Object.assign({identifier:r,controller:n,element:i},e),this.application.logDebugActivity(this.identifier,t,e)},this.module=t,this.scope=e,this.controller=new t.controllerConstructor(this),this.bindingObserver=new x(this,this.dispatcher),this.valueObserver=new k(this,this.controller),this.targetObserver=new A(this,this);try{this.controller.initialize(),this.logDebugActivity("initialize")}catch(t){this.handleError(t,"initializing controller")}}connect(){this.bindingObserver.start(),this.valueObserver.start(),this.targetObserver.start();try{this.controller.connect(),this.logDebugActivity("connect")}catch(t){this.handleError(t,"connecting controller")}}disconnect(){try{this.controller.disconnect(),this.logDebugActivity("disconnect")}catch(t){this.handleError(t,"disconnecting controller")}this.targetObserver.stop(),this.valueObserver.stop(),this.bindingObserver.stop()}get application(){return this.module.application}get identifier(){return this.module.identifier}get schema(){return this.application.schema}get dispatcher(){return this.application.dispatcher}get element(){return this.scope.element}get parentElement(){return this.element.parentElement}handleError(t,e,r={}){const{identifier:n,controller:i,element:o}=this;r=Object.assign({identifier:n,controller:i,element:o},r),this.application.handleError(t,`Error ${e}`,r)}targetConnected(t,e){this.invokeControllerMethod(`${e}TargetConnected`,t)}targetDisconnected(t,e){this.invokeControllerMethod(`${e}TargetDisconnected`,t)}invokeControllerMethod(t,...e){const r=this.controller;"function"==typeof r[t]&&r[t](...e)}}function M(t,e){const r=j(t);return Array.from(r.reduce(((t,r)=>(function(t,e){const r=t[e];return Array.isArray(r)?r:[]}(r,e).forEach((e=>t.add(e))),t)),new Set))}function T(t,e){return j(t).reduce(((t,r)=>(t.push(...function(t,e){const r=t[e];return r?Object.keys(r).map((t=>[t,r[t]])):[]}(r,e)),t)),[])}function j(t){const e=[];for(;t;)e.push(t),t=Object.getPrototypeOf(t);return e.reverse()}function N(t){return function(t,e){const r=L(t),n=function(t,e){return C(e).reduce(((r,n)=>{const i=function(t,e,r){const n=Object.getOwnPropertyDescriptor(t,r);if(!n||!("value"in n)){const t=Object.getOwnPropertyDescriptor(e,r).value;return n&&(t.get=n.get||t.get,t.set=n.set||t.set),t}}(t,e,n);return i&&Object.assign(r,{[n]:i}),r}),{})}(t.prototype,e);return Object.defineProperties(r.prototype,n),r}(t,function(t){return M(t,"blessings").reduce(((e,r)=>{const n=r(t);for(const t in n){const r=e[t]||{};e[t]=Object.assign(r,n[t])}return e}),{})}(t))}const C="function"==typeof Object.getOwnPropertySymbols?t=>[...Object.getOwnPropertyNames(t),...Object.getOwnPropertySymbols(t)]:Object.getOwnPropertyNames,L=(()=>{function t(t){function e(){return Reflect.construct(t,arguments,new.target)}return e.prototype=Object.create(t.prototype,{constructor:{value:e}}),Reflect.setPrototypeOf(e,t),e}try{return function(){const e=t((function(){this.a.call(this)}));e.prototype.a=function(){},new e}(),t}catch(t){return t=>class extends t{}}})();class F{constructor(t,e){this.application=t,this.definition=function(t){return{identifier:t.identifier,controllerConstructor:N(t.controllerConstructor)}}(e),this.contextsByScope=new WeakMap,this.connectedContexts=new Set}get identifier(){return this.definition.identifier}get controllerConstructor(){return this.definition.controllerConstructor}get contexts(){return Array.from(this.connectedContexts)}connectContextForScope(t){const e=this.fetchContextForScope(t);this.connectedContexts.add(e),e.connect()}disconnectContextForScope(t){const e=this.contextsByScope.get(t);e&&(this.connectedContexts.delete(e),e.disconnect())}fetchContextForScope(t){let e=this.contextsByScope.get(t);return e||(e=new S(this,t),this.contextsByScope.set(t,e)),e}}class B{constructor(t){this.scope=t}has(t){return this.data.has(this.getDataKey(t))}get(t){return this.getAll(t)[0]}getAll(t){const e=this.data.get(this.getDataKey(t))||"";return e.match(/[^\s]+/g)||[]}getAttributeName(t){return this.data.getAttributeNameForKey(this.getDataKey(t))}getDataKey(t){return`${t}-class`}get data(){return this.scope.data}}class D{constructor(t){this.scope=t}get element(){return this.scope.element}get identifier(){return this.scope.identifier}get(t){const e=this.getAttributeNameForKey(t);return this.element.getAttribute(e)}set(t,e){const r=this.getAttributeNameForKey(t);return this.element.setAttribute(r,e),this.get(t)}has(t){const e=this.getAttributeNameForKey(t);return this.element.hasAttribute(e)}delete(t){if(this.has(t)){const e=this.getAttributeNameForKey(t);return this.element.removeAttribute(e),!0}return!1}getAttributeNameForKey(t){return`data-${this.identifier}-${u(t)}`}}class ${constructor(t){this.warnedKeysByObject=new WeakMap,this.logger=t}warn(t,e,r){let n=this.warnedKeysByObject.get(t);n||(n=new Set,this.warnedKeysByObject.set(t,n)),n.has(e)||(n.add(e),this.logger.warn(r,t))}}function P(t,e){return`[${t}~="${e}"]`}class I{constructor(t){this.scope=t}get element(){return this.scope.element}get identifier(){return this.scope.identifier}get schema(){return this.scope.schema}has(t){return null!=this.find(t)}find(...t){return t.reduce(((t,e)=>t||this.findTarget(e)||this.findLegacyTarget(e)),void 0)}findAll(...t){return t.reduce(((t,e)=>[...t,...this.findAllTargets(e),...this.findAllLegacyTargets(e)]),[])}findTarget(t){const e=this.getSelectorForTargetName(t);return this.scope.findElement(e)}findAllTargets(t){const e=this.getSelectorForTargetName(t);return this.scope.findAllElements(e)}getSelectorForTargetName(t){return P(this.schema.targetAttributeForScope(this.identifier),t)}findLegacyTarget(t){const e=this.getLegacySelectorForTargetName(t);return this.deprecate(this.scope.findElement(e),t)}findAllLegacyTargets(t){const e=this.getLegacySelectorForTargetName(t);return this.scope.findAllElements(e).map((e=>this.deprecate(e,t)))}getLegacySelectorForTargetName(t){const e=`${this.identifier}.${t}`;return P(this.schema.targetAttribute,e)}deprecate(t,e){if(t){const{identifier:r}=this,n=this.schema.targetAttribute,i=this.schema.targetAttributeForScope(r);this.guide.warn(t,`target:${e}`,`Please replace ${n}="${r}.${e}" with ${i}="${e}". The ${n} attribute is deprecated and will be removed in a future version of Stimulus.`)}return t}get guide(){return this.scope.guide}}class V{constructor(t,e,r,n){this.targets=new I(this),this.classes=new B(this),this.data=new D(this),this.containsElement=t=>t.closest(this.controllerSelector)===this.element,this.schema=t,this.element=e,this.identifier=r,this.guide=new $(n)}findElement(t){return this.element.matches(t)?this.element:this.queryElements(t).find(this.containsElement)}findAllElements(t){return[...this.element.matches(t)?[this.element]:[],...this.queryElements(t).filter(this.containsElement)]}queryElements(t){return Array.from(this.element.querySelectorAll(t))}get controllerSelector(){return P(this.schema.controllerAttribute,this.identifier)}}class K{constructor(t,e,r){this.element=t,this.schema=e,this.delegate=r,this.valueListObserver=new E(this.element,this.controllerAttribute,this),this.scopesByIdentifierByElement=new WeakMap,this.scopeReferenceCounts=new WeakMap}start(){this.valueListObserver.start()}stop(){this.valueListObserver.stop()}get controllerAttribute(){return this.schema.controllerAttribute}parseValueForToken(t){const{element:e,content:r}=t,n=this.fetchScopesByIdentifierForElement(e);let i=n.get(r);return i||(i=this.delegate.createScopeForElementAndIdentifier(e,r),n.set(r,i)),i}elementMatchedValue(t,e){const r=(this.scopeReferenceCounts.get(e)||0)+1;this.scopeReferenceCounts.set(e,r),1==r&&this.delegate.scopeConnected(e)}elementUnmatchedValue(t,e){const r=this.scopeReferenceCounts.get(e);r&&(this.scopeReferenceCounts.set(e,r-1),1==r&&this.delegate.scopeDisconnected(e))}fetchScopesByIdentifierForElement(t){let e=this.scopesByIdentifierByElement.get(t);return e||(e=new Map,this.scopesByIdentifierByElement.set(t,e)),e}}class R{constructor(t){this.application=t,this.scopeObserver=new K(this.element,this.schema,this),this.scopesByIdentifier=new w,this.modulesByIdentifier=new Map}get element(){return this.application.element}get schema(){return this.application.schema}get logger(){return this.application.logger}get controllerAttribute(){return this.schema.controllerAttribute}get modules(){return Array.from(this.modulesByIdentifier.values())}get contexts(){return this.modules.reduce(((t,e)=>t.concat(e.contexts)),[])}start(){this.scopeObserver.start()}stop(){this.scopeObserver.stop()}loadDefinition(t){this.unloadIdentifier(t.identifier);const e=new F(this.application,t);this.connectModule(e)}unloadIdentifier(t){const e=this.modulesByIdentifier.get(t);e&&this.disconnectModule(e)}getContextForElementAndIdentifier(t,e){const r=this.modulesByIdentifier.get(e);if(r)return r.contexts.find((e=>e.element==t))}handleError(t,e,r){this.application.handleError(t,e,r)}createScopeForElementAndIdentifier(t,e){return new V(this.schema,t,e,this.logger)}scopeConnected(t){this.scopesByIdentifier.add(t.identifier,t);const e=this.modulesByIdentifier.get(t.identifier);e&&e.connectContextForScope(t)}scopeDisconnected(t){this.scopesByIdentifier.delete(t.identifier,t);const e=this.modulesByIdentifier.get(t.identifier);e&&e.disconnectContextForScope(t)}connectModule(t){this.modulesByIdentifier.set(t.identifier,t);this.scopesByIdentifier.getValuesForKey(t.identifier).forEach((e=>t.connectContextForScope(e)))}disconnectModule(t){this.modulesByIdentifier.delete(t.identifier);this.scopesByIdentifier.getValuesForKey(t.identifier).forEach((e=>t.disconnectContextForScope(e)))}}const z={controllerAttribute:"data-controller",actionAttribute:"data-action",targetAttribute:"data-target",targetAttributeForScope:t=>`data-${t}-target`};class _{constructor(t=document.documentElement,e=z){this.logger=console,this.debug=!1,this.logDebugActivity=(t,e,r={})=>{this.debug&&this.logFormattedMessage(t,e,r)},this.element=t,this.schema=e,this.dispatcher=new i(this),this.router=new R(this)}static start(t,e){const r=new _(t,e);return r.start(),r}async start(){await new Promise((t=>{"loading"==document.readyState?document.addEventListener("DOMContentLoaded",(()=>t())):t()})),this.logDebugActivity("application","starting"),this.dispatcher.start(),this.router.start(),this.logDebugActivity("application","start")}stop(){this.logDebugActivity("application","stopping"),this.dispatcher.stop(),this.router.stop(),this.logDebugActivity("application","stop")}register(t,e){e.shouldLoad&&this.load({identifier:t,controllerConstructor:e})}load(t,...e){(Array.isArray(t)?t:[t,...e]).forEach((t=>this.router.loadDefinition(t)))}unload(t,...e){(Array.isArray(t)?t:[t,...e]).forEach((t=>this.router.unloadIdentifier(t)))}get controllers(){return this.router.contexts.map((t=>t.controller))}getControllerForElementAndIdentifier(t,e){const r=this.router.getContextForElementAndIdentifier(t,e);return r?r.controller:null}handleError(t,e,r){var n;this.logger.error("%s\n\n%o\n\n%o",e,t,r),null===(n=window.onerror)||void 0===n||n.call(window,e,"",0,0,t)}logFormattedMessage(t,e,r={}){r=Object.assign({application:this},r),this.logger.groupCollapsed(`${t} #${e}`),this.logger.log("details:",Object.assign({},r)),this.logger.groupEnd()}}function q([t,e]){return function(t,e){const r=`${u(t)}-value`,n=function(t){const e=function(t){const e=U(t.type);if(e){const r=W(t.default);if(e!==r)throw new Error(`Type "${e}" must match the type of the default value. Given default value: "${t.default}" as "${r}"`);return e}}(t),r=W(t),n=U(t),i=e||r||n;if(i)return i;throw new Error(`Unknown value type "${t}"`)}(e);return{type:n,key:r,name:a(r),get defaultValue(){return function(t){const e=U(t);if(e)return H[e];const r=t.default;return void 0!==r?r:t}(e)},get hasCustomDefaultValue(){return void 0!==W(e)},reader:G[n],writer:Y[n]||Y.default}}(t,e)}function U(t){switch(t){case Array:return"array";case Boolean:return"boolean";case Number:return"number";case Object:return"object";case String:return"string"}}function W(t){switch(typeof t){case"boolean":return"boolean";case"number":return"number";case"string":return"string"}return Array.isArray(t)?"array":"[object Object]"===Object.prototype.toString.call(t)?"object":void 0}const H={get array(){return[]},boolean:!1,number:0,get object(){return{}},string:""},G={array(t){const e=JSON.parse(t);if(!Array.isArray(e))throw new TypeError("Expected array");return e},boolean:t=>!("0"==t||"false"==t),number:t=>Number(t),object(t){const e=JSON.parse(t);if(null===e||"object"!=typeof e||Array.isArray(e))throw new TypeError("Expected object");return e},string:t=>t},Y={default:function(t){return`${t}`},array:J,object:J};function J(t){return JSON.stringify(t)}class Z{constructor(t){this.context=t}static get shouldLoad(){return!0}get application(){return this.context.application}get scope(){return this.context.scope}get element(){return this.scope.element}get identifier(){return this.scope.identifier}get targets(){return this.scope.targets}get classes(){return this.scope.classes}get data(){return this.scope.data}initialize(){}connect(){}disconnect(){}dispatch(t,{target:e=this.element,detail:r={},prefix:n=this.identifier,bubbles:i=!0,cancelable:o=!0}={}){const s=new CustomEvent(n?`${n}:${t}`:t,{detail:r,bubbles:i,cancelable:o});return e.dispatchEvent(s),s}}Z.blessings=[function(t){return M(t,"classes").reduce(((t,e)=>{return Object.assign(t,{[`${r=e}Class`]:{get(){const{classes:t}=this;if(t.has(r))return t.get(r);{const e=t.getAttributeName(r);throw new Error(`Missing attribute "${e}"`)}}},[`${r}Classes`]:{get(){return this.classes.getAll(r)}},[`has${c(r)}Class`]:{get(){return this.classes.has(r)}}});var r}),{})},function(t){return M(t,"targets").reduce(((t,e)=>{return Object.assign(t,{[`${r=e}Target`]:{get(){const t=this.targets.find(r);if(t)return t;throw new Error(`Missing target element "${r}" for "${this.identifier}" controller`)}},[`${r}Targets`]:{get(){return this.targets.findAll(r)}},[`has${c(r)}Target`]:{get(){return this.targets.has(r)}}});var r}),{})},function(t){const e=T(t,"values"),r={valueDescriptorMap:{get(){return e.reduce(((t,e)=>{const r=q(e),n=this.data.getAttributeNameForKey(r.key);return Object.assign(t,{[n]:r})}),{})}}};return e.reduce(((t,e)=>Object.assign(t,function(t){const e=q(t),{key:r,name:n,reader:i,writer:o}=e;return{[n]:{get(){const t=this.data.get(r);return null!==t?i(t):e.defaultValue},set(t){void 0===t?this.data.delete(r):this.data.set(r,o(t))}},[`has${c(n)}`]:{get(){return this.data.has(r)||e.hasCustomDefaultValue}}}}(e))),r)}],Z.targets=[],Z.values={};var X=r(8205);function Q(t){return t.keys().map((e=>function(t,e){const r=function(t){const e=(t.match(/^(?:\.\/)?(.+)(?:[_-]controller\..+?)$/)||[])[1];if(e)return e.replace(/_/g,"-").replace(/\//g,"--")}(e);if(r)return function(t,e){const r=t.default;if("function"==typeof r)return{identifier:e,controllerConstructor:r}}(t(e),r)}(t,e))).filter((t=>t))}function tt(t){const e=_.start();t&&e.load(Q(t));for(const t in X.Z)X.Z.hasOwnProperty(t)&&X.Z[t].then((r=>{e.register(t,r.default)}));return e}},2711:function(t,e,r){t.exports=function(){"use strict";var t="undefined"!=typeof window?window:void 0!==r.g?r.g:"undefined"!=typeof self?self:{},e="Expected a function",n=NaN,i="[object Symbol]",o=/^\s+|\s+$/g,s=/^[-+]0x[0-9a-f]+$/i,a=/^0b[01]+$/i,c=/^0o[0-7]+$/i,u=parseInt,l="object"==typeof t&&t&&t.Object===Object&&t,h="object"==typeof self&&self&&self.Object===Object&&self,d=l||h||Function("return this")(),f=Object.prototype.toString,p=Math.max,m=Math.min,g=function(){return d.Date.now()};function v(t,r,n){var i,o,s,a,c,u,l=0,h=!1,d=!1,f=!0;if("function"!=typeof t)throw new TypeError(e);function v(e){var r=i,n=o;return i=o=void 0,l=e,a=t.apply(n,r)}function w(t){var e=t-u;return void 0===u||e>=r||e<0||d&&t-l>=s}function O(){var t=g();if(w(t))return E(t);c=setTimeout(O,function(t){var e=r-(t-u);return d?m(e,s-(t-l)):e}(t))}function E(t){return c=void 0,f&&i?v(t):(i=o=void 0,a)}function x(){var t=g(),e=w(t);if(i=arguments,o=this,u=t,e){if(void 0===c)return function(t){return l=t,c=setTimeout(O,r),h?v(t):a}(u);if(d)return c=setTimeout(O,r),v(u)}return void 0===c&&(c=setTimeout(O,r)),a}return r=y(r)||0,b(n)&&(h=!!n.leading,s=(d="maxWait"in n)?p(y(n.maxWait)||0,r):s,f="trailing"in n?!!n.trailing:f),x.cancel=function(){void 0!==c&&clearTimeout(c),l=0,i=u=o=c=void 0},x.flush=function(){return void 0===c?a:E(g())},x}function b(t){var e=typeof t;return!!t&&("object"==e||"function"==e)}function y(t){if("number"==typeof t)return t;if(function(t){return"symbol"==typeof t||function(t){return!!t&&"object"==typeof t}(t)&&f.call(t)==i}(t))return n;if(b(t)){var e="function"==typeof t.valueOf?t.valueOf():t;t=b(e)?e+"":e}if("string"!=typeof t)return 0===t?t:+t;t=t.replace(o,"");var r=a.test(t);return r||c.test(t)?u(t.slice(2),r?2:8):s.test(t)?n:+t}var w=function(t,r,n){var i=!0,o=!0;if("function"!=typeof t)throw new TypeError(e);return b(n)&&(i="leading"in n?!!n.leading:i,o="trailing"in n?!!n.trailing:o),v(t,r,{leading:i,maxWait:r,trailing:o})},O="Expected a function",E=NaN,x="[object Symbol]",k=/^\s+|\s+$/g,A=/^[-+]0x[0-9a-f]+$/i,S=/^0b[01]+$/i,M=/^0o[0-7]+$/i,T=parseInt,j="object"==typeof t&&t&&t.Object===Object&&t,N="object"==typeof self&&self&&self.Object===Object&&self,C=j||N||Function("return this")(),L=Object.prototype.toString,F=Math.max,B=Math.min,D=function(){return C.Date.now()};function $(t){var e=typeof t;return!!t&&("object"==e||"function"==e)}function P(t){if("number"==typeof t)return t;if(function(t){return"symbol"==typeof t||function(t){return!!t&&"object"==typeof t}(t)&&L.call(t)==x}(t))return E;if($(t)){var e="function"==typeof t.valueOf?t.valueOf():t;t=$(e)?e+"":e}if("string"!=typeof t)return 0===t?t:+t;t=t.replace(k,"");var r=S.test(t);return r||M.test(t)?T(t.slice(2),r?2:8):A.test(t)?E:+t}var I=function(t,e,r){var n,i,o,s,a,c,u=0,l=!1,h=!1,d=!0;if("function"!=typeof t)throw new TypeError(O);function f(e){var r=n,o=i;return n=i=void 0,u=e,s=t.apply(o,r)}function p(t){var r=t-c;return void 0===c||r>=e||r<0||h&&t-u>=o}function m(){var t=D();if(p(t))return g(t);a=setTimeout(m,function(t){var r=e-(t-c);return h?B(r,o-(t-u)):r}(t))}function g(t){return a=void 0,d&&n?f(t):(n=i=void 0,s)}function v(){var t=D(),r=p(t);if(n=arguments,i=this,c=t,r){if(void 0===a)return function(t){return u=t,a=setTimeout(m,e),l?f(t):s}(c);if(h)return a=setTimeout(m,e),f(c)}return void 0===a&&(a=setTimeout(m,e)),s}return e=P(e)||0,$(r)&&(l=!!r.leading,o=(h="maxWait"in r)?F(P(r.maxWait)||0,e):o,d="trailing"in r?!!r.trailing:d),v.cancel=function(){void 0!==a&&clearTimeout(a),u=0,n=c=i=a=void 0},v.flush=function(){return void 0===a?s:g(D())},v},V=function(){};function K(t){t&&t.forEach((function(t){var e=Array.prototype.slice.call(t.addedNodes),r=Array.prototype.slice.call(t.removedNodes);if(function t(e){var r=void 0,n=void 0;for(r=0;r=n.out&&!r.once?o():e>=n.in?t.animated||(function(t,e){e&&e.forEach((function(e){return t.classList.add(e)}))}(i,r.animatedClassNames),X("aos:in",i),t.options.id&&X("aos:in:"+t.options.id,i),t.animated=!0):t.animated&&!r.once&&o()}(t,window.pageYOffset)}))},tt=function(t){for(var e=0,r=0;t&&!isNaN(t.offsetLeft)&&!isNaN(t.offsetTop);)e+=t.offsetLeft-("BODY"!=t.tagName?t.scrollLeft:0),r+=t.offsetTop-("BODY"!=t.tagName?t.scrollTop:0),t=t.offsetParent;return{top:r,left:e}},et=function(t,e,r){var n=t.getAttribute("data-aos-"+e);if(void 0!==n){if("true"===n)return!0;if("false"===n)return!1}return n||r},rt=function(t,e){return t.forEach((function(t,r){var n=et(t.node,"mirror",e.mirror),i=et(t.node,"once",e.once),o=et(t.node,"id"),s=e.useClassNames&&t.node.getAttribute("data-aos"),a=[e.animatedClassName].concat(s?s.split(" "):[]).filter((function(t){return"string"==typeof t}));e.initClassName&&t.node.classList.add(e.initClassName),t.position={in:function(t,e,r){var n=window.innerHeight,i=et(t,"anchor"),o=et(t,"anchor-placement"),s=Number(et(t,"offset",o?0:e)),a=o||r,c=t;i&&document.querySelectorAll(i)&&(c=document.querySelectorAll(i)[0]);var u=tt(c).top-n;switch(a){case"top-bottom":break;case"center-bottom":u+=c.offsetHeight/2;break;case"bottom-bottom":u+=c.offsetHeight;break;case"top-center":u+=n/2;break;case"center-center":u+=n/2+c.offsetHeight/2;break;case"bottom-center":u+=n/2+c.offsetHeight;break;case"top-top":u+=n;break;case"bottom-top":u+=n+c.offsetHeight;break;case"center-top":u+=n+c.offsetHeight/2}return u+s}(t.node,e.offset,e.anchorPlacement),out:n&&function(t,e){window.innerHeight;var r=et(t,"anchor"),n=et(t,"offset",e),i=t;return r&&document.querySelectorAll(r)&&(i=document.querySelectorAll(r)[0]),tt(i).top+i.offsetHeight-n}(t.node,e.offset)},t.options={once:i,mirror:n,animatedClassNames:a,id:o}})),t},nt=function(){var t=document.querySelectorAll("[data-aos]");return Array.prototype.map.call(t,(function(t){return{node:t}}))},it=[],ot=!1,st={offset:120,delay:0,easing:"ease",duration:400,disable:!1,once:!1,mirror:!1,anchorPlacement:"top-bottom",startEvent:"DOMContentLoaded",animatedClassName:"aos-animate",initClassName:"aos-init",useClassNames:!1,disableMutationObserver:!1,throttleDelay:99,debounceDelay:50},at=function(){return document.all&&!window.atob},ct=function(){arguments.length>0&&void 0!==arguments[0]&&arguments[0]&&(ot=!0),ot&&(it=rt(it,st),Q(it),window.addEventListener("scroll",w((function(){Q(it,st.once)}),st.throttleDelay)))},ut=function(){if(it=nt(),ht(st.disable)||at())return lt();ct()},lt=function(){it.forEach((function(t,e){t.node.removeAttribute("data-aos"),t.node.removeAttribute("data-aos-easing"),t.node.removeAttribute("data-aos-duration"),t.node.removeAttribute("data-aos-delay"),st.initClassName&&t.node.classList.remove(st.initClassName),st.animatedClassName&&t.node.classList.remove(st.animatedClassName)}))},ht=function(t){return!0===t||"mobile"===t&&Z.mobile()||"phone"===t&&Z.phone()||"tablet"===t&&Z.tablet()||"function"==typeof t&&!0===t()};return{init:function(t){return st=U(st,t),it=nt(),st.disableMutationObserver||z.isSupported()||(console.info('\n aos: MutationObserver is not supported on this browser,\n code mutations observing has been disabled.\n You may have to call "refreshHard()" by yourself.\n '),st.disableMutationObserver=!0),st.disableMutationObserver||z.ready("[data-aos]",ut),ht(st.disable)||at()?lt():(document.querySelector("body").setAttribute("data-aos-easing",st.easing),document.querySelector("body").setAttribute("data-aos-duration",st.duration),document.querySelector("body").setAttribute("data-aos-delay",st.delay),-1===["DOMContentLoaded","load"].indexOf(st.startEvent)?document.addEventListener(st.startEvent,(function(){ct(!0)})):window.addEventListener("load",(function(){ct(!0)})),"DOMContentLoaded"===st.startEvent&&["complete","interactive"].indexOf(document.readyState)>-1&&ct(!0),window.addEventListener("resize",I(ct,st.debounceDelay,!0)),window.addEventListener("orientationchange",I(ct,st.debounceDelay,!0)),it)},refresh:ct,refreshHard:ut}}()},9662:(t,e,r)=>{var n=r(614),i=r(6330),o=TypeError;t.exports=function(t){if(n(t))return t;throw o(i(t)+" is not a function")}},6077:(t,e,r)=>{var n=r(614),i=String,o=TypeError;t.exports=function(t){if("object"==typeof t||n(t))return t;throw o("Can't set "+i(t)+" as a prototype")}},9670:(t,e,r)=>{var n=r(111),i=String,o=TypeError;t.exports=function(t){if(n(t))return t;throw o(i(t)+" is not an object")}},8533:(t,e,r)=>{"use strict";var n=r(2092).forEach,i=r(9341)("forEach");t.exports=i?[].forEach:function(t){return n(this,t,arguments.length>1?arguments[1]:void 0)}},1318:(t,e,r)=>{var n=r(5656),i=r(1400),o=r(6244),s=function(t){return function(e,r,s){var a,c=n(e),u=o(c),l=i(s,u);if(t&&r!=r){for(;u>l;)if((a=c[l++])!=a)return!0}else for(;u>l;l++)if((t||l in c)&&c[l]===r)return t||l||0;return!t&&-1}};t.exports={includes:s(!0),indexOf:s(!1)}},2092:(t,e,r)=>{var n=r(9974),i=r(1702),o=r(8361),s=r(7908),a=r(6244),c=r(5417),u=i([].push),l=function(t){var e=1==t,r=2==t,i=3==t,l=4==t,h=6==t,d=7==t,f=5==t||h;return function(p,m,g,v){for(var b,y,w=s(p),O=o(w),E=n(m,g),x=a(O),k=0,A=v||c,S=e?A(p,x):r||d?A(p,0):void 0;x>k;k++)if((f||k in O)&&(y=E(b=O[k],k,w),t))if(e)S[k]=y;else if(y)switch(t){case 3:return!0;case 5:return b;case 6:return k;case 2:u(S,b)}else switch(t){case 4:return!1;case 7:u(S,b)}return h?-1:i||l?l:S}};t.exports={forEach:l(0),map:l(1),filter:l(2),some:l(3),every:l(4),find:l(5),findIndex:l(6),filterReject:l(7)}},9341:(t,e,r)=>{"use strict";var n=r(7293);t.exports=function(t,e){var r=[][t];return!!r&&n((function(){r.call(null,e||function(){return 1},1)}))}},206:(t,e,r)=>{var n=r(1702);t.exports=n([].slice)},7475:(t,e,r)=>{var n=r(3157),i=r(4411),o=r(111),s=r(5112)("species"),a=Array;t.exports=function(t){var e;return n(t)&&(e=t.constructor,(i(e)&&(e===a||n(e.prototype))||o(e)&&null===(e=e[s]))&&(e=void 0)),void 0===e?a:e}},5417:(t,e,r)=>{var n=r(7475);t.exports=function(t,e){return new(n(t))(0===e?0:e)}},4326:(t,e,r)=>{var n=r(1702),i=n({}.toString),o=n("".slice);t.exports=function(t){return o(i(t),8,-1)}},648:(t,e,r)=>{var n=r(1694),i=r(614),o=r(4326),s=r(5112)("toStringTag"),a=Object,c="Arguments"==o(function(){return arguments}());t.exports=n?o:function(t){var e,r,n;return void 0===t?"Undefined":null===t?"Null":"string"==typeof(r=function(t,e){try{return t[e]}catch(t){}}(e=a(t),s))?r:c?o(e):"Object"==(n=o(e))&&i(e.callee)?"Arguments":n}},9920:(t,e,r)=>{var n=r(2597),i=r(3887),o=r(1236),s=r(3070);t.exports=function(t,e,r){for(var a=i(e),c=s.f,u=o.f,l=0;l{var n=r(9781),i=r(3070),o=r(9114);t.exports=n?function(t,e,r){return i.f(t,e,o(1,r))}:function(t,e,r){return t[e]=r,t}},9114:t=>{t.exports=function(t,e){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:e}}},8052:(t,e,r)=>{var n=r(614),i=r(3070),o=r(6339),s=r(3072);t.exports=function(t,e,r,a){a||(a={});var c=a.enumerable,u=void 0!==a.name?a.name:e;if(n(r)&&o(r,u,a),a.global)c?t[e]=r:s(e,r);else{try{a.unsafe?t[e]&&(c=!0):delete t[e]}catch(t){}c?t[e]=r:i.f(t,e,{value:r,enumerable:!1,configurable:!a.nonConfigurable,writable:!a.nonWritable})}return t}},3072:(t,e,r)=>{var n=r(7854),i=Object.defineProperty;t.exports=function(t,e){try{i(n,t,{value:e,configurable:!0,writable:!0})}catch(r){n[t]=e}return e}},9781:(t,e,r)=>{var n=r(7293);t.exports=!n((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]}))},4154:t=>{var e="object"==typeof document&&document.all,r=void 0===e&&void 0!==e;t.exports={all:e,IS_HTMLDDA:r}},317:(t,e,r)=>{var n=r(7854),i=r(111),o=n.document,s=i(o)&&i(o.createElement);t.exports=function(t){return s?o.createElement(t):{}}},8324:t=>{t.exports={CSSRuleList:0,CSSStyleDeclaration:0,CSSValueList:0,ClientRectList:0,DOMRectList:0,DOMStringList:0,DOMTokenList:1,DataTransferItemList:0,FileList:0,HTMLAllCollection:0,HTMLCollection:0,HTMLFormElement:0,HTMLSelectElement:0,MediaList:0,MimeTypeArray:0,NamedNodeMap:0,NodeList:1,PaintRequestList:0,Plugin:0,PluginArray:0,SVGLengthList:0,SVGNumberList:0,SVGPathSegList:0,SVGPointList:0,SVGStringList:0,SVGTransformList:0,SourceBufferList:0,StyleSheetList:0,TextTrackCueList:0,TextTrackList:0,TouchList:0}},8509:(t,e,r)=>{var n=r(317)("span").classList,i=n&&n.constructor&&n.constructor.prototype;t.exports=i===Object.prototype?void 0:i},8113:(t,e,r)=>{var n=r(5005);t.exports=n("navigator","userAgent")||""},7392:(t,e,r)=>{var n,i,o=r(7854),s=r(8113),a=o.process,c=o.Deno,u=a&&a.versions||c&&c.version,l=u&&u.v8;l&&(i=(n=l.split("."))[0]>0&&n[0]<4?1:+(n[0]+n[1])),!i&&s&&(!(n=s.match(/Edge\/(\d+)/))||n[1]>=74)&&(n=s.match(/Chrome\/(\d+)/))&&(i=+n[1]),t.exports=i},748:t=>{t.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},1060:(t,e,r)=>{var n=r(1702),i=Error,o=n("".replace),s=String(i("zxcasd").stack),a=/\n\s*at [^:]*:[^\n]*/,c=a.test(s);t.exports=function(t,e){if(c&&"string"==typeof t&&!i.prepareStackTrace)for(;e--;)t=o(t,a,"");return t}},2914:(t,e,r)=>{var n=r(7293),i=r(9114);t.exports=!n((function(){var t=Error("a");return!("stack"in t)||(Object.defineProperty(t,"stack",i(1,7)),7!==t.stack)}))},7762:(t,e,r)=>{"use strict";var n=r(9781),i=r(7293),o=r(9670),s=r(30),a=r(6277),c=Error.prototype.toString,u=i((function(){if(n){var t=s(Object.defineProperty({},"name",{get:function(){return this===t}}));if("true"!==c.call(t))return!0}return"2: 1"!==c.call({message:1,name:2})||"Error"!==c.call({})}));t.exports=u?function(){var t=o(this),e=a(t.name,"Error"),r=a(t.message);return e?r?e+": "+r:e:r}:c},2109:(t,e,r)=>{var n=r(7854),i=r(1236).f,o=r(8880),s=r(8052),a=r(3072),c=r(9920),u=r(4705);t.exports=function(t,e){var r,l,h,d,f,p=t.target,m=t.global,g=t.stat;if(r=m?n:g?n[p]||a(p,{}):(n[p]||{}).prototype)for(l in e){if(d=e[l],h=t.dontCallGetSet?(f=i(r,l))&&f.value:r[l],!u(m?l:p+(g?".":"#")+l,t.forced)&&void 0!==h){if(typeof d==typeof h)continue;c(d,h)}(t.sham||h&&h.sham)&&o(d,"sham",!0),s(r,l,d,t)}}},7293:t=>{t.exports=function(t){try{return!!t()}catch(t){return!0}}},2104:(t,e,r)=>{var n=r(4374),i=Function.prototype,o=i.apply,s=i.call;t.exports="object"==typeof Reflect&&Reflect.apply||(n?s.bind(o):function(){return s.apply(o,arguments)})},9974:(t,e,r)=>{var n=r(1470),i=r(9662),o=r(4374),s=n(n.bind);t.exports=function(t,e){return i(t),void 0===e?t:o?s(t,e):function(){return t.apply(e,arguments)}}},4374:(t,e,r)=>{var n=r(7293);t.exports=!n((function(){var t=function(){}.bind();return"function"!=typeof t||t.hasOwnProperty("prototype")}))},7065:(t,e,r)=>{"use strict";var n=r(1702),i=r(9662),o=r(111),s=r(2597),a=r(206),c=r(4374),u=Function,l=n([].concat),h=n([].join),d={},f=function(t,e,r){if(!s(d,e)){for(var n=[],i=0;i{var n=r(4374),i=Function.prototype.call;t.exports=n?i.bind(i):function(){return i.apply(i,arguments)}},6530:(t,e,r)=>{var n=r(9781),i=r(2597),o=Function.prototype,s=n&&Object.getOwnPropertyDescriptor,a=i(o,"name"),c=a&&"something"===function(){}.name,u=a&&(!n||n&&s(o,"name").configurable);t.exports={EXISTS:a,PROPER:c,CONFIGURABLE:u}},1470:(t,e,r)=>{var n=r(4326),i=r(1702);t.exports=function(t){if("Function"===n(t))return i(t)}},1702:(t,e,r)=>{var n=r(4374),i=Function.prototype,o=i.call,s=n&&i.bind.bind(o,o);t.exports=n?s:function(t){return function(){return o.apply(t,arguments)}}},5005:(t,e,r)=>{var n=r(7854),i=r(614),o=function(t){return i(t)?t:void 0};t.exports=function(t,e){return arguments.length<2?o(n[t]):n[t]&&n[t][e]}},8173:(t,e,r)=>{var n=r(9662),i=r(8554);t.exports=function(t,e){var r=t[e];return i(r)?void 0:n(r)}},7854:(t,e,r)=>{var n=function(t){return t&&t.Math==Math&&t};t.exports=n("object"==typeof globalThis&&globalThis)||n("object"==typeof window&&window)||n("object"==typeof self&&self)||n("object"==typeof r.g&&r.g)||function(){return this}()||Function("return this")()},2597:(t,e,r)=>{var n=r(1702),i=r(7908),o=n({}.hasOwnProperty);t.exports=Object.hasOwn||function(t,e){return o(i(t),e)}},3501:t=>{t.exports={}},490:(t,e,r)=>{var n=r(5005);t.exports=n("document","documentElement")},4664:(t,e,r)=>{var n=r(9781),i=r(7293),o=r(317);t.exports=!n&&!i((function(){return 7!=Object.defineProperty(o("div"),"a",{get:function(){return 7}}).a}))},8361:(t,e,r)=>{var n=r(1702),i=r(7293),o=r(4326),s=Object,a=n("".split);t.exports=i((function(){return!s("z").propertyIsEnumerable(0)}))?function(t){return"String"==o(t)?a(t,""):s(t)}:s},9587:(t,e,r)=>{var n=r(614),i=r(111),o=r(7674);t.exports=function(t,e,r){var s,a;return o&&n(s=e.constructor)&&s!==r&&i(a=s.prototype)&&a!==r.prototype&&o(t,a),t}},2788:(t,e,r)=>{var n=r(1702),i=r(614),o=r(5465),s=n(Function.toString);i(o.inspectSource)||(o.inspectSource=function(t){return s(t)}),t.exports=o.inspectSource},8340:(t,e,r)=>{var n=r(111),i=r(8880);t.exports=function(t,e){n(e)&&"cause"in e&&i(t,"cause",e.cause)}},9909:(t,e,r)=>{var n,i,o,s=r(4811),a=r(7854),c=r(111),u=r(8880),l=r(2597),h=r(5465),d=r(6200),f=r(3501),p="Object already initialized",m=a.TypeError,g=a.WeakMap;if(s||h.state){var v=h.state||(h.state=new g);v.get=v.get,v.has=v.has,v.set=v.set,n=function(t,e){if(v.has(t))throw m(p);return e.facade=t,v.set(t,e),e},i=function(t){return v.get(t)||{}},o=function(t){return v.has(t)}}else{var b=d("state");f[b]=!0,n=function(t,e){if(l(t,b))throw m(p);return e.facade=t,u(t,b,e),e},i=function(t){return l(t,b)?t[b]:{}},o=function(t){return l(t,b)}}t.exports={set:n,get:i,has:o,enforce:function(t){return o(t)?i(t):n(t,{})},getterFor:function(t){return function(e){var r;if(!c(e)||(r=i(e)).type!==t)throw m("Incompatible receiver, "+t+" required");return r}}}},3157:(t,e,r)=>{var n=r(4326);t.exports=Array.isArray||function(t){return"Array"==n(t)}},614:(t,e,r)=>{var n=r(4154),i=n.all;t.exports=n.IS_HTMLDDA?function(t){return"function"==typeof t||t===i}:function(t){return"function"==typeof t}},4411:(t,e,r)=>{var n=r(1702),i=r(7293),o=r(614),s=r(648),a=r(5005),c=r(2788),u=function(){},l=[],h=a("Reflect","construct"),d=/^\s*(?:class|function)\b/,f=n(d.exec),p=!d.exec(u),m=function(t){if(!o(t))return!1;try{return h(u,l,t),!0}catch(t){return!1}},g=function(t){if(!o(t))return!1;switch(s(t)){case"AsyncFunction":case"GeneratorFunction":case"AsyncGeneratorFunction":return!1}try{return p||!!f(d,c(t))}catch(t){return!0}};g.sham=!0,t.exports=!h||i((function(){var t;return m(m.call)||!m(Object)||!m((function(){t=!0}))||t}))?g:m},4705:(t,e,r)=>{var n=r(7293),i=r(614),o=/#|\.prototype\./,s=function(t,e){var r=c[a(t)];return r==l||r!=u&&(i(e)?n(e):!!e)},a=s.normalize=function(t){return String(t).replace(o,".").toLowerCase()},c=s.data={},u=s.NATIVE="N",l=s.POLYFILL="P";t.exports=s},8554:t=>{t.exports=function(t){return null==t}},111:(t,e,r)=>{var n=r(614),i=r(4154),o=i.all;t.exports=i.IS_HTMLDDA?function(t){return"object"==typeof t?null!==t:n(t)||t===o}:function(t){return"object"==typeof t?null!==t:n(t)}},1913:t=>{t.exports=!1},2190:(t,e,r)=>{var n=r(5005),i=r(614),o=r(7976),s=r(3307),a=Object;t.exports=s?function(t){return"symbol"==typeof t}:function(t){var e=n("Symbol");return i(e)&&o(e.prototype,a(t))}},6244:(t,e,r)=>{var n=r(7466);t.exports=function(t){return n(t.length)}},6339:(t,e,r)=>{var n=r(7293),i=r(614),o=r(2597),s=r(9781),a=r(6530).CONFIGURABLE,c=r(2788),u=r(9909),l=u.enforce,h=u.get,d=Object.defineProperty,f=s&&!n((function(){return 8!==d((function(){}),"length",{value:8}).length})),p=String(String).split("String"),m=t.exports=function(t,e,r){"Symbol("===String(e).slice(0,7)&&(e="["+String(e).replace(/^Symbol\(([^)]*)\)/,"$1")+"]"),r&&r.getter&&(e="get "+e),r&&r.setter&&(e="set "+e),(!o(t,"name")||a&&t.name!==e)&&(s?d(t,"name",{value:e,configurable:!0}):t.name=e),f&&r&&o(r,"arity")&&t.length!==r.arity&&d(t,"length",{value:r.arity});try{r&&o(r,"constructor")&&r.constructor?s&&d(t,"prototype",{writable:!1}):t.prototype&&(t.prototype=void 0)}catch(t){}var n=l(t);return o(n,"source")||(n.source=p.join("string"==typeof e?e:"")),t};Function.prototype.toString=m((function(){return i(this)&&h(this).source||c(this)}),"toString")},4758:t=>{var e=Math.ceil,r=Math.floor;t.exports=Math.trunc||function(t){var n=+t;return(n>0?r:e)(n)}},6277:(t,e,r)=>{var n=r(1340);t.exports=function(t,e){return void 0===t?arguments.length<2?"":e:n(t)}},30:(t,e,r)=>{var n,i=r(9670),o=r(6048),s=r(748),a=r(3501),c=r(490),u=r(317),l=r(6200),h=l("IE_PROTO"),d=function(){},f=function(t){return" - - - - - - - - - -
- -
-
- - -
-
-
- -

Quels sont les outils que nous utilisons ?

-
-
-
    -
  • Matomo Il est recommandé par la CNIL, permet de ne pas envoyer vos données à des tiers, il est hébergé et infogéré par nos soins et nous permet la collecte et l'analyse de quelques données du trafic que nous recevons. Celles-ci nous permettent par la suite d'avoir une idée des interactions avec le contenu proposé et de travailler à son amélioration.
  • -
  • Et ... c'est tout.
  • -
-

Quelles sont les données que nous collectons ?

-

La première fois que vous nous rendez visite

-
    -
  • Les deux premiers chiffres de votre adresse IP, qui nous permettent de déterminer votre réseau de provenance et par la même votre origine géographique
  • -
  • La date et l'heure de votre visite et sa durée
  • -
  • Les pages que vous avez consultées
  • -
  • L'appareil utilisé, son système d'exploitation (version, modèle) ainsi que le type de navigateur (nom, langue, version) que vous utilisez
  • -
  • Éventuellement le site qui vous a amené jusqu'à nous
  • -
-

À chaque fois que vous consultez une page

-
    -
  • L'URL de la page (titre, temps de chargement)
  • -
  • L'enchaînement des pages au cours de votre lecture
  • -
-

Qui a accès à ces données ?

-

Ces données ne sont accessibles et manipulées que par les employé·e·s Rix.

-

Où mes données sont-elles stockées ?

-

- L'ensemble des documents transmis par nos correspondants sont conservés sur un stockage privé, hébergé par en France 🇫🇷. - Dans le cas de correspondances écrites (courriels) ils sont stockés en Suisse 🇨🇭. -

-

Il est possible de chiffrer les correspondances envoyées à contact@rix.fr à l'aide de notre clé publique.

-

Elles sont stockées sur nos serveurs, en France, qu'il s'agisse de données collectées lors de votre passage sur nos sites, ou de données collectées lors de l'une de nos correspondances.

-

Durée de conservation des données

-
    -
  • Les données collectées par Matomo sont conservées 180 jours;
  • -
  • Les données de vos candidatures (Papier comme numérique) sont conservées le temps du traitement;
  • -
-

Qui contacter en cas de questions sur vos données ?

-
    -
  • Vous pouvez accéder aux données vous concernant, les rectifier, demander leur effacement ou exercer votre droit à la limitation du traitement de vos données.
  • -
  • Vous pouvez retirer à tout moment votre consentement au traitement de vos données ;
  • -
  • Vous pouvez également vous opposer au traitement de vos données ;
  • -
  • Vous pouvez également exercer votre droit à la portabilité de vos données.
  • -
- -

Consultez le site de la CNIL pour plus d’informations sur vos droits.

- -

Pour exercer ces droits ou pour toute question sur le traitement de vos données dans ce dispositif, vous pouvez nous contacter: contact@rix.fr

- Si vous estimez, après nous avoir contactés, que vos droits « Informatique et Libertés » ne sont pas respectés, vous pouvez adresser une réclamation à la CNIL. -

-
-
-
-
- - - - - - diff --git a/pr/137/contact/index.html b/pr/137/contact/index.html deleted file mode 100644 index 8c76b357..00000000 --- a/pr/137/contact/index.html +++ /dev/null @@ -1,345 +0,0 @@ - - - - - - - Rix 🐺 - Nous contacter - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

- Besoin d'informations ? - Contactez-nous - Promis, on ne mord pas -

- -
-
-
-
Téléphone
- Une question à nous poser ? -
- - - - - - - - - - - 04 82 53 76 09 - -
-
-
- -
-
-
Mail
- Un challenge à nous proposer ? -
- - - - - - - - contact@rix.fr - -
-
-
- -
-
- Passez nous voir - - - - - - - Expliquez-nous votre problématique autour d'un ☕ ! - - 34 rue Jean Broquin - 69006 Lyon, France -
-
-
-
- -
- - - - - - diff --git a/pr/137/etudes-de-cas/index.html b/pr/137/etudes-de-cas/index.html deleted file mode 100644 index 51e0598a..00000000 --- a/pr/137/etudes-de-cas/index.html +++ /dev/null @@ -1,343 +0,0 @@ - - - - - - - Réalisations web : revue de nos projets depuis 18 ans | Rix - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - -
-
- - - - - - diff --git a/pr/137/etudes-de-cas/musique-music/index.html b/pr/137/etudes-de-cas/musique-music/index.html deleted file mode 100644 index 36a0c938..00000000 --- a/pr/137/etudes-de-cas/musique-music/index.html +++ /dev/null @@ -1,406 +0,0 @@ - - - - - - - Stockage résilient à haute disponibilité. | Rix 🐺 - Expertise & solutions devops sur-mesure - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
- -
-
-
-
-
-

Stockage résilient à haute disponibilité.

-

Musique & Music permet aux professionnels de la vidéo d'enrichir leurs productions avec de l'illustration sonore.

- Services -
    -
  • Conception
  • -
  • Infrastructure
  • -
  • Stockage haute dispo.
  • -
  • Stratégie de déploiement
  • -
-
-
-
- -
-
-
-
-
- Les technos utilisées -
- -
-

Le contexte du projet

-

Musique & Music est un éditeur spécialisé dans la musique de production dédiée aux professionnels. L'application web permet aux monteurs vidéos de chercher facilement des sons afin d'illustrer leurs productions. Parmi les atouts de l'application, il y a notamment la richesse du catalogue, la fluidité de la recherche et la pertinence des résultats proposés. Une recherche par similarité permet aux clients de Musique & Music de rechercher finement un style de musique en important des fichiers audio.

-

Musique & Music a confié à Rix la conception de son infrastructure et son infogérance, permettant de s'appuyer sur une équipe rodée à l'exploitation.

-

L'expertise Rix déployée pour l'application Musique Music

-

Analyse de l'existant

-

Musique & Music n'en était pas à sa première version, l'application existait déjà depuis plusieurs années mais la dette technique, l'obsolescence du code existant et la contrainte d'exploiter des briques logicielles propriétaires ne donnant plus satisfaction, ont décidé les fondateurs à repartir d'une feuille blanche. -Nous avons dès lors été solicités pour étudier et concevoir une infrastructure destinée à acceuillir la nouvelle application. -En collaboration avec les équipes de concepteurs d'Elao nous avons commencé à imaginer ce que pourrait être cette nouvelle infrastructure en fonction des contraintes métiers du projet (disponibilité, performance et résilience).

-

La première étape étant de récupérer, sécuriser et rendre hautement disponible les données musicales.

-

Le stockage

-

C'est la pierre angulaire du métier de Musique & Music, si l'application peut s'autoriser d'exceptionnelles interruptions de service la donnée doit elle, rester disponible. -Avec la volonté de rester souverain sur l'ensemble de son infrastructure, nous avons opté pour une solution reposant sur CEPH avec le Cloud Disk Array de chez OVH.

-

Les points important qui ont permis de retenir cette solution:

-
    -
  • La distribution du stockage
  • -
  • La triple réplication des données
  • -
  • La disponibilité
  • -
  • Le redimensionnement à chaud
  • -
-

La sureté des données

-

Au dela de l'aspect disponibilité des données, nous devions également veiller à disposer des pistes musicales hors infrastructure, en cas d'incident grave sur la brique de stockage entrainant son indisponibilité. -Nous avons opté pour une solution de synchronisation incrémentale des données sur un NAS Synology à travers un flux chiffré sur une instance dédiée à cette tâche.

-

La brique applicative

-

Elle repose sur une « stack » web assez standard basée sur du public cloud et mettant en oeuvre sur réseau privé:

-
    -
  • Un répartiteur de charge de type HAProxy
  • -
  • Un serveur Nginx
  • -
  • Un serveur de base de données de type MariaDB
  • -
-

Le tout fonctionnant sur un environnement applicatif PHP/Symfony.

-
- Étude de cas - Schema d'infrastructure -
- Musique & Music - Schema d'infrastructure -
-
-

Conception de l'infrastructure

-

Comme pour l'ensemble de nos infras, nous appuyons fortement sur l'automatisation, à la fois des déploiements applicatifs via CI/CD mais également de l'infrastructure avec différents outils:

-
    -
  • Terraform pour l'IaC (Infrastructure as Code) afin de déployer nos différentes briques;
  • -
  • Ansible pour la construction des environnements d'exécution applicatif;
  • -
  • Helm pour le déploiement des infrastructures de type Kubernetes.
  • -
-

Environnement d'exploitation

-

L'environnement d'exploitation réponds aux standards Rix à savoir:

-
    -
  • Un alerting de l'ensemble des services via messagerie, mail et SMS;
  • -
  • Une remontée des métriques dans différents dashboard Grafana (Système et applicatif) hébergé et infogéré sur nos infrastructures 🇫🇷;
  • -
  • Une exploitation des logs applicatifs et système via le composant Loki (Grafana) hébergé et infogéré sur nos infrastructures 🇫🇷;
  • -
  • Remontée des erreurs aux équipes applicatives via une plateforme Sentry (SaaS);
  • -
  • Les secrets applicatifs sont stockés dans un coffre de type Hashicorp Vault hébergé et infogéré sur nos infrastructures 🇫🇷.
  • -
-
-
- -
-
- - - - - - diff --git a/pr/137/etudes-de-cas/panneau-pocket/index.html b/pr/137/etudes-de-cas/panneau-pocket/index.html deleted file mode 100644 index a9f7ec5d..00000000 --- a/pr/137/etudes-de-cas/panneau-pocket/index.html +++ /dev/null @@ -1,404 +0,0 @@ - - - - - - - Tenue de charge, disponibilité, scalabilité. | Rix 🐺 - Expertise & solutions devops sur-mesure - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
- -
-
-
-
-
-

Tenue de charge, disponibilité, scalabilité.

-

Panneau Pocket, acteur majeur auprès des collectivités pour l'information de leurs usagés.

- Services -
    -
  • Conception
  • -
  • Accompagnement
  • -
  • Pilotage
  • -
-
-
-
- -
-
-
-
-
- Les technos utilisées -
- -
-

Le contexte du projet

-

Panneau Pocket a pour objectif de permettre aux collectivités de mieux communiquer avec leurs administrés et propose à ce titre une application mobile capable d'alerter en temps réel ses utilisateurs d'une nouvelle publication.

-

Les objectifs

-
    -
  • Être capables de tenir une solicitation des infrastructures soutenue;
  • -
  • Répondre à des pics de trafic importants lors de communications d'envergure;
  • -
  • Avoir l'obligation de disponibilité;
  • -
  • Assurer la sécurité, la sureté et la confidentialités des données.
  • -
-

Architecture

-

Panneau pocket répose essentiellement sur de l'API qui permet d'exposer de manière structurée et sécurisée ses données à l'application mobile. -Nous intervenons en collaboration avec les équipes de développement applicatif afin de trouver les meilleures solutions pour répondre au besoin exposé par les équipes de PanneauPocket.

-

Métier et contraintes

-

Le travail de Rix est de faire en sorte que 3 points essentiels soient au rendez-vous:

-
    -
  • les temps de réponses doivent être bons (Ne pas oublier que nos utilisateurs sont souvent sur de l'itinérance et loin des infrastructures des grandes métropoles);
  • -
  • l'application doit mettre en oeuvre une tolérance à la panne resposant sur de la redondance et assurer, même en cas d'incident, une qualité de service minimum;
  • -
  • les incidents dans la mesure du possible, doivent être anticipés.
  • -
-

À cela vient s'ajouter les contraintes de sureté des données personnelles et bien évidemment les données des utilisateurs ne doivent pas quitter l'union européenne.

-

Auquel nous ajoutons bien évidemment nos propres contraintes de qualité/supervision:

-
    -
  • l'ensemble de l'infrastructure doit être décrite sour forme d'IAC (Infrastructure As Code);
  • -
  • les environnements applicatifs doivent être « versionnés » et doivent pouvoir être redéployés de manière automatique et idempotente;
  • -
  • l'ensemble des secrets applicatifs sont stockés dans un espace chiffré, sécurisé;
  • -
  • les sauvegardes bénéficient d'une triple réplique sur deux fournisseurs différents et sont chiffrées;
  • -
  • les accès sont controlés et audités.
  • -
-

Mise en oeuvre

-

Afin de remplir ce contrat et répondre aux contraintes métier nous nous sommes appuyés sur du matériel OVH et Scaleway en proposant une infrastructure redondée classique orchestrée sur un réseau privé intégrant:

-
    -
  • Un répartiteur de charge en frontal (redondé);
  • -
  • Plusieurs instances applicatives;
  • -
  • Un répartiteur de charge SQL Maxscale (redondé);
  • -
  • Plusieurs instances de base de données.
  • -
-

À cela vient s'ajouter les briques logiques standard (WAF, SG...) que nous ne détaillerons pas ici ainsi que les espaces de stockage reposants sur différentes stratégies mixant NFS, stockage de type bloc et stockage objet (S3).

-

Environnement d'exploitation

-

L'environnement d'exploitation réponds aux standards Rix à savoir:

-
    -
  • Une remontée des métriques dans différents dashboard Grafana (Système et applicatif);
  • -
  • Un alerting de l'ensemble des services via messagerie, mail et SMS;
  • -
  • Une exploitation des logs applicatifs et système via le composant Loki (Grafana);
  • -
  • Remontée des erreurs aux équipes applicatives via une plateforme Sentry;
  • -
  • Les secrets applicatifs sont stockés dans un coffre de type Hashicorp Vault déployé sur nos infrastructures.
  • -
-
-
- -
-
- - - - - - diff --git a/pr/137/favicon-16x16.png b/pr/137/favicon-16x16.png deleted file mode 100644 index 2243c906..00000000 Binary files a/pr/137/favicon-16x16.png and /dev/null differ diff --git a/pr/137/favicon-32x32.png b/pr/137/favicon-32x32.png deleted file mode 100644 index 92da1ced..00000000 Binary files a/pr/137/favicon-32x32.png and /dev/null differ diff --git a/pr/137/favicon-96x96.png b/pr/137/favicon-96x96.png deleted file mode 100644 index 09b5d0a3..00000000 Binary files a/pr/137/favicon-96x96.png and /dev/null differ diff --git a/pr/137/favicon.ico b/pr/137/favicon.ico deleted file mode 100644 index 2243c906..00000000 Binary files a/pr/137/favicon.ico and /dev/null differ diff --git a/pr/137/google98e08ccbf4b44d9b.html b/pr/137/google98e08ccbf4b44d9b.html deleted file mode 100644 index e195c9cc..00000000 --- a/pr/137/google98e08ccbf4b44d9b.html +++ /dev/null @@ -1 +0,0 @@ -google-site-verification: google98e08ccbf4b44d9b.html \ No newline at end of file diff --git a/pr/137/images/logo-signature.png b/pr/137/images/logo-signature.png deleted file mode 100644 index 992665ed..00000000 Binary files a/pr/137/images/logo-signature.png and /dev/null differ diff --git a/pr/137/index.html b/pr/137/index.html deleted file mode 100644 index 43e97653..00000000 --- a/pr/137/index.html +++ /dev/null @@ -1,557 +0,0 @@ - - - - - - - Rix 🐺 - Expertise Kubernetes & SRE - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - -
-
-
- -

- Nos valeurs - Nos engagements -

-
-
-
    -
  • -
    - -

    Indépendance et gouvernance

    -
    -

    Après plusieurs années à utiliser de grand fournisseurs de solution « cloud », nous avons pris le parti d'essayer au maximum, de travailler avec des acteurs nationaux afin de trouver des solutions matérielles permettant de construire des infrastructures robustes et évolutives.

    -

    Parce que nous croyons fermement en la nécessité d'indépendance des entreprises Françaises et Européennes vis à vis des grands acteurs étrangers.

    -
  • -
  • -
    - -

    Proximité et réactivité

    -
    -

    Nous aimons rencontrer les personnes avec lesquelles nous travaillons, notre objectif est de bien comprendre les enjeux du métier afin d'apporter un maximum de valeur à travers nos solutions.

    -

    Chaque projet étant différent nous avons besoin de comprendre vos contraintes et vos objectifs afin de faire correspondre nos infrastructures à vos besoins réels.

    -
  • -
  • -
    - -

    Sécurité

    -
    -

    Très sensibles aux problématiques de sécurité, nous sommes partie prenante de la mise en conformité de vos infrastructures.

    -

    Nous vous accompagnons pour la mise en place de vos PRA et leur mise en application. Si vous souhaitez aller plus loin, notre réseau de professionnels reconnus vous permettra de mettre en œuvre Pentest, Audit et/ou Bug Bounty.

    -
  • -
- -
-

Notre culture,
l'Open source

-
-

Adeptes depuis bien des années de la culture OpenSource, nous nous reposons sur des outils libres et contribuons lorsque nous le pouvons, notamment à travers notre projet Manala

- Aller sur notre Github -
-
- -
-

- Nos services - Apporter un maximum de valeur aux équipes techniques. -

-

Nous accompagnons vos équipes dans la mise en place de la culture et des outils orientés Ops en nous appuyant sur notre expérience de plus de 15 ans d'exploitation d'applications web.

- - - -
-
-
-
-
-
-

- Notre territoire - Un écosystème éprouvé -

-
- -
    -
  • - -

    Analyse

    -

    Collecte des données et analyse des comportements applicatifs et système pour le pilotage et la compréhension des incidents.

    -
  • -
  • - -

    Détection

    -

    Système de supervision permettant d'anticiper au maximum de potentiels incidents.

    -
  • -
  • - -

    Automation

    -

    Construction des environnements applicatifs à l'aide d'outils d'IAC et de stratégies « d'automation »

    -
  • -
  • - -

    Supervision

    -

    Supervision des systèmes et des applications en temps réel.

    -
  • -
  • - -

    Sauvegarde

    -

    Mise en sûreté de vos données, avec réplication et chiffrement.

    -
  • -
-
-
-
-
-

- Nos clients - Ils nous font confiance -

-
    -
  • - -

    M6

    -

    Fondé par le groupe M6 et co entreprit avec le groupe RTL, Bedrock conçoit et exploite des plateformes de streaming à la pointe de l'état de l'art à destination de différents groupe de médias et diffuseurs à travers l'Europe.

    -
  • -
  • - -

    Centre de Recherche en Nutrition Humaine Rhône-Alpes

    -

    Le Centre de Recherche en Nutrition Humaine Rhône-Alpes travaille à l’amélioration de l’alimentation pour la santé et le bien-être de l’Homme. Il développe des programmes de recherche en nutrition dans le cadre des appels d’offres nationaux, européens et internationaux et collabore avec les industriels et les chercheurs de grands groupes mondiaux.

    -
  • -
  • - -

    Elao

    -

    Elao est un atelier de co-conception d'applications web et mobile sur mesure mais avant tout une équipe de concepteurs / développeurs seniors. Spécialistes des applications métiers à forte valeur stratégique Elao aime être au contact des utilisateurs finaux afin d'être au cœur du métier de ses clients.

    -
  • -
  • - -

    Amabla

    -

    Amabla est une plateforme qui propose de regrouper les IA génératives dans un seul outil. Elle s'adapter au contexte métier de ses clients afin de concevoir des assistants en lien avec leurs besoins. Avec sa plateforme, Amabla a pour volonté d'optimiser le temps de travail des utilisateurs en les accompagnants sur des tâches secondaires afin de leur permettre de se concentrer sur leur véritable valeur ajoutée.

    -
  • -
  • - -

    GRIEPS

    -

    Le GRIEPS, organisme de formation-conseil, est une SCOP qui accompagne les établissements sanitaires et médicosociaux ainsi que les professionnels de santé salariés et libéraux à répondre aux besoins de santé de la population et à s'adapter aux mutations de l'environnement sur les plans cliniques, organisationnels et managériaux.

    -
  • -
  • - -

    Maison de la danse

    -

    Numeridanse est la plateforme multimédia de la danse. Elle donne accès à un fonds vidéo unique : spectacles filmés, documentaires, interviews, fictions, vidéo danse. Tous les genres, styles et formes sont représentés : butô, danse classique, néoclassique, baroque, danses indiennes, africaines, flamenco, contemporain, danses traditionnelles, hip hop, tango, jazz, arts du cirque, performance... Porté et coordonné par la Maison de la Danse de Lyon, Numeridanse a été imaginé par le réalisateur Charles Picq.

    -
  • -
  • - -

    Musique & Music

    -

    Première librairie musicale indépendante française. Musique & Music accompagne depuis 30 ans les professionnels dans l'illustration sonore de leurs projets audiovisuels. En associant des services et des outils innovant à une librairie musicale riche de près de 720 000 titres, Musique & Music permets à ses clients de gagner un temps précieux dans leurs recherches musicales.

    -
  • -
  • - -

    Panneau pocket

    -

    Panneau pocket est l’application mobile d’informations et d’alertes n°1 en France avec plus de 9000 collectivités équipées. Mairies, Gendarmeries, Intercommunalités, Ecoles, RPI des enfants, Syndicats des eaux … les citoyens retrouvent leur vie locale dans une seule et unique application sur leur smartphone. La population reçoit en temps réel les notifications des actualités de leur territoire : c’est l’information qui va vers l’habitant !

    -
  • -
  • - -

    Wotol

    -

    Wotol est une des plus anciennes place de marché B2B entièrement dédiée à l'industrie et à l'achat de revente de machines outils.

    -
  • -
- -
-
- - - - - - diff --git a/pr/137/legal/index.html b/pr/137/legal/index.html deleted file mode 100644 index 98c85bfa..00000000 --- a/pr/137/legal/index.html +++ /dev/null @@ -1,328 +0,0 @@ - - - - - - - Rix 🐺 - Expertise & solutions devops sur-mesure - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
- - -
-
-
- -

Conditions générales d'utilisation

-
-
-
- -

L'accès et l'utilisation de ce support sont régis par les présentes conditions d’utilisation. Par votre accès, votre navigation et votre utilisation de notre site, vous reconnaissez que vous avez lu, compris et accepté sans réserve ces présentes conditions. Nous nous réservons le droit de les modifier.

- -

Liens

-

Bien que nous vérifiions le contenu des liens hypertextes pointant sur des pages extérieures au moment de leur création, le contenu des sites visés peut changer postérieurement à cette vérification. Nous invitons donc les visiteurs à la plus grande prudence.

-

De même, nous vous prions de bien vouloir nous signaler tout lien hypertexte brisé ou erroné.

- -

Contenus

-

Bien que nous nous efforçions d'être le plus rigoureux possible, nous ne pouvons garantir l'exactitude et l'exhaustivité des données publiées sur ce support.

-

Le site est maintenu et mis à jour, nous ne pouvons néanmoins pas garantir l'actualisation des informations et méthodes qu'il contient.

-

Nous nous réservons le droits de corriger à tous moment ces données si d'aventure nous constations qu'elles sont érronées ou inexactes. En conséquence, l'utilisateur est seul responsable de leur utilisation.

- -

Responsabilité

-

Nous ne pourrions être tenu pour responsable des dommages directs ou indirects qui pourraient résulter de l'accès ou de l'utilisation des ressources proposées par ce site.

-

Concernant les articles du blog notamment, ils sont rédigés à des fins pédagogiques et éducatives et doivent être mis en application en ayant bien compris leur finalité.

-

Nous nous réservons le droit de modifier ou d'interrompre, temporairement ou de façon permanente, tout ou partie de l'accès au site afin d'effectuer les opérations de maintenance nécessaires à son bon fonctionnement.

- -

Protection des données personnelles

-

La confidentialité et la sécurité de vos données est une priorité absolue pour nous.

-

Les adresses électroniques recueillies dans le cadre du formulaire de contact ne sont utilisées que pour l'envoi d'informations ayant motivé l'inscription.

-

Pour plus d’informations sur nos pratiques de confidentialité et les mesures prises pour protéger votre vie privée, consultez en détails notre page protection des données.

- -

Droits d'auteur

-

Reproduction sur support papier

-

À l'exception de l'iconographie, la reproduction des pages de ce site sur un support papier est autorisée, sous réserve du respect des trois conditions suivantes :

-
    -
  • Gratuité de la diffusion
  • -
  • Respect de l'intégrité des documents reproduits (aucune modification, ni altération d'aucune sorte)
  • -
  • Citation explicite du site (https://rix-fr.github.io/rix/pr/137/) comme source et mention que les droits de reproduction sont réservés et strictement limités
  • -
-

Reproduction sur support électronique

-

La reproduction de tout ou partie de ce site sur un support électronique est autorisée sous réserve de l'ajout de façon claire et lisible de la source (https://rix-fr.github.io/rix/pr/137/) et de la mention "Droits réservés". Les informations utilisées ne doivent l'être à des fins personnelles, associatives ou professionnelles ; toute utilisation à des fins commerciales ou publicitaires est exclue.

-
-
-
-
- - - - - - diff --git a/pr/137/mstile-152x152.png b/pr/137/mstile-152x152.png deleted file mode 100644 index 498a4898..00000000 Binary files a/pr/137/mstile-152x152.png and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2022/icloud-sync-with-gnome-calendar/evolution-connexion-icloud.jpg/0f7f8f434b8b8c19862771a6a260297b.jpg b/pr/137/resized/content/images/blog/2022/icloud-sync-with-gnome-calendar/evolution-connexion-icloud.jpg/0f7f8f434b8b8c19862771a6a260297b.jpg deleted file mode 100644 index ecb9ddc7..00000000 Binary files a/pr/137/resized/content/images/blog/2022/icloud-sync-with-gnome-calendar/evolution-connexion-icloud.jpg/0f7f8f434b8b8c19862771a6a260297b.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2022/icloud-sync-with-gnome-calendar/evolution-connexion-icloud.jpg/e5d189ab24fcbd3857dbf579a7cdc917.jpg b/pr/137/resized/content/images/blog/2022/icloud-sync-with-gnome-calendar/evolution-connexion-icloud.jpg/e5d189ab24fcbd3857dbf579a7cdc917.jpg deleted file mode 100644 index 1d42697a..00000000 Binary files a/pr/137/resized/content/images/blog/2022/icloud-sync-with-gnome-calendar/evolution-connexion-icloud.jpg/e5d189ab24fcbd3857dbf579a7cdc917.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2022/icloud-sync-with-gnome-calendar/evolution-new-calendar.jpg/93cbb4c889c42310fd05bf1db5499e38.jpg b/pr/137/resized/content/images/blog/2022/icloud-sync-with-gnome-calendar/evolution-new-calendar.jpg/93cbb4c889c42310fd05bf1db5499e38.jpg deleted file mode 100644 index fd12a6d9..00000000 Binary files a/pr/137/resized/content/images/blog/2022/icloud-sync-with-gnome-calendar/evolution-new-calendar.jpg/93cbb4c889c42310fd05bf1db5499e38.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2022/icloud-sync-with-gnome-calendar/evolution-new-calendar.jpg/cea2561c4f29f52ce74d81d1292d2ddd.jpg b/pr/137/resized/content/images/blog/2022/icloud-sync-with-gnome-calendar/evolution-new-calendar.jpg/cea2561c4f29f52ce74d81d1292d2ddd.jpg deleted file mode 100644 index 0614a550..00000000 Binary files a/pr/137/resized/content/images/blog/2022/icloud-sync-with-gnome-calendar/evolution-new-calendar.jpg/cea2561c4f29f52ce74d81d1292d2ddd.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2022/osx-to-linux/mailspring-screenshot-01.jpg/43afa9f7916f18f63bf34bc3e03aa0e5.jpg b/pr/137/resized/content/images/blog/2022/osx-to-linux/mailspring-screenshot-01.jpg/43afa9f7916f18f63bf34bc3e03aa0e5.jpg deleted file mode 100644 index 7dae32d1..00000000 Binary files a/pr/137/resized/content/images/blog/2022/osx-to-linux/mailspring-screenshot-01.jpg/43afa9f7916f18f63bf34bc3e03aa0e5.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2022/osx-to-linux/mailspring-screenshot-01.jpg/9b445763b027f4a80d28d4f7af9b4555.jpg b/pr/137/resized/content/images/blog/2022/osx-to-linux/mailspring-screenshot-01.jpg/9b445763b027f4a80d28d4f7af9b4555.jpg deleted file mode 100644 index b7709581..00000000 Binary files a/pr/137/resized/content/images/blog/2022/osx-to-linux/mailspring-screenshot-01.jpg/9b445763b027f4a80d28d4f7af9b4555.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2022/osx-to-linux/visionneuse-pdf.jpg/4f20a6fea1b9fa1bbf2335ee8ddbbf8e.jpg b/pr/137/resized/content/images/blog/2022/osx-to-linux/visionneuse-pdf.jpg/4f20a6fea1b9fa1bbf2335ee8ddbbf8e.jpg deleted file mode 100644 index b606fa55..00000000 Binary files a/pr/137/resized/content/images/blog/2022/osx-to-linux/visionneuse-pdf.jpg/4f20a6fea1b9fa1bbf2335ee8ddbbf8e.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2022/osx-to-linux/visionneuse-pdf.jpg/8f62ab41cbb5677cff762e39b0aac290.jpg b/pr/137/resized/content/images/blog/2022/osx-to-linux/visionneuse-pdf.jpg/8f62ab41cbb5677cff762e39b0aac290.jpg deleted file mode 100644 index 628035a6..00000000 Binary files a/pr/137/resized/content/images/blog/2022/osx-to-linux/visionneuse-pdf.jpg/8f62ab41cbb5677cff762e39b0aac290.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/ansible/ansible-part-1/ansible-ping.gif/c9b728f3e46b8cbce28924647d01323f.gif b/pr/137/resized/content/images/blog/2023/ansible/ansible-part-1/ansible-ping.gif/c9b728f3e46b8cbce28924647d01323f.gif deleted file mode 100644 index dd8d7fa6..00000000 Binary files a/pr/137/resized/content/images/blog/2023/ansible/ansible-part-1/ansible-ping.gif/c9b728f3e46b8cbce28924647d01323f.gif and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-apt.jpg/60652788f5851e9cafba97c9469e7b37.jpg b/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-apt.jpg/60652788f5851e9cafba97c9469e7b37.jpg deleted file mode 100644 index 364938cb..00000000 Binary files a/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-apt.jpg/60652788f5851e9cafba97c9469e7b37.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-apt.jpg/61297f50c3f81b91155f26fcf12b639e.jpg b/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-apt.jpg/61297f50c3f81b91155f26fcf12b639e.jpg deleted file mode 100644 index 597633a7..00000000 Binary files a/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-apt.jpg/61297f50c3f81b91155f26fcf12b639e.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-example.png/60539304d17a65d7f89b5eb88cea3749.png b/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-example.png/60539304d17a65d7f89b5eb88cea3749.png deleted file mode 100644 index 4e0ebe53..00000000 Binary files a/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-example.png/60539304d17a65d7f89b5eb88cea3749.png and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-example.png/cca60d1a0018444ee0fdca536c154d58.png b/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-example.png/cca60d1a0018444ee0fdca536c154d58.png deleted file mode 100644 index 4b4d89a7..00000000 Binary files a/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-example.png/cca60d1a0018444ee0fdca536c154d58.png and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-groups.jpg/1322e1e27cdb4902c08f0bc366de6f39.jpg b/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-groups.jpg/1322e1e27cdb4902c08f0bc366de6f39.jpg deleted file mode 100644 index 75921b1c..00000000 Binary files a/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-groups.jpg/1322e1e27cdb4902c08f0bc366de6f39.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-groups.jpg/c27db12c82517f971ecdbd3d010185db.jpg b/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-groups.jpg/c27db12c82517f971ecdbd3d010185db.jpg deleted file mode 100644 index a462b90d..00000000 Binary files a/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-groups.jpg/c27db12c82517f971ecdbd3d010185db.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-handler.jpg/6eb1cc47a7c80cb64d8a1ad991701ef4.jpg b/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-handler.jpg/6eb1cc47a7c80cb64d8a1ad991701ef4.jpg deleted file mode 100644 index 171b298b..00000000 Binary files a/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-handler.jpg/6eb1cc47a7c80cb64d8a1ad991701ef4.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-handler.jpg/dc709322f2276448cacc708dcd0218fc.jpg b/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-handler.jpg/dc709322f2276448cacc708dcd0218fc.jpg deleted file mode 100644 index c541b8be..00000000 Binary files a/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-handler.jpg/dc709322f2276448cacc708dcd0218fc.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-list-host.gif/3380753e5b694ee86c4d72d157c09c79.gif b/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-list-host.gif/3380753e5b694ee86c4d72d157c09c79.gif deleted file mode 100644 index f22751e7..00000000 Binary files a/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-list-host.gif/3380753e5b694ee86c4d72d157c09c79.gif and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-list-tasks-hosts.jpg/ae35ec8bc8245ce7cada9bd81e7ff6c5.jpg b/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-list-tasks-hosts.jpg/ae35ec8bc8245ce7cada9bd81e7ff6c5.jpg deleted file mode 100644 index 2cd9e72d..00000000 Binary files a/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-list-tasks-hosts.jpg/ae35ec8bc8245ce7cada9bd81e7ff6c5.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-list-tasks-hosts.jpg/cb2200015ba23ffc86168edb3bcadaf0.jpg b/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-list-tasks-hosts.jpg/cb2200015ba23ffc86168edb3bcadaf0.jpg deleted file mode 100644 index dc8f72d3..00000000 Binary files a/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-list-tasks-hosts.jpg/cb2200015ba23ffc86168edb3bcadaf0.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-pre-tasks.jpg/4c55a2c80172722cb00c2699a96f791e.jpg b/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-pre-tasks.jpg/4c55a2c80172722cb00c2699a96f791e.jpg deleted file mode 100644 index bdde45c3..00000000 Binary files a/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-pre-tasks.jpg/4c55a2c80172722cb00c2699a96f791e.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-pre-tasks.jpg/709fe795189f9280d551c12204c0a274.jpg b/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-pre-tasks.jpg/709fe795189f9280d551c12204c0a274.jpg deleted file mode 100644 index 7b5a0506..00000000 Binary files a/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-pre-tasks.jpg/709fe795189f9280d551c12204c0a274.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-tasks.jpg/3d3e1ff571b62722a02ea21e52fce83f.jpg b/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-tasks.jpg/3d3e1ff571b62722a02ea21e52fce83f.jpg deleted file mode 100644 index 6df73b10..00000000 Binary files a/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-tasks.jpg/3d3e1ff571b62722a02ea21e52fce83f.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-tasks.jpg/eb4c8f6e6951132cc85983159479ceae.jpg b/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-tasks.jpg/eb4c8f6e6951132cc85983159479ceae.jpg deleted file mode 100644 index 6b7dd275..00000000 Binary files a/pr/137/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-tasks.jpg/eb4c8f6e6951132cc85983159479ceae.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/ansible/lazy-ansible/manala_init.gif/14dbfa2b6d24284c97f635b0e4ad70b9.gif b/pr/137/resized/content/images/blog/2023/ansible/lazy-ansible/manala_init.gif/14dbfa2b6d24284c97f635b0e4ad70b9.gif deleted file mode 100644 index 8a2fd860..00000000 Binary files a/pr/137/resized/content/images/blog/2023/ansible/lazy-ansible/manala_init.gif/14dbfa2b6d24284c97f635b0e4ad70b9.gif and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/cours-docker-wsl/add-user-to-docker-group.jpg/9c284bf54b95c30385f2bb78ea00815b.jpg b/pr/137/resized/content/images/blog/2023/cours-docker-wsl/add-user-to-docker-group.jpg/9c284bf54b95c30385f2bb78ea00815b.jpg deleted file mode 100644 index b19bcd1d..00000000 Binary files a/pr/137/resized/content/images/blog/2023/cours-docker-wsl/add-user-to-docker-group.jpg/9c284bf54b95c30385f2bb78ea00815b.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/cours-docker-wsl/add-user-to-docker-group.jpg/e2748db59362efca3ba6f018df84383b.jpg b/pr/137/resized/content/images/blog/2023/cours-docker-wsl/add-user-to-docker-group.jpg/e2748db59362efca3ba6f018df84383b.jpg deleted file mode 100644 index a1126546..00000000 Binary files a/pr/137/resized/content/images/blog/2023/cours-docker-wsl/add-user-to-docker-group.jpg/e2748db59362efca3ba6f018df84383b.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/cours-docker-wsl/docker-desktop-settings.jpg/715304e02b5695843f019c99941dd8d3.jpg b/pr/137/resized/content/images/blog/2023/cours-docker-wsl/docker-desktop-settings.jpg/715304e02b5695843f019c99941dd8d3.jpg deleted file mode 100644 index d07f6786..00000000 Binary files a/pr/137/resized/content/images/blog/2023/cours-docker-wsl/docker-desktop-settings.jpg/715304e02b5695843f019c99941dd8d3.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/cours-docker-wsl/docker-desktop-settings.jpg/c3b93f12d11ccf947af7185c695bacec.jpg b/pr/137/resized/content/images/blog/2023/cours-docker-wsl/docker-desktop-settings.jpg/c3b93f12d11ccf947af7185c695bacec.jpg deleted file mode 100644 index 2eb2885c..00000000 Binary files a/pr/137/resized/content/images/blog/2023/cours-docker-wsl/docker-desktop-settings.jpg/c3b93f12d11ccf947af7185c695bacec.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/cours-docker-wsl/wsl_debian_shell.jpg/6fbef3ca7b35e4a4ba2503ac4d9f074b.jpg b/pr/137/resized/content/images/blog/2023/cours-docker-wsl/wsl_debian_shell.jpg/6fbef3ca7b35e4a4ba2503ac4d9f074b.jpg deleted file mode 100644 index fe1c0a4f..00000000 Binary files a/pr/137/resized/content/images/blog/2023/cours-docker-wsl/wsl_debian_shell.jpg/6fbef3ca7b35e4a4ba2503ac4d9f074b.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/cours-docker-wsl/wsl_debian_shell.jpg/f988070bcb4009128e0af18075906181.jpg b/pr/137/resized/content/images/blog/2023/cours-docker-wsl/wsl_debian_shell.jpg/f988070bcb4009128e0af18075906181.jpg deleted file mode 100644 index 81530f59..00000000 Binary files a/pr/137/resized/content/images/blog/2023/cours-docker-wsl/wsl_debian_shell.jpg/f988070bcb4009128e0af18075906181.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/cours/ssh-keygen.gif/4c624a8987673a6deb25cf1395b926ce.gif b/pr/137/resized/content/images/blog/2023/cours/ssh-keygen.gif/4c624a8987673a6deb25cf1395b926ce.gif deleted file mode 100644 index 08d51c36..00000000 Binary files a/pr/137/resized/content/images/blog/2023/cours/ssh-keygen.gif/4c624a8987673a6deb25cf1395b926ce.gif and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/cours/ssh.gif/02e06122a9eba89d4faed7b89ae519d6.gif b/pr/137/resized/content/images/blog/2023/cours/ssh.gif/02e06122a9eba89d4faed7b89ae519d6.gif deleted file mode 100644 index 087af9d3..00000000 Binary files a/pr/137/resized/content/images/blog/2023/cours/ssh.gif/02e06122a9eba89d4faed7b89ae519d6.gif and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/post-mortem/alerts.jpg/8bcbbffcadbef7f75fde9d5075fff6c7.jpg b/pr/137/resized/content/images/blog/2023/post-mortem/alerts.jpg/8bcbbffcadbef7f75fde9d5075fff6c7.jpg deleted file mode 100644 index 60d25ae7..00000000 Binary files a/pr/137/resized/content/images/blog/2023/post-mortem/alerts.jpg/8bcbbffcadbef7f75fde9d5075fff6c7.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/post-mortem/alerts.jpg/d8e4282a1f0b7b26dd76e70ffc584939.jpg b/pr/137/resized/content/images/blog/2023/post-mortem/alerts.jpg/d8e4282a1f0b7b26dd76e70ffc584939.jpg deleted file mode 100644 index c4ba520a..00000000 Binary files a/pr/137/resized/content/images/blog/2023/post-mortem/alerts.jpg/d8e4282a1f0b7b26dd76e70ffc584939.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/post-mortem/global_charge.jpg/436e0eacf0a0486949f8f44a7b32572f.jpg b/pr/137/resized/content/images/blog/2023/post-mortem/global_charge.jpg/436e0eacf0a0486949f8f44a7b32572f.jpg deleted file mode 100644 index ccc8504f..00000000 Binary files a/pr/137/resized/content/images/blog/2023/post-mortem/global_charge.jpg/436e0eacf0a0486949f8f44a7b32572f.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/post-mortem/global_charge.jpg/a53cd6721cba36d54e19dc82b424f187.jpg b/pr/137/resized/content/images/blog/2023/post-mortem/global_charge.jpg/a53cd6721cba36d54e19dc82b424f187.jpg deleted file mode 100644 index 2037d9d4..00000000 Binary files a/pr/137/resized/content/images/blog/2023/post-mortem/global_charge.jpg/a53cd6721cba36d54e19dc82b424f187.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/post-mortem/graphs_php_down.jpg/87da498e177900e1315617b6158da966.jpg b/pr/137/resized/content/images/blog/2023/post-mortem/graphs_php_down.jpg/87da498e177900e1315617b6158da966.jpg deleted file mode 100644 index 15e94e62..00000000 Binary files a/pr/137/resized/content/images/blog/2023/post-mortem/graphs_php_down.jpg/87da498e177900e1315617b6158da966.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/post-mortem/graphs_php_down.jpg/9adc412824436ea11dc0efeb47ffa049.jpg b/pr/137/resized/content/images/blog/2023/post-mortem/graphs_php_down.jpg/9adc412824436ea11dc0efeb47ffa049.jpg deleted file mode 100644 index d5d50d81..00000000 Binary files a/pr/137/resized/content/images/blog/2023/post-mortem/graphs_php_down.jpg/9adc412824436ea11dc0efeb47ffa049.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/post-mortem/netstat.jpg/0047ca9060a60a16a5c470387107e90f.jpg b/pr/137/resized/content/images/blog/2023/post-mortem/netstat.jpg/0047ca9060a60a16a5c470387107e90f.jpg deleted file mode 100644 index 34ec775e..00000000 Binary files a/pr/137/resized/content/images/blog/2023/post-mortem/netstat.jpg/0047ca9060a60a16a5c470387107e90f.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/post-mortem/netstat.jpg/2be0fdc956da99803fe72b8d43ba31ef.jpg b/pr/137/resized/content/images/blog/2023/post-mortem/netstat.jpg/2be0fdc956da99803fe72b8d43ba31ef.jpg deleted file mode 100644 index 20af8ab1..00000000 Binary files a/pr/137/resized/content/images/blog/2023/post-mortem/netstat.jpg/2be0fdc956da99803fe72b8d43ba31ef.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/post-mortem/network_db.jpg/412cb9cf6da17be08defe32ae888d3f5.jpg b/pr/137/resized/content/images/blog/2023/post-mortem/network_db.jpg/412cb9cf6da17be08defe32ae888d3f5.jpg deleted file mode 100644 index 387d7c42..00000000 Binary files a/pr/137/resized/content/images/blog/2023/post-mortem/network_db.jpg/412cb9cf6da17be08defe32ae888d3f5.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/post-mortem/network_db.jpg/c9853f03496f05e254f1e692784ff9c5.jpg b/pr/137/resized/content/images/blog/2023/post-mortem/network_db.jpg/c9853f03496f05e254f1e692784ff9c5.jpg deleted file mode 100644 index bd2ffc62..00000000 Binary files a/pr/137/resized/content/images/blog/2023/post-mortem/network_db.jpg/c9853f03496f05e254f1e692784ff9c5.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/post-mortem/network_db_2.jpg/3155bb88a4cac88509c3b717e9475e8d.jpg b/pr/137/resized/content/images/blog/2023/post-mortem/network_db_2.jpg/3155bb88a4cac88509c3b717e9475e8d.jpg deleted file mode 100644 index 73024abb..00000000 Binary files a/pr/137/resized/content/images/blog/2023/post-mortem/network_db_2.jpg/3155bb88a4cac88509c3b717e9475e8d.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/post-mortem/network_db_2.jpg/bd89ffdb7e4f9955ac21af37b9e23b3e.jpg b/pr/137/resized/content/images/blog/2023/post-mortem/network_db_2.jpg/bd89ffdb7e4f9955ac21af37b9e23b3e.jpg deleted file mode 100644 index 3e8fd7f6..00000000 Binary files a/pr/137/resized/content/images/blog/2023/post-mortem/network_db_2.jpg/bd89ffdb7e4f9955ac21af37b9e23b3e.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/post-mortem/network_web.jpg/8cce76c7e7bf1df3c864a3a66bd2271e.jpg b/pr/137/resized/content/images/blog/2023/post-mortem/network_web.jpg/8cce76c7e7bf1df3c864a3a66bd2271e.jpg deleted file mode 100644 index 8213c1da..00000000 Binary files a/pr/137/resized/content/images/blog/2023/post-mortem/network_web.jpg/8cce76c7e7bf1df3c864a3a66bd2271e.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2023/post-mortem/network_web.jpg/bdb0e53c8e7700cd7fb63eb069c1b9d8.jpg b/pr/137/resized/content/images/blog/2023/post-mortem/network_web.jpg/bdb0e53c8e7700cd7fb63eb069c1b9d8.jpg deleted file mode 100644 index eccff16e..00000000 Binary files a/pr/137/resized/content/images/blog/2023/post-mortem/network_web.jpg/bdb0e53c8e7700cd7fb63eb069c1b9d8.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2024/cours/ansible/ansible-variables/ansible-playbook-conditionnal.png/b88e67b7f711496c8e8d9a298509cf79.png b/pr/137/resized/content/images/blog/2024/cours/ansible/ansible-variables/ansible-playbook-conditionnal.png/b88e67b7f711496c8e8d9a298509cf79.png deleted file mode 100644 index 3ef26df6..00000000 Binary files a/pr/137/resized/content/images/blog/2024/cours/ansible/ansible-variables/ansible-playbook-conditionnal.png/b88e67b7f711496c8e8d9a298509cf79.png and /dev/null differ diff --git a/pr/137/resized/content/images/blog/2024/cours/ansible/ansible-variables/ansible-playbook-conditionnal.png/e934573fed71f6be66df931f294b09cc.png b/pr/137/resized/content/images/blog/2024/cours/ansible/ansible-variables/ansible-playbook-conditionnal.png/e934573fed71f6be66df931f294b09cc.png deleted file mode 100644 index 6c49a148..00000000 Binary files a/pr/137/resized/content/images/blog/2024/cours/ansible/ansible-variables/ansible-playbook-conditionnal.png/e934573fed71f6be66df931f294b09cc.png and /dev/null differ diff --git a/pr/137/resized/content/images/blog/styleguide/exemple-gif.gif/26720a805a265753b6f13e5a03fba4b5.gif b/pr/137/resized/content/images/blog/styleguide/exemple-gif.gif/26720a805a265753b6f13e5a03fba4b5.gif deleted file mode 100644 index 95fba5c6..00000000 Binary files a/pr/137/resized/content/images/blog/styleguide/exemple-gif.gif/26720a805a265753b6f13e5a03fba4b5.gif and /dev/null differ diff --git a/pr/137/resized/content/images/blog/styleguide/exemple-image-relative.jpg/470cd3cf457415c4960a560c201fc694.jpg b/pr/137/resized/content/images/blog/styleguide/exemple-image-relative.jpg/470cd3cf457415c4960a560c201fc694.jpg deleted file mode 100644 index 82c8f105..00000000 Binary files a/pr/137/resized/content/images/blog/styleguide/exemple-image-relative.jpg/470cd3cf457415c4960a560c201fc694.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/styleguide/exemple-image-relative.jpg/7b2c4debf6c584f73e5afaaa462ddb16.jpg b/pr/137/resized/content/images/blog/styleguide/exemple-image-relative.jpg/7b2c4debf6c584f73e5afaaa462ddb16.jpg deleted file mode 100644 index 34e6b367..00000000 Binary files a/pr/137/resized/content/images/blog/styleguide/exemple-image-relative.jpg/7b2c4debf6c584f73e5afaaa462ddb16.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/styleguide/exemple-image.jpg/460bc80c1807a1298f6305e117c1f5b0.jpg b/pr/137/resized/content/images/blog/styleguide/exemple-image.jpg/460bc80c1807a1298f6305e117c1f5b0.jpg deleted file mode 100644 index 429ac5d4..00000000 Binary files a/pr/137/resized/content/images/blog/styleguide/exemple-image.jpg/460bc80c1807a1298f6305e117c1f5b0.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/styleguide/exemple-image.jpg/6bda512686956a3df0bc5aef03d8e1c0.jpg b/pr/137/resized/content/images/blog/styleguide/exemple-image.jpg/6bda512686956a3df0bc5aef03d8e1c0.jpg deleted file mode 100644 index f3b36740..00000000 Binary files a/pr/137/resized/content/images/blog/styleguide/exemple-image.jpg/6bda512686956a3df0bc5aef03d8e1c0.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/ansible-inventaire.jpg/123eb99d9fbf168320e89faa5ce468a5.jpg b/pr/137/resized/content/images/blog/thumbnails/ansible-inventaire.jpg/123eb99d9fbf168320e89faa5ce468a5.jpg deleted file mode 100644 index 87c1b101..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/ansible-inventaire.jpg/123eb99d9fbf168320e89faa5ce468a5.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/ansible-inventaire.jpg/5dab74ef27fe54d7973d01e0758de679.jpg b/pr/137/resized/content/images/blog/thumbnails/ansible-inventaire.jpg/5dab74ef27fe54d7973d01e0758de679.jpg deleted file mode 100644 index 3684a05b..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/ansible-inventaire.jpg/5dab74ef27fe54d7973d01e0758de679.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/ansible-inventaire.jpg/9934b0fcbba9748a3a903ae6159048db.jpg b/pr/137/resized/content/images/blog/thumbnails/ansible-inventaire.jpg/9934b0fcbba9748a3a903ae6159048db.jpg deleted file mode 100644 index 8db6e6e6..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/ansible-inventaire.jpg/9934b0fcbba9748a3a903ae6159048db.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/ansible-inventaire.jpg/e0505a9dbf401f343226eeedd5f74340.jpg b/pr/137/resized/content/images/blog/thumbnails/ansible-inventaire.jpg/e0505a9dbf401f343226eeedd5f74340.jpg deleted file mode 100644 index fb30eafa..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/ansible-inventaire.jpg/e0505a9dbf401f343226eeedd5f74340.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/ansible-les-roles.jpg/191b6f07c7b99f7bd539e0e590b61f11.jpg b/pr/137/resized/content/images/blog/thumbnails/ansible-les-roles.jpg/191b6f07c7b99f7bd539e0e590b61f11.jpg deleted file mode 100644 index c2a96e07..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/ansible-les-roles.jpg/191b6f07c7b99f7bd539e0e590b61f11.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/ansible-les-roles.jpg/78d8d30de26aff170a9ed5a8686e2f5e.jpg b/pr/137/resized/content/images/blog/thumbnails/ansible-les-roles.jpg/78d8d30de26aff170a9ed5a8686e2f5e.jpg deleted file mode 100644 index 38363b97..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/ansible-les-roles.jpg/78d8d30de26aff170a9ed5a8686e2f5e.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/ansible-les-roles.jpg/de1f91939a92181909cd864ce0e2b458.jpg b/pr/137/resized/content/images/blog/thumbnails/ansible-les-roles.jpg/de1f91939a92181909cd864ce0e2b458.jpg deleted file mode 100644 index b5237a7a..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/ansible-les-roles.jpg/de1f91939a92181909cd864ce0e2b458.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/ansible-les-roles.jpg/f593d9e7bb0bc2fa23d8b8191a30b0df.jpg b/pr/137/resized/content/images/blog/thumbnails/ansible-les-roles.jpg/f593d9e7bb0bc2fa23d8b8191a30b0df.jpg deleted file mode 100644 index c0d88404..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/ansible-les-roles.jpg/f593d9e7bb0bc2fa23d8b8191a30b0df.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/ansible-les-variables.jpg/5f3469213b403c2130d75900d38f7de1.jpg b/pr/137/resized/content/images/blog/thumbnails/ansible-les-variables.jpg/5f3469213b403c2130d75900d38f7de1.jpg deleted file mode 100644 index acc4424b..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/ansible-les-variables.jpg/5f3469213b403c2130d75900d38f7de1.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/ansible-les-variables.jpg/601bad5cd31b31771ac7b06df43bae55.jpg b/pr/137/resized/content/images/blog/thumbnails/ansible-les-variables.jpg/601bad5cd31b31771ac7b06df43bae55.jpg deleted file mode 100644 index 10c165f1..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/ansible-les-variables.jpg/601bad5cd31b31771ac7b06df43bae55.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/ansible-les-variables.jpg/6a2fdabb3f8a5138364e8e2cead8433e.jpg b/pr/137/resized/content/images/blog/thumbnails/ansible-les-variables.jpg/6a2fdabb3f8a5138364e8e2cead8433e.jpg deleted file mode 100644 index f9f16d3a..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/ansible-les-variables.jpg/6a2fdabb3f8a5138364e8e2cead8433e.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/ansible-les-variables.jpg/724d16db79134789c1198b9577d4692f.jpg b/pr/137/resized/content/images/blog/thumbnails/ansible-les-variables.jpg/724d16db79134789c1198b9577d4692f.jpg deleted file mode 100644 index 195160ce..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/ansible-les-variables.jpg/724d16db79134789c1198b9577d4692f.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/ansible-playbooks.jpg/3835aca3650cfac77042ea0cfa196016.jpg b/pr/137/resized/content/images/blog/thumbnails/ansible-playbooks.jpg/3835aca3650cfac77042ea0cfa196016.jpg deleted file mode 100644 index 453bc891..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/ansible-playbooks.jpg/3835aca3650cfac77042ea0cfa196016.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/ansible-playbooks.jpg/67d8223c821d5f9ae6bdff772e8da8a8.jpg b/pr/137/resized/content/images/blog/thumbnails/ansible-playbooks.jpg/67d8223c821d5f9ae6bdff772e8da8a8.jpg deleted file mode 100644 index 9063617b..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/ansible-playbooks.jpg/67d8223c821d5f9ae6bdff772e8da8a8.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/ansible-playbooks.jpg/7c2421f183fe3910b158e0269822f67e.jpg b/pr/137/resized/content/images/blog/thumbnails/ansible-playbooks.jpg/7c2421f183fe3910b158e0269822f67e.jpg deleted file mode 100644 index 2e456531..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/ansible-playbooks.jpg/7c2421f183fe3910b158e0269822f67e.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/ansible-playbooks.jpg/9a6bfd093813c3412a0c59303420861d.jpg b/pr/137/resized/content/images/blog/thumbnails/ansible-playbooks.jpg/9a6bfd093813c3412a0c59303420861d.jpg deleted file mode 100644 index db5989da..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/ansible-playbooks.jpg/9a6bfd093813c3412a0c59303420861d.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/ansible-premier-pas.jpg/7d3657af4af16eeed961f5a60591cb88.jpg b/pr/137/resized/content/images/blog/thumbnails/ansible-premier-pas.jpg/7d3657af4af16eeed961f5a60591cb88.jpg deleted file mode 100644 index 57ed21aa..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/ansible-premier-pas.jpg/7d3657af4af16eeed961f5a60591cb88.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/ansible-premier-pas.jpg/85a273249dc4436723377363ef772dfc.jpg b/pr/137/resized/content/images/blog/thumbnails/ansible-premier-pas.jpg/85a273249dc4436723377363ef772dfc.jpg deleted file mode 100644 index e8ea3a62..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/ansible-premier-pas.jpg/85a273249dc4436723377363ef772dfc.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/ansible-premier-pas.jpg/ca79bfe07ddc4d125f81c3552c6299c7.jpg b/pr/137/resized/content/images/blog/thumbnails/ansible-premier-pas.jpg/ca79bfe07ddc4d125f81c3552c6299c7.jpg deleted file mode 100644 index 9bf4ef62..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/ansible-premier-pas.jpg/ca79bfe07ddc4d125f81c3552c6299c7.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/ansible-premier-pas.jpg/d62c67f803636c1d3a3a17f144e04a75.jpg b/pr/137/resized/content/images/blog/thumbnails/ansible-premier-pas.jpg/d62c67f803636c1d3a3a17f144e04a75.jpg deleted file mode 100644 index 84c0d42a..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/ansible-premier-pas.jpg/d62c67f803636c1d3a3a17f144e04a75.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/calendar.jpg/1759af6b89e8a6cf93e2919540947b13.jpg b/pr/137/resized/content/images/blog/thumbnails/calendar.jpg/1759af6b89e8a6cf93e2919540947b13.jpg deleted file mode 100644 index 702664fa..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/calendar.jpg/1759af6b89e8a6cf93e2919540947b13.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/calendar.jpg/4045862451ebd1f21a69fbfe1f05408b.jpg b/pr/137/resized/content/images/blog/thumbnails/calendar.jpg/4045862451ebd1f21a69fbfe1f05408b.jpg deleted file mode 100644 index 62978076..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/calendar.jpg/4045862451ebd1f21a69fbfe1f05408b.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/calendar.jpg/73773f96882739f398c4db9552656943.jpg b/pr/137/resized/content/images/blog/thumbnails/calendar.jpg/73773f96882739f398c4db9552656943.jpg deleted file mode 100644 index 3415b037..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/calendar.jpg/73773f96882739f398c4db9552656943.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/calendar.jpg/855f4f842396350759226c8041b96f7b.jpg b/pr/137/resized/content/images/blog/thumbnails/calendar.jpg/855f4f842396350759226c8041b96f7b.jpg deleted file mode 100644 index 68bf39a5..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/calendar.jpg/855f4f842396350759226c8041b96f7b.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/cle-ssh-principes.jpg/2260efd8671ecc49e9d6665506828634.jpg b/pr/137/resized/content/images/blog/thumbnails/cle-ssh-principes.jpg/2260efd8671ecc49e9d6665506828634.jpg deleted file mode 100644 index 5074f34c..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/cle-ssh-principes.jpg/2260efd8671ecc49e9d6665506828634.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/cle-ssh-principes.jpg/622d8f92c05e0988cb285b4a75fb17c6.jpg b/pr/137/resized/content/images/blog/thumbnails/cle-ssh-principes.jpg/622d8f92c05e0988cb285b4a75fb17c6.jpg deleted file mode 100644 index a69d83a0..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/cle-ssh-principes.jpg/622d8f92c05e0988cb285b4a75fb17c6.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/cle-ssh-principes.jpg/965641c67b52e6bd5acfbafbcf8b365f.jpg b/pr/137/resized/content/images/blog/thumbnails/cle-ssh-principes.jpg/965641c67b52e6bd5acfbafbcf8b365f.jpg deleted file mode 100644 index aa7f9ec2..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/cle-ssh-principes.jpg/965641c67b52e6bd5acfbafbcf8b365f.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/cle-ssh-principes.jpg/9cf50d92001aa3a66a9f197c9d1f7acb.jpg b/pr/137/resized/content/images/blog/thumbnails/cle-ssh-principes.jpg/9cf50d92001aa3a66a9f197c9d1f7acb.jpg deleted file mode 100644 index 622cdb1c..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/cle-ssh-principes.jpg/9cf50d92001aa3a66a9f197c9d1f7acb.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/inside-linux.jpg/20e08dbd8a74450ab6b73fea6ce37bb3.jpg b/pr/137/resized/content/images/blog/thumbnails/inside-linux.jpg/20e08dbd8a74450ab6b73fea6ce37bb3.jpg deleted file mode 100644 index e3c2db6b..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/inside-linux.jpg/20e08dbd8a74450ab6b73fea6ce37bb3.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/inside-linux.jpg/54e9b51481250743bf2b4dc8403b4442.jpg b/pr/137/resized/content/images/blog/thumbnails/inside-linux.jpg/54e9b51481250743bf2b4dc8403b4442.jpg deleted file mode 100644 index ad081861..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/inside-linux.jpg/54e9b51481250743bf2b4dc8403b4442.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/inside-linux.jpg/cfd86d11514e7101c38ec348b99539c0.jpg b/pr/137/resized/content/images/blog/thumbnails/inside-linux.jpg/cfd86d11514e7101c38ec348b99539c0.jpg deleted file mode 100644 index b126862a..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/inside-linux.jpg/cfd86d11514e7101c38ec348b99539c0.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/inside-linux.jpg/f1c0389148b3a3a33cbfff09d577338f.jpg b/pr/137/resized/content/images/blog/thumbnails/inside-linux.jpg/f1c0389148b3a3a33cbfff09d577338f.jpg deleted file mode 100644 index aed6ddb1..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/inside-linux.jpg/f1c0389148b3a3a33cbfff09d577338f.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/lazy-ansible.jpg/0e0f1c3e64deaf88e029b72ece90497d.jpg b/pr/137/resized/content/images/blog/thumbnails/lazy-ansible.jpg/0e0f1c3e64deaf88e029b72ece90497d.jpg deleted file mode 100644 index b1b423eb..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/lazy-ansible.jpg/0e0f1c3e64deaf88e029b72ece90497d.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/lazy-ansible.jpg/9ee2c8ce29ba79b4812efdc70b035c29.jpg b/pr/137/resized/content/images/blog/thumbnails/lazy-ansible.jpg/9ee2c8ce29ba79b4812efdc70b035c29.jpg deleted file mode 100644 index 81afbc03..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/lazy-ansible.jpg/9ee2c8ce29ba79b4812efdc70b035c29.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/lazy-ansible.jpg/da0275853f294688186b86f071c3b69c.jpg b/pr/137/resized/content/images/blog/thumbnails/lazy-ansible.jpg/da0275853f294688186b86f071c3b69c.jpg deleted file mode 100644 index 7378777d..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/lazy-ansible.jpg/da0275853f294688186b86f071c3b69c.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/lazy-ansible.jpg/eae61410753152dba71f62f27b1a5f3b.jpg b/pr/137/resized/content/images/blog/thumbnails/lazy-ansible.jpg/eae61410753152dba71f62f27b1a5f3b.jpg deleted file mode 100644 index f53067e8..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/lazy-ansible.jpg/eae61410753152dba71f62f27b1a5f3b.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/nature.jpg/49a1600a5d20e55868ab81b9b0f988aa.jpg b/pr/137/resized/content/images/blog/thumbnails/nature.jpg/49a1600a5d20e55868ab81b9b0f988aa.jpg deleted file mode 100644 index c271fbd7..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/nature.jpg/49a1600a5d20e55868ab81b9b0f988aa.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/nature.jpg/51f1c24cccc96753857fb175bb93da75.jpg b/pr/137/resized/content/images/blog/thumbnails/nature.jpg/51f1c24cccc96753857fb175bb93da75.jpg deleted file mode 100644 index dea1483a..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/nature.jpg/51f1c24cccc96753857fb175bb93da75.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/nature.jpg/592124ea920e0186fde09e7fbcb2228f.jpg b/pr/137/resized/content/images/blog/thumbnails/nature.jpg/592124ea920e0186fde09e7fbcb2228f.jpg deleted file mode 100644 index 3bfa2ce5..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/nature.jpg/592124ea920e0186fde09e7fbcb2228f.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/nature.jpg/e94a5d9cdc4a77a9294acb47cc6063c7.jpg b/pr/137/resized/content/images/blog/thumbnails/nature.jpg/e94a5d9cdc4a77a9294acb47cc6063c7.jpg deleted file mode 100644 index 30504731..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/nature.jpg/e94a5d9cdc4a77a9294acb47cc6063c7.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/raspberrypi.jpg/40540f49ef3f539794bd75fd51f6acfd.jpg b/pr/137/resized/content/images/blog/thumbnails/raspberrypi.jpg/40540f49ef3f539794bd75fd51f6acfd.jpg deleted file mode 100644 index 7dd152dd..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/raspberrypi.jpg/40540f49ef3f539794bd75fd51f6acfd.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/raspberrypi.jpg/80aae598a50641b22d1bab9f3f560087.jpg b/pr/137/resized/content/images/blog/thumbnails/raspberrypi.jpg/80aae598a50641b22d1bab9f3f560087.jpg deleted file mode 100644 index 6b6178bc..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/raspberrypi.jpg/80aae598a50641b22d1bab9f3f560087.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/raspberrypi.jpg/c38eae8d1b9f4b8b5e845b0cef7ff224.jpg b/pr/137/resized/content/images/blog/thumbnails/raspberrypi.jpg/c38eae8d1b9f4b8b5e845b0cef7ff224.jpg deleted file mode 100644 index 46fe6523..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/raspberrypi.jpg/c38eae8d1b9f4b8b5e845b0cef7ff224.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/raspberrypi.jpg/c94b2bd8ba163dd5c3a809d26180e46c.jpg b/pr/137/resized/content/images/blog/thumbnails/raspberrypi.jpg/c94b2bd8ba163dd5c3a809d26180e46c.jpg deleted file mode 100644 index c6c8fe36..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/raspberrypi.jpg/c94b2bd8ba163dd5c3a809d26180e46c.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/thisisengineering.jpg/01fb9421473fc2004327195b8776f337.jpg b/pr/137/resized/content/images/blog/thumbnails/thisisengineering.jpg/01fb9421473fc2004327195b8776f337.jpg deleted file mode 100644 index 869ccacc..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/thisisengineering.jpg/01fb9421473fc2004327195b8776f337.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/thisisengineering.jpg/df62f52a1bc314f874520cf74f863d76.jpg b/pr/137/resized/content/images/blog/thumbnails/thisisengineering.jpg/df62f52a1bc314f874520cf74f863d76.jpg deleted file mode 100644 index 025c247f..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/thisisengineering.jpg/df62f52a1bc314f874520cf74f863d76.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/thisisengineering.jpg/ed9effd75cf35b088fcedcd589759c7b.jpg b/pr/137/resized/content/images/blog/thumbnails/thisisengineering.jpg/ed9effd75cf35b088fcedcd589759c7b.jpg deleted file mode 100644 index 9c2a2592..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/thisisengineering.jpg/ed9effd75cf35b088fcedcd589759c7b.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/thisisengineering.jpg/ffbe8863d77de881d181cdd4d9d83280.jpg b/pr/137/resized/content/images/blog/thumbnails/thisisengineering.jpg/ffbe8863d77de881d181cdd4d9d83280.jpg deleted file mode 100644 index 10dd5496..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/thisisengineering.jpg/ffbe8863d77de881d181cdd4d9d83280.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/zsh.jpg/00fc284e365bbe49d303c8f64ebb321d.jpg b/pr/137/resized/content/images/blog/thumbnails/zsh.jpg/00fc284e365bbe49d303c8f64ebb321d.jpg deleted file mode 100644 index ad021cf1..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/zsh.jpg/00fc284e365bbe49d303c8f64ebb321d.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/zsh.jpg/377d013ecdf113dd876ef37c3c2e12b1.jpg b/pr/137/resized/content/images/blog/thumbnails/zsh.jpg/377d013ecdf113dd876ef37c3c2e12b1.jpg deleted file mode 100644 index f8af6384..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/zsh.jpg/377d013ecdf113dd876ef37c3c2e12b1.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/zsh.jpg/82e6b7dbc2cf345a64f872d47ebf7158.jpg b/pr/137/resized/content/images/blog/thumbnails/zsh.jpg/82e6b7dbc2cf345a64f872d47ebf7158.jpg deleted file mode 100644 index 84149809..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/zsh.jpg/82e6b7dbc2cf345a64f872d47ebf7158.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/blog/thumbnails/zsh.jpg/985de3f6cb7e319c4763d6fd2c992f0d.jpg b/pr/137/resized/content/images/blog/thumbnails/zsh.jpg/985de3f6cb7e319c4763d6fd2c992f0d.jpg deleted file mode 100644 index 48a4759c..00000000 Binary files a/pr/137/resized/content/images/blog/thumbnails/zsh.jpg/985de3f6cb7e319c4763d6fd2c992f0d.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/case-study/headers/musique-music-banner.jpg/1f1cae10550c57cf5f9cd9930a1f0a65.jpg b/pr/137/resized/content/images/case-study/headers/musique-music-banner.jpg/1f1cae10550c57cf5f9cd9930a1f0a65.jpg deleted file mode 100644 index 6742a6a5..00000000 Binary files a/pr/137/resized/content/images/case-study/headers/musique-music-banner.jpg/1f1cae10550c57cf5f9cd9930a1f0a65.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/case-study/headers/musique-music-banner.jpg/2dff0bc8922f329a819dd585cbef5754.jpg b/pr/137/resized/content/images/case-study/headers/musique-music-banner.jpg/2dff0bc8922f329a819dd585cbef5754.jpg deleted file mode 100644 index abbb9f19..00000000 Binary files a/pr/137/resized/content/images/case-study/headers/musique-music-banner.jpg/2dff0bc8922f329a819dd585cbef5754.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/case-study/headers/musique-music-banner.jpg/59aafcc02a4fc671d3cad25c77e637bb.jpg b/pr/137/resized/content/images/case-study/headers/musique-music-banner.jpg/59aafcc02a4fc671d3cad25c77e637bb.jpg deleted file mode 100644 index 015b6c39..00000000 Binary files a/pr/137/resized/content/images/case-study/headers/musique-music-banner.jpg/59aafcc02a4fc671d3cad25c77e637bb.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/case-study/headers/musique-music-banner.jpg/a9958791fa4a222c5d5b17ca3b5b636e.jpg b/pr/137/resized/content/images/case-study/headers/musique-music-banner.jpg/a9958791fa4a222c5d5b17ca3b5b636e.jpg deleted file mode 100644 index 90ea8b2f..00000000 Binary files a/pr/137/resized/content/images/case-study/headers/musique-music-banner.jpg/a9958791fa4a222c5d5b17ca3b5b636e.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/case-study/headers/musique-music-banner.jpg/b78cfc3e4ee6df7bf507c4fc8f08b9a0.jpg b/pr/137/resized/content/images/case-study/headers/musique-music-banner.jpg/b78cfc3e4ee6df7bf507c4fc8f08b9a0.jpg deleted file mode 100644 index 9f8dd247..00000000 Binary files a/pr/137/resized/content/images/case-study/headers/musique-music-banner.jpg/b78cfc3e4ee6df7bf507c4fc8f08b9a0.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/case-study/headers/musique-music-banner.jpg/ecfed8b4460fb69dc0850d025538a3fb.jpg b/pr/137/resized/content/images/case-study/headers/musique-music-banner.jpg/ecfed8b4460fb69dc0850d025538a3fb.jpg deleted file mode 100644 index 3ae29c3b..00000000 Binary files a/pr/137/resized/content/images/case-study/headers/musique-music-banner.jpg/ecfed8b4460fb69dc0850d025538a3fb.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/case-study/headers/panneaupocket-banner.jpg/343e1c894fa80ff82473b99a053bb17a.jpg b/pr/137/resized/content/images/case-study/headers/panneaupocket-banner.jpg/343e1c894fa80ff82473b99a053bb17a.jpg deleted file mode 100644 index 4450defb..00000000 Binary files a/pr/137/resized/content/images/case-study/headers/panneaupocket-banner.jpg/343e1c894fa80ff82473b99a053bb17a.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/case-study/headers/panneaupocket-banner.jpg/63f5c579c10945d2e2e72eb37d412037.jpg b/pr/137/resized/content/images/case-study/headers/panneaupocket-banner.jpg/63f5c579c10945d2e2e72eb37d412037.jpg deleted file mode 100644 index a6a2500e..00000000 Binary files a/pr/137/resized/content/images/case-study/headers/panneaupocket-banner.jpg/63f5c579c10945d2e2e72eb37d412037.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/case-study/headers/panneaupocket-banner.jpg/91c96f92850b2a42a784cbcdd5eb03e3.jpg b/pr/137/resized/content/images/case-study/headers/panneaupocket-banner.jpg/91c96f92850b2a42a784cbcdd5eb03e3.jpg deleted file mode 100644 index bab83123..00000000 Binary files a/pr/137/resized/content/images/case-study/headers/panneaupocket-banner.jpg/91c96f92850b2a42a784cbcdd5eb03e3.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/case-study/headers/panneaupocket-banner.jpg/ba8f61c3337904cafd02eff92fbddc20.jpg b/pr/137/resized/content/images/case-study/headers/panneaupocket-banner.jpg/ba8f61c3337904cafd02eff92fbddc20.jpg deleted file mode 100644 index 6aebe46c..00000000 Binary files a/pr/137/resized/content/images/case-study/headers/panneaupocket-banner.jpg/ba8f61c3337904cafd02eff92fbddc20.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/case-study/headers/panneaupocket-banner.jpg/baaeaf9426a098c63b345f39f1b1cd04.jpg b/pr/137/resized/content/images/case-study/headers/panneaupocket-banner.jpg/baaeaf9426a098c63b345f39f1b1cd04.jpg deleted file mode 100644 index 50fa4b33..00000000 Binary files a/pr/137/resized/content/images/case-study/headers/panneaupocket-banner.jpg/baaeaf9426a098c63b345f39f1b1cd04.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/case-study/headers/panneaupocket-banner.jpg/e9c7f838eea7a161a48541a001176cff.jpg b/pr/137/resized/content/images/case-study/headers/panneaupocket-banner.jpg/e9c7f838eea7a161a48541a001176cff.jpg deleted file mode 100644 index bd2b18f8..00000000 Binary files a/pr/137/resized/content/images/case-study/headers/panneaupocket-banner.jpg/e9c7f838eea7a161a48541a001176cff.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/case-study/musique-music-homepage.jpg/cdb54e86578cb96c05bf5e2e351d866f.jpg b/pr/137/resized/content/images/case-study/musique-music-homepage.jpg/cdb54e86578cb96c05bf5e2e351d866f.jpg deleted file mode 100644 index c156e61c..00000000 Binary files a/pr/137/resized/content/images/case-study/musique-music-homepage.jpg/cdb54e86578cb96c05bf5e2e351d866f.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/case-study/musique-music-homepage.jpg/dd98aaa11f2cf8adfb04421cbaed50bd.jpg b/pr/137/resized/content/images/case-study/musique-music-homepage.jpg/dd98aaa11f2cf8adfb04421cbaed50bd.jpg deleted file mode 100644 index 5c2a3e0c..00000000 Binary files a/pr/137/resized/content/images/case-study/musique-music-homepage.jpg/dd98aaa11f2cf8adfb04421cbaed50bd.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/case-study/musique-music-playlists.jpg/beebf9c70e30fcecd35153abb57a12d8.jpg b/pr/137/resized/content/images/case-study/musique-music-playlists.jpg/beebf9c70e30fcecd35153abb57a12d8.jpg deleted file mode 100644 index f6b09422..00000000 Binary files a/pr/137/resized/content/images/case-study/musique-music-playlists.jpg/beebf9c70e30fcecd35153abb57a12d8.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/case-study/musique-music-playlists.jpg/e547a5dd2aef3b4c076d715edf38a0e5.jpg b/pr/137/resized/content/images/case-study/musique-music-playlists.jpg/e547a5dd2aef3b4c076d715edf38a0e5.jpg deleted file mode 100644 index 9edeea97..00000000 Binary files a/pr/137/resized/content/images/case-study/musique-music-playlists.jpg/e547a5dd2aef3b4c076d715edf38a0e5.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/case-study/musique-music-results.jpg/4b23263a6df515c071c55b67785b57ff.jpg b/pr/137/resized/content/images/case-study/musique-music-results.jpg/4b23263a6df515c071c55b67785b57ff.jpg deleted file mode 100644 index 64fcf5c0..00000000 Binary files a/pr/137/resized/content/images/case-study/musique-music-results.jpg/4b23263a6df515c071c55b67785b57ff.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/case-study/musique-music-results.jpg/73b50bff4d6508ab6cb55e3212504347.jpg b/pr/137/resized/content/images/case-study/musique-music-results.jpg/73b50bff4d6508ab6cb55e3212504347.jpg deleted file mode 100644 index 38b87a38..00000000 Binary files a/pr/137/resized/content/images/case-study/musique-music-results.jpg/73b50bff4d6508ab6cb55e3212504347.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/case-study/schemas/m_and_m/case_studies_m_and_m.png/00a5c051980fc70656aa8fcbdb772f4b.png b/pr/137/resized/content/images/case-study/schemas/m_and_m/case_studies_m_and_m.png/00a5c051980fc70656aa8fcbdb772f4b.png deleted file mode 100644 index 27511ffc..00000000 Binary files a/pr/137/resized/content/images/case-study/schemas/m_and_m/case_studies_m_and_m.png/00a5c051980fc70656aa8fcbdb772f4b.png and /dev/null differ diff --git a/pr/137/resized/content/images/case-study/schemas/m_and_m/case_studies_m_and_m.png/297b7509f9acc565f5a7edfa13f25857.png b/pr/137/resized/content/images/case-study/schemas/m_and_m/case_studies_m_and_m.png/297b7509f9acc565f5a7edfa13f25857.png deleted file mode 100644 index 9d8cd4b7..00000000 Binary files a/pr/137/resized/content/images/case-study/schemas/m_and_m/case_studies_m_and_m.png/297b7509f9acc565f5a7edfa13f25857.png and /dev/null differ diff --git a/pr/137/resized/content/images/member/avatars/gfaivre.jpg/b9e2ad5ccb2390253e2c23ccbfb41dc7.jpg b/pr/137/resized/content/images/member/avatars/gfaivre.jpg/b9e2ad5ccb2390253e2c23ccbfb41dc7.jpg deleted file mode 100644 index e4a14222..00000000 Binary files a/pr/137/resized/content/images/member/avatars/gfaivre.jpg/b9e2ad5ccb2390253e2c23ccbfb41dc7.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/member/avatars/gfaivre.jpg/d2d8af0b210c37c677635983f1c02d6d.jpg b/pr/137/resized/content/images/member/avatars/gfaivre.jpg/d2d8af0b210c37c677635983f1c02d6d.jpg deleted file mode 100644 index d0c46bcc..00000000 Binary files a/pr/137/resized/content/images/member/avatars/gfaivre.jpg/d2d8af0b210c37c677635983f1c02d6d.jpg and /dev/null differ diff --git a/pr/137/resized/content/images/member/avatars/rix.png/4f7bb694753bd129601b986c5bdaabdb.png b/pr/137/resized/content/images/member/avatars/rix.png/4f7bb694753bd129601b986c5bdaabdb.png deleted file mode 100644 index f168c791..00000000 Binary files a/pr/137/resized/content/images/member/avatars/rix.png/4f7bb694753bd129601b986c5bdaabdb.png and /dev/null differ diff --git a/pr/137/resized/content/images/member/avatars/rix.png/7bdb9f34d8b53f82d37c01fd92196263.png b/pr/137/resized/content/images/member/avatars/rix.png/7bdb9f34d8b53f82d37c01fd92196263.png deleted file mode 100644 index e44c1db8..00000000 Binary files a/pr/137/resized/content/images/member/avatars/rix.png/7bdb9f34d8b53f82d37c01fd92196263.png and /dev/null differ diff --git a/pr/137/robots.txt b/pr/137/robots.txt deleted file mode 100644 index 9cfced28..00000000 --- a/pr/137/robots.txt +++ /dev/null @@ -1,4 +0,0 @@ -User-agent: * -Allow: / - -Sitemap: https://www.rix.fr/sitemap.xml \ No newline at end of file diff --git a/pr/137/services/index.html b/pr/137/services/index.html deleted file mode 100644 index 2eb96056..00000000 --- a/pr/137/services/index.html +++ /dev/null @@ -1,396 +0,0 @@ - - - - - - - Rix 🐺 - Nos services - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - -
-
-
- -

- Services - Conseils DevOps -

-
-
-
- -

Accompagnement et outillage DevOps

-

DevLab qui regroupe des outils devops éprouvés tout au long de nos 15 ans d'expérience dans l'écosystème web : stratégie et automatisation des processus de déploiement d'applications (intégration continue, réduction des cycles de livraison), traitement des logs applicatifs et système, supervision des systèmes et applicatifs en temps réel, service de secrets applicatifs (vault), stratégie d'anonymisation des données, etc.

-

Nous prenons en charge leur hébergement et leur maintenance mais nous pouvons également intervenir directement chez vous pour vous conseiller et/ou les mettre en place ou accompagner vos équipes devops.

-
    -
  • Hébergé en France;
  • -
  • 15 ans d'expérience de stratégie de déploiement et d'outillages;
  • -
  • Expertise d'outils devops opensource;
  • -
  • Mise en place, conseil et accompagnement des équipes.
  • -
-
-
- -

Performance et diagnostique

-

Parce que la vie d'une application est loin d'être un long fleuve tranquille, nous pouvons vous accompagner à diagnostiquer certaines problématiques applicatives mais également vous aider à mettre en place PRA et PCA afin qu'en cas d'incident, sa résolution soit la plus efficace/rapide possible.

-

- -

-
    -
  • Tirs de charge afin d'estimer le point de dégradation et/ou de rupture de votre service;
  • -
  • Diagnostique et piste de résolution en cas de comportement anormal;
  • -
  • Accompagnement et rédaction des PRA/PCA.
  • -
-
-
- -

Environnement de développement

-

L'un des avantages d'évoluer dans l'écosystème des concepteurs d'applications c'est d'avoir une vision globale de sa mise en œuvre. De sa conception jusqu'à sa mise en production en passant par sa réalisation.

-

Nous avons très vite pris la mesure de l'importance d'avoir un environnement de développement (une usine logiciel) qui permet aux équipes de développeurs de ne pas (ou peu) avoir à se soucier de la création de l'environnement applicatif.

-

L'idéal ? Utiliser le même socle IAC (Infrastructure As Code) pour automatiser la création de ses environnements de développement que pour créer ses environnements de production.

-
    -
  • Même socle applicatif chez tous les développeurs;
  • -
  • Gain de temps, les équipes sont efficaces;
  • -
  • Porte d'entrée pour une montée en compétences vers la philosophie DevOps;
  • -
  • Mise en place, conseil et accompagnement des équipes.
  • -
-
-
-
-
-
-

- Services - Infrastructure -

-
- -

Conception d'infrastructures sur-mesure

-

De la conception à la mise en production, nous étudions le projet que vous souhaitez faire héberger, nous assimilons votre métier, ses contraintes et ses spécificités.

-

L’objectif étant de concevoir une infrastructure en adéquation avec le besoin de vos applicatifs et de vos équipes tout en respectant leur équilibre budgétaire.

-
    -
  • Étude de votre projet, recommandation d'un fournisseur en fonction de vos objectifs;
  • -
  • Conception et déploiement vers le fournisseur de matériel choisi.
  • -
-
-
- - OVH Cloud - - - Scaleway - -
-
- - AWS - - - Google Cloud - -
-
- -

Expertise Kubernetes

-

Technologie de plus en plus demandée, Kubernetes tient beaucoup de promesses mais amène énormément de contraintes. - Nous exploitons et concevons des solutions sur ce socle depuis la disponibilité du produit et avons accompagné des équipes importantes lors de la migration de leur existant vers cette solution.

-

Nous sommes en capacité de vous apporter une étude de faisabilité, d'accompagner et former vos équipes et de concevoir l'ensemble des composants nécessaires au bon fonctionnement d'une infrastructure Kubernetes.

-
    -
  • Conseils d'experts pour la conception (ou la migration) de votre application;
  • -
  • Grosse expérience auprès de « pure player » d'envergure;
  • -
  • Mise en place, conseil et accompagnement des équipes
  • -
-
-
- - AWS - - - Google Cloud - -
-
- - AWS - - - Google Cloud - -
-
- -

Pilotage d'infrastructures

-

Le coeur de notre métier ! Assurer la maintenance en conditions opérationnelles de vos infrastructures, être proactif quant aux possibles incidents et intervenir rapidement lorsqu'ils surviennent. -

-
    -
  • Hébergé au choix en France ou ailleurs;
  • -
  • Supervision active des serveurs et des applicatifs;
  • -
  • Une équipe support disponible en cas d'incidents;
  • -
  • La garantie d'un suivi régulier (Comportement, mise à jour, sécurité).
  • -
-
-
- - AWS - - - Google Cloud - -
-
-
- - - - - - diff --git a/pr/137/site.webmanifest b/pr/137/site.webmanifest deleted file mode 100644 index ea79e55f..00000000 --- a/pr/137/site.webmanifest +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "Rix website", - "short_name": "Rix", - "icons": [ - { - "src": "/android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" - } - ], - "theme_color": "#ffffff", - "background_color": "#ffffff", - "display": "standalone" -} diff --git a/pr/137/sitemap.xml b/pr/137/sitemap.xml deleted file mode 100644 index c23f3f47..00000000 --- a/pr/137/sitemap.xml +++ /dev/null @@ -1,103 +0,0 @@ - - - - https://rix-fr.github.io/rix/pr/137/blog - 2024-02-26T00:00:00+00:00 0 monthly - - - https://rix-fr.github.io/rix/pr/137/etudes-de-cas - 2022-09-12T00:00:00+00:00 monthly - - - https://rix-fr.github.io/rix/pr/137/ - 2024-03-14T12:50:34+00:00 monthly - - - https://rix-fr.github.io/rix/pr/137/a-propos - 2024-03-14T12:50:34+00:00 monthly - - - https://rix-fr.github.io/rix/pr/137/services - 2024-03-14T12:50:37+00:00 monthly - - - https://rix-fr.github.io/rix/pr/137/contact - 2024-03-14T12:50:34+00:00 monthly - - - https://rix-fr.github.io/rix/pr/137/legal - 2024-03-14T12:50:34+00:00 monthly - - - https://rix-fr.github.io/rix/pr/137/confidentialite - 2024-03-14T12:50:34+00:00 monthly - - - https://rix-fr.github.io/rix/pr/137/blog/cours/ansible/ansible-les-roles - 2024-02-26T00:00:00+00:00 monthly - - - https://rix-fr.github.io/rix/pr/137/blog/cours/ansible/ansible-les-variables - 2024-01-29T00:00:00+00:00 monthly - - - https://rix-fr.github.io/rix/pr/137/blog/cours/ansible/ansible-les-playbooks - 2024-01-29T00:00:00+00:00 monthly - - - https://rix-fr.github.io/rix/pr/137/blog/cours/ansible/ansible-les-inventaires-statiques - 2023-11-27T00:00:00+00:00 monthly - - - https://rix-fr.github.io/rix/pr/137/blog/cours/ansible/ansible-premiers-pas - 2023-11-23T00:00:00+00:00 monthly - - - https://rix-fr.github.io/rix/pr/137/blog/cours/ansible/ansible-environnement-cle-en-main - 2023-11-22T00:00:00+00:00 monthly - - - https://rix-fr.github.io/rix/pr/137/blog/cours/cle-ssh-principes-de-base - 2023-10-20T00:00:00+00:00 monthly - - - https://rix-fr.github.io/rix/pr/137/blog/post-mortem/sre-interpretation-incident - 2023-10-16T00:00:00+00:00 monthly - - - https://rix-fr.github.io/rix/pr/137/blog/cours/docker-avec-windows-et-wsl - 2023-04-11T00:00:00+00:00 monthly - - - https://rix-fr.github.io/rix/pr/137/blog/cours/utiliser-la-configuration-ssh-client - 2023-01-02T00:00:00+00:00 monthly - - - https://rix-fr.github.io/rix/pr/137/blog/page/2 - 2022-12-22T00:00:00+00:00 monthly - - - https://rix-fr.github.io/rix/pr/137/etudes-de-cas/musique-music - 2022-09-12T00:00:00+00:00 monthly - - - https://rix-fr.github.io/rix/pr/137/etudes-de-cas/panneau-pocket - 2021-04-21T00:00:00+00:00 monthly - - - https://rix-fr.github.io/rix/pr/137/blog/linux/construire-image-debian-raspberry - 2022-12-22T00:00:00+00:00 monthly - - - https://rix-fr.github.io/rix/pr/137/blog/linux/integrer-icloud-gnome-calendar - 2022-10-25T00:00:00+00:00 monthly - - - https://rix-fr.github.io/rix/pr/137/blog/linux/osx-to-linux-part-1 - 2022-11-18T00:00:00+00:00 monthly - - - https://rix-fr.github.io/rix/pr/137/blog/styleguide/example - 2021-03-17T00:00:00+00:00 monthly - - diff --git a/pr/137/social/og-default.jpg b/pr/137/social/og-default.jpg deleted file mode 100644 index 00ed6690..00000000 Binary files a/pr/137/social/og-default.jpg and /dev/null differ diff --git a/pr/137/social/twitter-default.jpg b/pr/137/social/twitter-default.jpg deleted file mode 100644 index 1fbacbb7..00000000 Binary files a/pr/137/social/twitter-default.jpg and /dev/null differ