diff --git a/pr/131/.well-known/mta-sts.txt b/pr/131/.well-known/mta-sts.txt new file mode 100644 index 00000000..740174fd --- /dev/null +++ b/pr/131/.well-known/mta-sts.txt @@ -0,0 +1,6 @@ + +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/131/.well-known/security.txt b/pr/131/.well-known/security.txt new file mode 100644 index 00000000..6a418ea6 --- /dev/null +++ b/pr/131/.well-known/security.txt @@ -0,0 +1,3 @@ +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/131/404.html b/pr/131/404.html new file mode 100644 index 00000000..d1d824c7 --- /dev/null +++ b/pr/131/404.html @@ -0,0 +1,270 @@ + + + + + + + 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/131/a-propos/index.html b/pr/131/a-propos/index.html new file mode 100644 index 00000000..4294c18c --- /dev/null +++ b/pr/131/a-propos/index.html @@ -0,0 +1,413 @@ + + + + + + + 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/131/android-chrome-192x192.png b/pr/131/android-chrome-192x192.png new file mode 100644 index 00000000..751e5e92 Binary files /dev/null and b/pr/131/android-chrome-192x192.png differ diff --git a/pr/131/apple-touch-icon.png b/pr/131/apple-touch-icon.png new file mode 100644 index 00000000..229f5b3f Binary files /dev/null and b/pr/131/apple-touch-icon.png differ diff --git a/pr/131/blog/cours/ansible/ansible-environnement-cle-en-main/index.html b/pr/131/blog/cours/ansible/ansible-environnement-cle-en-main/index.html new file mode 100644 index 00000000..61ed8fdd --- /dev/null +++ b/pr/131/blog/cours/ansible/ansible-environnement-cle-en-main/index.html @@ -0,0 +1,468 @@ + + + + + + + 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/131/blog/cours/ansible/ansible-les-inventaires-statiques/index.html b/pr/131/blog/cours/ansible/ansible-les-inventaires-statiques/index.html new file mode 100644 index 00000000..5843ac2c --- /dev/null +++ b/pr/131/blog/cours/ansible/ansible-les-inventaires-statiques/index.html @@ -0,0 +1,751 @@ + + + + + + + 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/131/blog/cours/ansible/ansible-les-playbooks/index.html b/pr/131/blog/cours/ansible/ansible-les-playbooks/index.html new file mode 100644 index 00000000..213a9457 --- /dev/null +++ b/pr/131/blog/cours/ansible/ansible-les-playbooks/index.html @@ -0,0 +1,937 @@ + + + + + + + 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: web
+
+  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' => 'web',
+    '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
+      notify:
+          - restart_mariadb
+

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 export...

+

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 export_.

+

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 export_* 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 !

+

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/131/blog/cours/ansible/ansible-premiers-pas/index.html b/pr/131/blog/cours/ansible/ansible-premiers-pas/index.html new file mode 100644 index 00000000..4dd4e8da --- /dev/null +++ b/pr/131/blog/cours/ansible/ansible-premiers-pas/index.html @@ -0,0 +1,502 @@ + + + + + + + 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/131/blog/cours/cle-ssh-principes-de-base/index.html b/pr/131/blog/cours/cle-ssh-principes-de-base/index.html new file mode 100644 index 00000000..368747b6 --- /dev/null +++ b/pr/131/blog/cours/cle-ssh-principes-de-base/index.html @@ -0,0 +1,480 @@ + + + + + + + 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/131/blog/cours/docker-avec-windows-et-wsl/index.html b/pr/131/blog/cours/docker-avec-windows-et-wsl/index.html new file mode 100644 index 00000000..3daef81e --- /dev/null +++ b/pr/131/blog/cours/docker-avec-windows-et-wsl/index.html @@ -0,0 +1,460 @@ + + + + + + + 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/131/blog/cours/utiliser-la-configuration-ssh-client/index.html b/pr/131/blog/cours/utiliser-la-configuration-ssh-client/index.html new file mode 100644 index 00000000..518113b1 --- /dev/null +++ b/pr/131/blog/cours/utiliser-la-configuration-ssh-client/index.html @@ -0,0 +1,561 @@ + + + + + + + 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/131/blog/index.html b/pr/131/blog/index.html new file mode 100644 index 00000000..6a1e088b --- /dev/null +++ b/pr/131/blog/index.html @@ -0,0 +1,782 @@ + + + + + + + Le blog de l'équipe Rix + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + Retour à l'accueil + + + + + + + + +
+
+
+ + + +
+
+
+
+ + + +
+
+
+
+ + + + + + diff --git a/pr/131/blog/linux/construire-image-debian-raspberry/index.html b/pr/131/blog/linux/construire-image-debian-raspberry/index.html new file mode 100644 index 00000000..e072781a --- /dev/null +++ b/pr/131/blog/linux/construire-image-debian-raspberry/index.html @@ -0,0 +1,580 @@ + + + + + + + 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/131/blog/linux/integrer-icloud-gnome-calendar/index.html b/pr/131/blog/linux/integrer-icloud-gnome-calendar/index.html new file mode 100644 index 00000000..b8fcbaf5 --- /dev/null +++ b/pr/131/blog/linux/integrer-icloud-gnome-calendar/index.html @@ -0,0 +1,419 @@ + + + + + + + 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/131/blog/linux/osx-to-linux-part-1/index.html b/pr/131/blog/linux/osx-to-linux-part-1/index.html new file mode 100644 index 00000000..a099e97e --- /dev/null +++ b/pr/131/blog/linux/osx-to-linux-part-1/index.html @@ -0,0 +1,459 @@ + + + + + + + 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/131/blog/page/2/index.html b/pr/131/blog/page/2/index.html new file mode 100644 index 00000000..54d9ae90 --- /dev/null +++ b/pr/131/blog/page/2/index.html @@ -0,0 +1,385 @@ + + + + + + + Le blog de l'équipe Rix + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + Retour à l'accueil + + + + + + + + +
+
+
+ + + +
+ +
+
+ + + + + + diff --git a/pr/131/blog/post-mortem/sre-interpretation-incident/index.html b/pr/131/blog/post-mortem/sre-interpretation-incident/index.html new file mode 100644 index 00000000..aa36dda5 --- /dev/null +++ b/pr/131/blog/post-mortem/sre-interpretation-incident/index.html @@ -0,0 +1,545 @@ + + + + + + + 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/131/blog/rss.xml b/pr/131/blog/rss.xml new file mode 100644 index 00000000..8fb6d140 --- /dev/null +++ b/pr/131/blog/rss.xml @@ -0,0 +1,111 @@ + + + + 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/131/blog + Wed, 24 Jan 2024 11:19:00 +0000 + fr + + https://rix-fr.github.io/rix/pr/131/apple-touch-icon.png + Le blog de l'équipe Rix + https://rix-fr.github.io/rix/pr/131/blog + + + + Ansible - Les playbooks + Mon, 22 Jan 2024 00:00:00 +0000 + + + + https://rix-fr.github.io/rix/pr/131/blog/cours/ansible/ansible-les-playbooks + https://rix-fr.github.io/rix/pr/131/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/131/blog/cours/ansible/ansible-les-inventaires-statiques + https://rix-fr.github.io/rix/pr/131/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/131/blog/cours/ansible/ansible-premiers-pas + https://rix-fr.github.io/rix/pr/131/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/131/blog/cours/ansible/ansible-environnement-cle-en-main + https://rix-fr.github.io/rix/pr/131/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/131/blog/cours/cle-ssh-principes-de-base + https://rix-fr.github.io/rix/pr/131/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/131/blog/post-mortem/sre-interpretation-incident + https://rix-fr.github.io/rix/pr/131/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/131/blog/styleguide/example/index.html b/pr/131/blog/styleguide/example/index.html new file mode 100644 index 00000000..dcbc0803 --- /dev/null +++ b/pr/131/blog/styleguide/example/index.html @@ -0,0 +1,741 @@ + + + + + + + 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/131/browserconfig.xml b/pr/131/browserconfig.xml new file mode 100644 index 00000000..73cb7330 --- /dev/null +++ b/pr/131/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #ff4344 + + + diff --git a/pr/131/build/305.befd8c23.css b/pr/131/build/305.befd8c23.css new file mode 100644 index 00000000..af9e1106 --- /dev/null +++ b/pr/131/build/305.befd8c23.css @@ -0,0 +1 @@ +@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/131/build/fonts/montserrat-regular.4ada7f2f.woff2) format("woff2"),url(/rix/pr/131/build/fonts/montserrat-regular.4a3d6361.woff) format("woff")}@font-face{font-family:Montserrat Medium;font-style:normal;font-weight:400;src:url(/rix/pr/131/build/fonts/montserrat-medium.cf734a37.woff2) format("woff2"),url(/rix/pr/131/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/131/build/fonts/outfit-semi-bold.007337b1.woff2) format("woff2"),url(/rix/pr/131/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/131/build/fonts/dm-serif-display-regular.f7535417.woff2) format("woff2"),url(/rix/pr/131/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/131/build/fonts/icomoon.81a5b7b9.eot);src:url(/rix/pr/131/build/fonts/icomoon.81a5b7b9.eot) format("embedded-opentype"),url(/rix/pr/131/build/fonts/icomoon.450cbdb1.ttf) format("truetype"),url(/rix/pr/131/build/fonts/icomoon.32b7badb.woff) format("woff"),url(/rix/pr/131/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/131/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/131/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/131/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/131/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/131/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/131/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/131/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/131/build/images/banner-about.cf7414b4.webp)}.banner--blog{background-image:url(/rix/pr/131/build/images/banner-blog.777e5df6.webp)}.banner--case-study{background-image:url(/rix/pr/131/build/images/banner-case-study.c6780f13.webp)}.banner--home{background-image:url(/rix/pr/131/build/images/banner-home.e928350e.webp)}.banner--services{background-image:url(/rix/pr/131/build/images/banner-services.6d7424af.webp)}@media (max-width:995px){.page-home .banner{height:1950px}.banner--about{background-image:url(/rix/pr/131/build/images/banner-about-mobile.ac090375.webp)}.banner--blog{background-image:url(/rix/pr/131/build/images/banner-blog-mobile.dd80f874.webp)}.banner--case-study{background-image:url(/rix/pr/131/build/images/banner-case-study-mobile.9a484e1a.webp)}.banner--home{background-image:url(/rix/pr/131/build/images/banner-home-mobile.2fa58f16.webp)}.banner--services{background-image:url(/rix/pr/131/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/131/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/131/build/images/top.0eb15c3f.svg) no-repeat;background-position:100% 100%;height:100px;top:-100px}.about-values:after{background:url(/rix/pr/131/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/131/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/131/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/131/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/131/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/131/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/131/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/131/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/131/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/131/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/131/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/131/build/70.671fda9c.js b/pr/131/build/70.671fda9c.js new file mode 100644 index 00000000..246d8a7b --- /dev/null +++ b/pr/131/build/70.671fda9c.js @@ -0,0 +1 @@ +(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/131/contact/index.html b/pr/131/contact/index.html new file mode 100644 index 00000000..b652c6e7 --- /dev/null +++ b/pr/131/contact/index.html @@ -0,0 +1,345 @@ + + + + + + + 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/131/etudes-de-cas/index.html b/pr/131/etudes-de-cas/index.html new file mode 100644 index 00000000..be3ee149 --- /dev/null +++ b/pr/131/etudes-de-cas/index.html @@ -0,0 +1,343 @@ + + + + + + + Réalisations web : revue de nos projets depuis 18 ans | Rix + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + + + +
+
+ + + + + + diff --git a/pr/131/etudes-de-cas/musique-music/index.html b/pr/131/etudes-de-cas/musique-music/index.html new file mode 100644 index 00000000..94e095fd --- /dev/null +++ b/pr/131/etudes-de-cas/musique-music/index.html @@ -0,0 +1,406 @@ + + + + + + + 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/131/etudes-de-cas/panneau-pocket/index.html b/pr/131/etudes-de-cas/panneau-pocket/index.html new file mode 100644 index 00000000..ee027492 --- /dev/null +++ b/pr/131/etudes-de-cas/panneau-pocket/index.html @@ -0,0 +1,404 @@ + + + + + + + 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/131/favicon-16x16.png b/pr/131/favicon-16x16.png new file mode 100644 index 00000000..2243c906 Binary files /dev/null and b/pr/131/favicon-16x16.png differ diff --git a/pr/131/favicon-32x32.png b/pr/131/favicon-32x32.png new file mode 100644 index 00000000..92da1ced Binary files /dev/null and b/pr/131/favicon-32x32.png differ diff --git a/pr/131/favicon-96x96.png b/pr/131/favicon-96x96.png new file mode 100644 index 00000000..09b5d0a3 Binary files /dev/null and b/pr/131/favicon-96x96.png differ diff --git a/pr/131/favicon.ico b/pr/131/favicon.ico new file mode 100644 index 00000000..2243c906 Binary files /dev/null and b/pr/131/favicon.ico differ diff --git a/pr/131/google98e08ccbf4b44d9b.html b/pr/131/google98e08ccbf4b44d9b.html new file mode 100644 index 00000000..e195c9cc --- /dev/null +++ b/pr/131/google98e08ccbf4b44d9b.html @@ -0,0 +1 @@ +google-site-verification: google98e08ccbf4b44d9b.html \ No newline at end of file diff --git a/pr/131/images/logo-signature.png b/pr/131/images/logo-signature.png new file mode 100644 index 00000000..992665ed Binary files /dev/null and b/pr/131/images/logo-signature.png differ diff --git a/pr/131/index.html b/pr/131/index.html new file mode 100644 index 00000000..d47b0dc9 --- /dev/null +++ b/pr/131/index.html @@ -0,0 +1,557 @@ + + + + + + + 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/131/legal/index.html b/pr/131/legal/index.html new file mode 100644 index 00000000..3c66e3ce --- /dev/null +++ b/pr/131/legal/index.html @@ -0,0 +1,328 @@ + + + + + + + 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/131/) 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/131/) 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/131/mstile-152x152.png b/pr/131/mstile-152x152.png new file mode 100644 index 00000000..498a4898 Binary files /dev/null and b/pr/131/mstile-152x152.png differ diff --git a/pr/131/resized/content/images/blog/2022/icloud-sync-with-gnome-calendar/evolution-connexion-icloud.jpg/0f7f8f434b8b8c19862771a6a260297b.jpg b/pr/131/resized/content/images/blog/2022/icloud-sync-with-gnome-calendar/evolution-connexion-icloud.jpg/0f7f8f434b8b8c19862771a6a260297b.jpg new file mode 100644 index 00000000..ecb9ddc7 Binary files /dev/null and b/pr/131/resized/content/images/blog/2022/icloud-sync-with-gnome-calendar/evolution-connexion-icloud.jpg/0f7f8f434b8b8c19862771a6a260297b.jpg differ diff --git a/pr/131/resized/content/images/blog/2022/icloud-sync-with-gnome-calendar/evolution-connexion-icloud.jpg/e5d189ab24fcbd3857dbf579a7cdc917.jpg b/pr/131/resized/content/images/blog/2022/icloud-sync-with-gnome-calendar/evolution-connexion-icloud.jpg/e5d189ab24fcbd3857dbf579a7cdc917.jpg new file mode 100644 index 00000000..1d42697a Binary files /dev/null and b/pr/131/resized/content/images/blog/2022/icloud-sync-with-gnome-calendar/evolution-connexion-icloud.jpg/e5d189ab24fcbd3857dbf579a7cdc917.jpg differ diff --git a/pr/131/resized/content/images/blog/2022/icloud-sync-with-gnome-calendar/evolution-new-calendar.jpg/93cbb4c889c42310fd05bf1db5499e38.jpg b/pr/131/resized/content/images/blog/2022/icloud-sync-with-gnome-calendar/evolution-new-calendar.jpg/93cbb4c889c42310fd05bf1db5499e38.jpg new file mode 100644 index 00000000..fd12a6d9 Binary files /dev/null and b/pr/131/resized/content/images/blog/2022/icloud-sync-with-gnome-calendar/evolution-new-calendar.jpg/93cbb4c889c42310fd05bf1db5499e38.jpg differ diff --git a/pr/131/resized/content/images/blog/2022/icloud-sync-with-gnome-calendar/evolution-new-calendar.jpg/cea2561c4f29f52ce74d81d1292d2ddd.jpg b/pr/131/resized/content/images/blog/2022/icloud-sync-with-gnome-calendar/evolution-new-calendar.jpg/cea2561c4f29f52ce74d81d1292d2ddd.jpg new file mode 100644 index 00000000..0614a550 Binary files /dev/null and b/pr/131/resized/content/images/blog/2022/icloud-sync-with-gnome-calendar/evolution-new-calendar.jpg/cea2561c4f29f52ce74d81d1292d2ddd.jpg differ diff --git a/pr/131/resized/content/images/blog/2022/osx-to-linux/mailspring-screenshot-01.jpg/43afa9f7916f18f63bf34bc3e03aa0e5.jpg b/pr/131/resized/content/images/blog/2022/osx-to-linux/mailspring-screenshot-01.jpg/43afa9f7916f18f63bf34bc3e03aa0e5.jpg new file mode 100644 index 00000000..7dae32d1 Binary files /dev/null and b/pr/131/resized/content/images/blog/2022/osx-to-linux/mailspring-screenshot-01.jpg/43afa9f7916f18f63bf34bc3e03aa0e5.jpg differ diff --git a/pr/131/resized/content/images/blog/2022/osx-to-linux/mailspring-screenshot-01.jpg/9b445763b027f4a80d28d4f7af9b4555.jpg b/pr/131/resized/content/images/blog/2022/osx-to-linux/mailspring-screenshot-01.jpg/9b445763b027f4a80d28d4f7af9b4555.jpg new file mode 100644 index 00000000..b7709581 Binary files /dev/null and b/pr/131/resized/content/images/blog/2022/osx-to-linux/mailspring-screenshot-01.jpg/9b445763b027f4a80d28d4f7af9b4555.jpg differ diff --git a/pr/131/resized/content/images/blog/2022/osx-to-linux/visionneuse-pdf.jpg/4f20a6fea1b9fa1bbf2335ee8ddbbf8e.jpg b/pr/131/resized/content/images/blog/2022/osx-to-linux/visionneuse-pdf.jpg/4f20a6fea1b9fa1bbf2335ee8ddbbf8e.jpg new file mode 100644 index 00000000..b606fa55 Binary files /dev/null and b/pr/131/resized/content/images/blog/2022/osx-to-linux/visionneuse-pdf.jpg/4f20a6fea1b9fa1bbf2335ee8ddbbf8e.jpg differ diff --git a/pr/131/resized/content/images/blog/2022/osx-to-linux/visionneuse-pdf.jpg/8f62ab41cbb5677cff762e39b0aac290.jpg b/pr/131/resized/content/images/blog/2022/osx-to-linux/visionneuse-pdf.jpg/8f62ab41cbb5677cff762e39b0aac290.jpg new file mode 100644 index 00000000..628035a6 Binary files /dev/null and b/pr/131/resized/content/images/blog/2022/osx-to-linux/visionneuse-pdf.jpg/8f62ab41cbb5677cff762e39b0aac290.jpg differ diff --git a/pr/131/resized/content/images/blog/2023/ansible/ansible-part-1/ansible-ping.gif/c9b728f3e46b8cbce28924647d01323f.gif b/pr/131/resized/content/images/blog/2023/ansible/ansible-part-1/ansible-ping.gif/c9b728f3e46b8cbce28924647d01323f.gif new file mode 100644 index 00000000..dd8d7fa6 Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/ansible/ansible-part-1/ansible-ping.gif/c9b728f3e46b8cbce28924647d01323f.gif differ diff --git a/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-apt.jpg/60652788f5851e9cafba97c9469e7b37.jpg b/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-apt.jpg/60652788f5851e9cafba97c9469e7b37.jpg new file mode 100644 index 00000000..364938cb Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-apt.jpg/60652788f5851e9cafba97c9469e7b37.jpg differ diff --git a/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-apt.jpg/61297f50c3f81b91155f26fcf12b639e.jpg b/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-apt.jpg/61297f50c3f81b91155f26fcf12b639e.jpg new file mode 100644 index 00000000..597633a7 Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-apt.jpg/61297f50c3f81b91155f26fcf12b639e.jpg differ diff --git a/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-example.png/60539304d17a65d7f89b5eb88cea3749.png b/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-example.png/60539304d17a65d7f89b5eb88cea3749.png new file mode 100644 index 00000000..4e0ebe53 Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-example.png/60539304d17a65d7f89b5eb88cea3749.png differ diff --git a/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-example.png/cca60d1a0018444ee0fdca536c154d58.png b/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-example.png/cca60d1a0018444ee0fdca536c154d58.png new file mode 100644 index 00000000..4b4d89a7 Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-example.png/cca60d1a0018444ee0fdca536c154d58.png differ diff --git a/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-groups.jpg/1322e1e27cdb4902c08f0bc366de6f39.jpg b/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-groups.jpg/1322e1e27cdb4902c08f0bc366de6f39.jpg new file mode 100644 index 00000000..75921b1c Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-groups.jpg/1322e1e27cdb4902c08f0bc366de6f39.jpg differ diff --git a/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-groups.jpg/c27db12c82517f971ecdbd3d010185db.jpg b/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-groups.jpg/c27db12c82517f971ecdbd3d010185db.jpg new file mode 100644 index 00000000..a462b90d Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-groups.jpg/c27db12c82517f971ecdbd3d010185db.jpg differ diff --git a/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-handler.jpg/6eb1cc47a7c80cb64d8a1ad991701ef4.jpg b/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-handler.jpg/6eb1cc47a7c80cb64d8a1ad991701ef4.jpg new file mode 100644 index 00000000..171b298b Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-handler.jpg/6eb1cc47a7c80cb64d8a1ad991701ef4.jpg differ diff --git a/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-handler.jpg/dc709322f2276448cacc708dcd0218fc.jpg b/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-handler.jpg/dc709322f2276448cacc708dcd0218fc.jpg new file mode 100644 index 00000000..c541b8be Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-handler.jpg/dc709322f2276448cacc708dcd0218fc.jpg differ diff --git a/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-list-host.gif/3380753e5b694ee86c4d72d157c09c79.gif b/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-list-host.gif/3380753e5b694ee86c4d72d157c09c79.gif new file mode 100644 index 00000000..f22751e7 Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-list-host.gif/3380753e5b694ee86c4d72d157c09c79.gif differ diff --git a/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-list-tasks-hosts.jpg/ae35ec8bc8245ce7cada9bd81e7ff6c5.jpg b/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-list-tasks-hosts.jpg/ae35ec8bc8245ce7cada9bd81e7ff6c5.jpg new file mode 100644 index 00000000..2cd9e72d Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-list-tasks-hosts.jpg/ae35ec8bc8245ce7cada9bd81e7ff6c5.jpg differ diff --git a/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-list-tasks-hosts.jpg/cb2200015ba23ffc86168edb3bcadaf0.jpg b/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-list-tasks-hosts.jpg/cb2200015ba23ffc86168edb3bcadaf0.jpg new file mode 100644 index 00000000..dc8f72d3 Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-list-tasks-hosts.jpg/cb2200015ba23ffc86168edb3bcadaf0.jpg differ diff --git a/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-pre-tasks.jpg/4c55a2c80172722cb00c2699a96f791e.jpg b/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-pre-tasks.jpg/4c55a2c80172722cb00c2699a96f791e.jpg new file mode 100644 index 00000000..bdde45c3 Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-pre-tasks.jpg/4c55a2c80172722cb00c2699a96f791e.jpg differ diff --git a/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-pre-tasks.jpg/709fe795189f9280d551c12204c0a274.jpg b/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-pre-tasks.jpg/709fe795189f9280d551c12204c0a274.jpg new file mode 100644 index 00000000..7b5a0506 Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-pre-tasks.jpg/709fe795189f9280d551c12204c0a274.jpg differ diff --git a/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-tasks.jpg/3d3e1ff571b62722a02ea21e52fce83f.jpg b/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-tasks.jpg/3d3e1ff571b62722a02ea21e52fce83f.jpg new file mode 100644 index 00000000..6df73b10 Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-tasks.jpg/3d3e1ff571b62722a02ea21e52fce83f.jpg differ diff --git a/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-tasks.jpg/eb4c8f6e6951132cc85983159479ceae.jpg b/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-tasks.jpg/eb4c8f6e6951132cc85983159479ceae.jpg new file mode 100644 index 00000000..6b7dd275 Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/ansible/ansible-playbook/ansible-playbook-tasks.jpg/eb4c8f6e6951132cc85983159479ceae.jpg differ diff --git a/pr/131/resized/content/images/blog/2023/ansible/lazy-ansible/manala_init.gif/14dbfa2b6d24284c97f635b0e4ad70b9.gif b/pr/131/resized/content/images/blog/2023/ansible/lazy-ansible/manala_init.gif/14dbfa2b6d24284c97f635b0e4ad70b9.gif new file mode 100644 index 00000000..8a2fd860 Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/ansible/lazy-ansible/manala_init.gif/14dbfa2b6d24284c97f635b0e4ad70b9.gif differ diff --git a/pr/131/resized/content/images/blog/2023/cours-docker-wsl/add-user-to-docker-group.jpg/9c284bf54b95c30385f2bb78ea00815b.jpg b/pr/131/resized/content/images/blog/2023/cours-docker-wsl/add-user-to-docker-group.jpg/9c284bf54b95c30385f2bb78ea00815b.jpg new file mode 100644 index 00000000..b19bcd1d Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/cours-docker-wsl/add-user-to-docker-group.jpg/9c284bf54b95c30385f2bb78ea00815b.jpg differ diff --git a/pr/131/resized/content/images/blog/2023/cours-docker-wsl/add-user-to-docker-group.jpg/e2748db59362efca3ba6f018df84383b.jpg b/pr/131/resized/content/images/blog/2023/cours-docker-wsl/add-user-to-docker-group.jpg/e2748db59362efca3ba6f018df84383b.jpg new file mode 100644 index 00000000..a1126546 Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/cours-docker-wsl/add-user-to-docker-group.jpg/e2748db59362efca3ba6f018df84383b.jpg differ diff --git a/pr/131/resized/content/images/blog/2023/cours-docker-wsl/docker-desktop-settings.jpg/715304e02b5695843f019c99941dd8d3.jpg b/pr/131/resized/content/images/blog/2023/cours-docker-wsl/docker-desktop-settings.jpg/715304e02b5695843f019c99941dd8d3.jpg new file mode 100644 index 00000000..d07f6786 Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/cours-docker-wsl/docker-desktop-settings.jpg/715304e02b5695843f019c99941dd8d3.jpg differ diff --git a/pr/131/resized/content/images/blog/2023/cours-docker-wsl/docker-desktop-settings.jpg/c3b93f12d11ccf947af7185c695bacec.jpg b/pr/131/resized/content/images/blog/2023/cours-docker-wsl/docker-desktop-settings.jpg/c3b93f12d11ccf947af7185c695bacec.jpg new file mode 100644 index 00000000..2eb2885c Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/cours-docker-wsl/docker-desktop-settings.jpg/c3b93f12d11ccf947af7185c695bacec.jpg differ diff --git a/pr/131/resized/content/images/blog/2023/cours-docker-wsl/wsl_debian_shell.jpg/6fbef3ca7b35e4a4ba2503ac4d9f074b.jpg b/pr/131/resized/content/images/blog/2023/cours-docker-wsl/wsl_debian_shell.jpg/6fbef3ca7b35e4a4ba2503ac4d9f074b.jpg new file mode 100644 index 00000000..fe1c0a4f Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/cours-docker-wsl/wsl_debian_shell.jpg/6fbef3ca7b35e4a4ba2503ac4d9f074b.jpg differ diff --git a/pr/131/resized/content/images/blog/2023/cours-docker-wsl/wsl_debian_shell.jpg/f988070bcb4009128e0af18075906181.jpg b/pr/131/resized/content/images/blog/2023/cours-docker-wsl/wsl_debian_shell.jpg/f988070bcb4009128e0af18075906181.jpg new file mode 100644 index 00000000..81530f59 Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/cours-docker-wsl/wsl_debian_shell.jpg/f988070bcb4009128e0af18075906181.jpg differ diff --git a/pr/131/resized/content/images/blog/2023/cours/ssh-keygen.gif/4c624a8987673a6deb25cf1395b926ce.gif b/pr/131/resized/content/images/blog/2023/cours/ssh-keygen.gif/4c624a8987673a6deb25cf1395b926ce.gif new file mode 100644 index 00000000..08d51c36 Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/cours/ssh-keygen.gif/4c624a8987673a6deb25cf1395b926ce.gif differ diff --git a/pr/131/resized/content/images/blog/2023/cours/ssh.gif/02e06122a9eba89d4faed7b89ae519d6.gif b/pr/131/resized/content/images/blog/2023/cours/ssh.gif/02e06122a9eba89d4faed7b89ae519d6.gif new file mode 100644 index 00000000..087af9d3 Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/cours/ssh.gif/02e06122a9eba89d4faed7b89ae519d6.gif differ diff --git a/pr/131/resized/content/images/blog/2023/post-mortem/alerts.jpg/8bcbbffcadbef7f75fde9d5075fff6c7.jpg b/pr/131/resized/content/images/blog/2023/post-mortem/alerts.jpg/8bcbbffcadbef7f75fde9d5075fff6c7.jpg new file mode 100644 index 00000000..60d25ae7 Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/post-mortem/alerts.jpg/8bcbbffcadbef7f75fde9d5075fff6c7.jpg differ diff --git a/pr/131/resized/content/images/blog/2023/post-mortem/alerts.jpg/d8e4282a1f0b7b26dd76e70ffc584939.jpg b/pr/131/resized/content/images/blog/2023/post-mortem/alerts.jpg/d8e4282a1f0b7b26dd76e70ffc584939.jpg new file mode 100644 index 00000000..c4ba520a Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/post-mortem/alerts.jpg/d8e4282a1f0b7b26dd76e70ffc584939.jpg differ diff --git a/pr/131/resized/content/images/blog/2023/post-mortem/global_charge.jpg/436e0eacf0a0486949f8f44a7b32572f.jpg b/pr/131/resized/content/images/blog/2023/post-mortem/global_charge.jpg/436e0eacf0a0486949f8f44a7b32572f.jpg new file mode 100644 index 00000000..ccc8504f Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/post-mortem/global_charge.jpg/436e0eacf0a0486949f8f44a7b32572f.jpg differ diff --git a/pr/131/resized/content/images/blog/2023/post-mortem/global_charge.jpg/a53cd6721cba36d54e19dc82b424f187.jpg b/pr/131/resized/content/images/blog/2023/post-mortem/global_charge.jpg/a53cd6721cba36d54e19dc82b424f187.jpg new file mode 100644 index 00000000..2037d9d4 Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/post-mortem/global_charge.jpg/a53cd6721cba36d54e19dc82b424f187.jpg differ diff --git a/pr/131/resized/content/images/blog/2023/post-mortem/graphs_php_down.jpg/87da498e177900e1315617b6158da966.jpg b/pr/131/resized/content/images/blog/2023/post-mortem/graphs_php_down.jpg/87da498e177900e1315617b6158da966.jpg new file mode 100644 index 00000000..15e94e62 Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/post-mortem/graphs_php_down.jpg/87da498e177900e1315617b6158da966.jpg differ diff --git a/pr/131/resized/content/images/blog/2023/post-mortem/graphs_php_down.jpg/9adc412824436ea11dc0efeb47ffa049.jpg b/pr/131/resized/content/images/blog/2023/post-mortem/graphs_php_down.jpg/9adc412824436ea11dc0efeb47ffa049.jpg new file mode 100644 index 00000000..d5d50d81 Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/post-mortem/graphs_php_down.jpg/9adc412824436ea11dc0efeb47ffa049.jpg differ diff --git a/pr/131/resized/content/images/blog/2023/post-mortem/netstat.jpg/0047ca9060a60a16a5c470387107e90f.jpg b/pr/131/resized/content/images/blog/2023/post-mortem/netstat.jpg/0047ca9060a60a16a5c470387107e90f.jpg new file mode 100644 index 00000000..34ec775e Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/post-mortem/netstat.jpg/0047ca9060a60a16a5c470387107e90f.jpg differ diff --git a/pr/131/resized/content/images/blog/2023/post-mortem/netstat.jpg/2be0fdc956da99803fe72b8d43ba31ef.jpg b/pr/131/resized/content/images/blog/2023/post-mortem/netstat.jpg/2be0fdc956da99803fe72b8d43ba31ef.jpg new file mode 100644 index 00000000..20af8ab1 Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/post-mortem/netstat.jpg/2be0fdc956da99803fe72b8d43ba31ef.jpg differ diff --git a/pr/131/resized/content/images/blog/2023/post-mortem/network_db.jpg/412cb9cf6da17be08defe32ae888d3f5.jpg b/pr/131/resized/content/images/blog/2023/post-mortem/network_db.jpg/412cb9cf6da17be08defe32ae888d3f5.jpg new file mode 100644 index 00000000..387d7c42 Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/post-mortem/network_db.jpg/412cb9cf6da17be08defe32ae888d3f5.jpg differ diff --git a/pr/131/resized/content/images/blog/2023/post-mortem/network_db.jpg/c9853f03496f05e254f1e692784ff9c5.jpg b/pr/131/resized/content/images/blog/2023/post-mortem/network_db.jpg/c9853f03496f05e254f1e692784ff9c5.jpg new file mode 100644 index 00000000..bd2ffc62 Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/post-mortem/network_db.jpg/c9853f03496f05e254f1e692784ff9c5.jpg differ diff --git a/pr/131/resized/content/images/blog/2023/post-mortem/network_db_2.jpg/3155bb88a4cac88509c3b717e9475e8d.jpg b/pr/131/resized/content/images/blog/2023/post-mortem/network_db_2.jpg/3155bb88a4cac88509c3b717e9475e8d.jpg new file mode 100644 index 00000000..73024abb Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/post-mortem/network_db_2.jpg/3155bb88a4cac88509c3b717e9475e8d.jpg differ diff --git a/pr/131/resized/content/images/blog/2023/post-mortem/network_db_2.jpg/bd89ffdb7e4f9955ac21af37b9e23b3e.jpg b/pr/131/resized/content/images/blog/2023/post-mortem/network_db_2.jpg/bd89ffdb7e4f9955ac21af37b9e23b3e.jpg new file mode 100644 index 00000000..3e8fd7f6 Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/post-mortem/network_db_2.jpg/bd89ffdb7e4f9955ac21af37b9e23b3e.jpg differ diff --git a/pr/131/resized/content/images/blog/2023/post-mortem/network_web.jpg/8cce76c7e7bf1df3c864a3a66bd2271e.jpg b/pr/131/resized/content/images/blog/2023/post-mortem/network_web.jpg/8cce76c7e7bf1df3c864a3a66bd2271e.jpg new file mode 100644 index 00000000..8213c1da Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/post-mortem/network_web.jpg/8cce76c7e7bf1df3c864a3a66bd2271e.jpg differ diff --git a/pr/131/resized/content/images/blog/2023/post-mortem/network_web.jpg/bdb0e53c8e7700cd7fb63eb069c1b9d8.jpg b/pr/131/resized/content/images/blog/2023/post-mortem/network_web.jpg/bdb0e53c8e7700cd7fb63eb069c1b9d8.jpg new file mode 100644 index 00000000..eccff16e Binary files /dev/null and b/pr/131/resized/content/images/blog/2023/post-mortem/network_web.jpg/bdb0e53c8e7700cd7fb63eb069c1b9d8.jpg differ diff --git a/pr/131/resized/content/images/blog/styleguide/exemple-gif.gif/26720a805a265753b6f13e5a03fba4b5.gif b/pr/131/resized/content/images/blog/styleguide/exemple-gif.gif/26720a805a265753b6f13e5a03fba4b5.gif new file mode 100644 index 00000000..95fba5c6 Binary files /dev/null and b/pr/131/resized/content/images/blog/styleguide/exemple-gif.gif/26720a805a265753b6f13e5a03fba4b5.gif differ diff --git a/pr/131/resized/content/images/blog/styleguide/exemple-image-relative.jpg/470cd3cf457415c4960a560c201fc694.jpg b/pr/131/resized/content/images/blog/styleguide/exemple-image-relative.jpg/470cd3cf457415c4960a560c201fc694.jpg new file mode 100644 index 00000000..82c8f105 Binary files /dev/null and b/pr/131/resized/content/images/blog/styleguide/exemple-image-relative.jpg/470cd3cf457415c4960a560c201fc694.jpg differ diff --git a/pr/131/resized/content/images/blog/styleguide/exemple-image-relative.jpg/7b2c4debf6c584f73e5afaaa462ddb16.jpg b/pr/131/resized/content/images/blog/styleguide/exemple-image-relative.jpg/7b2c4debf6c584f73e5afaaa462ddb16.jpg new file mode 100644 index 00000000..34e6b367 Binary files /dev/null and b/pr/131/resized/content/images/blog/styleguide/exemple-image-relative.jpg/7b2c4debf6c584f73e5afaaa462ddb16.jpg differ diff --git a/pr/131/resized/content/images/blog/styleguide/exemple-image.jpg/460bc80c1807a1298f6305e117c1f5b0.jpg b/pr/131/resized/content/images/blog/styleguide/exemple-image.jpg/460bc80c1807a1298f6305e117c1f5b0.jpg new file mode 100644 index 00000000..429ac5d4 Binary files /dev/null and b/pr/131/resized/content/images/blog/styleguide/exemple-image.jpg/460bc80c1807a1298f6305e117c1f5b0.jpg differ diff --git a/pr/131/resized/content/images/blog/styleguide/exemple-image.jpg/6bda512686956a3df0bc5aef03d8e1c0.jpg b/pr/131/resized/content/images/blog/styleguide/exemple-image.jpg/6bda512686956a3df0bc5aef03d8e1c0.jpg new file mode 100644 index 00000000..f3b36740 Binary files /dev/null and b/pr/131/resized/content/images/blog/styleguide/exemple-image.jpg/6bda512686956a3df0bc5aef03d8e1c0.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/ansible-inventaire.jpg/123eb99d9fbf168320e89faa5ce468a5.jpg b/pr/131/resized/content/images/blog/thumbnails/ansible-inventaire.jpg/123eb99d9fbf168320e89faa5ce468a5.jpg new file mode 100644 index 00000000..87c1b101 Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/ansible-inventaire.jpg/123eb99d9fbf168320e89faa5ce468a5.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/ansible-inventaire.jpg/5dab74ef27fe54d7973d01e0758de679.jpg b/pr/131/resized/content/images/blog/thumbnails/ansible-inventaire.jpg/5dab74ef27fe54d7973d01e0758de679.jpg new file mode 100644 index 00000000..3684a05b Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/ansible-inventaire.jpg/5dab74ef27fe54d7973d01e0758de679.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/ansible-inventaire.jpg/9934b0fcbba9748a3a903ae6159048db.jpg b/pr/131/resized/content/images/blog/thumbnails/ansible-inventaire.jpg/9934b0fcbba9748a3a903ae6159048db.jpg new file mode 100644 index 00000000..8db6e6e6 Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/ansible-inventaire.jpg/9934b0fcbba9748a3a903ae6159048db.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/ansible-inventaire.jpg/e0505a9dbf401f343226eeedd5f74340.jpg b/pr/131/resized/content/images/blog/thumbnails/ansible-inventaire.jpg/e0505a9dbf401f343226eeedd5f74340.jpg new file mode 100644 index 00000000..fb30eafa Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/ansible-inventaire.jpg/e0505a9dbf401f343226eeedd5f74340.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/ansible-playbooks.jpg/13bbdeec5128bc4a67a438036ce05de1.jpg b/pr/131/resized/content/images/blog/thumbnails/ansible-playbooks.jpg/13bbdeec5128bc4a67a438036ce05de1.jpg new file mode 100644 index 00000000..6da44b3e Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/ansible-playbooks.jpg/13bbdeec5128bc4a67a438036ce05de1.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/ansible-playbooks.jpg/2903de14c5b26063f2657f61e9f7372b.jpg b/pr/131/resized/content/images/blog/thumbnails/ansible-playbooks.jpg/2903de14c5b26063f2657f61e9f7372b.jpg new file mode 100644 index 00000000..a205896f Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/ansible-playbooks.jpg/2903de14c5b26063f2657f61e9f7372b.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/ansible-playbooks.jpg/3835aca3650cfac77042ea0cfa196016.jpg b/pr/131/resized/content/images/blog/thumbnails/ansible-playbooks.jpg/3835aca3650cfac77042ea0cfa196016.jpg new file mode 100644 index 00000000..453bc891 Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/ansible-playbooks.jpg/3835aca3650cfac77042ea0cfa196016.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/ansible-playbooks.jpg/9a6bfd093813c3412a0c59303420861d.jpg b/pr/131/resized/content/images/blog/thumbnails/ansible-playbooks.jpg/9a6bfd093813c3412a0c59303420861d.jpg new file mode 100644 index 00000000..db5989da Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/ansible-playbooks.jpg/9a6bfd093813c3412a0c59303420861d.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/ansible-premier-pas.jpg/78d80603ac66329b5a5c37cefde68627.jpg b/pr/131/resized/content/images/blog/thumbnails/ansible-premier-pas.jpg/78d80603ac66329b5a5c37cefde68627.jpg new file mode 100644 index 00000000..b2fd2d9f Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/ansible-premier-pas.jpg/78d80603ac66329b5a5c37cefde68627.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/ansible-premier-pas.jpg/7d3657af4af16eeed961f5a60591cb88.jpg b/pr/131/resized/content/images/blog/thumbnails/ansible-premier-pas.jpg/7d3657af4af16eeed961f5a60591cb88.jpg new file mode 100644 index 00000000..57ed21aa Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/ansible-premier-pas.jpg/7d3657af4af16eeed961f5a60591cb88.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/ansible-premier-pas.jpg/85a273249dc4436723377363ef772dfc.jpg b/pr/131/resized/content/images/blog/thumbnails/ansible-premier-pas.jpg/85a273249dc4436723377363ef772dfc.jpg new file mode 100644 index 00000000..e8ea3a62 Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/ansible-premier-pas.jpg/85a273249dc4436723377363ef772dfc.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/ansible-premier-pas.jpg/ebf0b5572c3a5487e79c649499f0e41f.jpg b/pr/131/resized/content/images/blog/thumbnails/ansible-premier-pas.jpg/ebf0b5572c3a5487e79c649499f0e41f.jpg new file mode 100644 index 00000000..0e40968c Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/ansible-premier-pas.jpg/ebf0b5572c3a5487e79c649499f0e41f.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/calendar.jpg/1759af6b89e8a6cf93e2919540947b13.jpg b/pr/131/resized/content/images/blog/thumbnails/calendar.jpg/1759af6b89e8a6cf93e2919540947b13.jpg new file mode 100644 index 00000000..702664fa Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/calendar.jpg/1759af6b89e8a6cf93e2919540947b13.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/calendar.jpg/4045862451ebd1f21a69fbfe1f05408b.jpg b/pr/131/resized/content/images/blog/thumbnails/calendar.jpg/4045862451ebd1f21a69fbfe1f05408b.jpg new file mode 100644 index 00000000..62978076 Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/calendar.jpg/4045862451ebd1f21a69fbfe1f05408b.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/calendar.jpg/73773f96882739f398c4db9552656943.jpg b/pr/131/resized/content/images/blog/thumbnails/calendar.jpg/73773f96882739f398c4db9552656943.jpg new file mode 100644 index 00000000..3415b037 Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/calendar.jpg/73773f96882739f398c4db9552656943.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/calendar.jpg/855f4f842396350759226c8041b96f7b.jpg b/pr/131/resized/content/images/blog/thumbnails/calendar.jpg/855f4f842396350759226c8041b96f7b.jpg new file mode 100644 index 00000000..68bf39a5 Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/calendar.jpg/855f4f842396350759226c8041b96f7b.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/cle-ssh-principes.jpg/2260efd8671ecc49e9d6665506828634.jpg b/pr/131/resized/content/images/blog/thumbnails/cle-ssh-principes.jpg/2260efd8671ecc49e9d6665506828634.jpg new file mode 100644 index 00000000..5074f34c Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/cle-ssh-principes.jpg/2260efd8671ecc49e9d6665506828634.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/cle-ssh-principes.jpg/622d8f92c05e0988cb285b4a75fb17c6.jpg b/pr/131/resized/content/images/blog/thumbnails/cle-ssh-principes.jpg/622d8f92c05e0988cb285b4a75fb17c6.jpg new file mode 100644 index 00000000..a69d83a0 Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/cle-ssh-principes.jpg/622d8f92c05e0988cb285b4a75fb17c6.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/cle-ssh-principes.jpg/965641c67b52e6bd5acfbafbcf8b365f.jpg b/pr/131/resized/content/images/blog/thumbnails/cle-ssh-principes.jpg/965641c67b52e6bd5acfbafbcf8b365f.jpg new file mode 100644 index 00000000..aa7f9ec2 Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/cle-ssh-principes.jpg/965641c67b52e6bd5acfbafbcf8b365f.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/cle-ssh-principes.jpg/9cf50d92001aa3a66a9f197c9d1f7acb.jpg b/pr/131/resized/content/images/blog/thumbnails/cle-ssh-principes.jpg/9cf50d92001aa3a66a9f197c9d1f7acb.jpg new file mode 100644 index 00000000..622cdb1c Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/cle-ssh-principes.jpg/9cf50d92001aa3a66a9f197c9d1f7acb.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/inside-linux.jpg/20e08dbd8a74450ab6b73fea6ce37bb3.jpg b/pr/131/resized/content/images/blog/thumbnails/inside-linux.jpg/20e08dbd8a74450ab6b73fea6ce37bb3.jpg new file mode 100644 index 00000000..e3c2db6b Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/inside-linux.jpg/20e08dbd8a74450ab6b73fea6ce37bb3.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/inside-linux.jpg/54e9b51481250743bf2b4dc8403b4442.jpg b/pr/131/resized/content/images/blog/thumbnails/inside-linux.jpg/54e9b51481250743bf2b4dc8403b4442.jpg new file mode 100644 index 00000000..ad081861 Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/inside-linux.jpg/54e9b51481250743bf2b4dc8403b4442.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/inside-linux.jpg/cfd86d11514e7101c38ec348b99539c0.jpg b/pr/131/resized/content/images/blog/thumbnails/inside-linux.jpg/cfd86d11514e7101c38ec348b99539c0.jpg new file mode 100644 index 00000000..b126862a Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/inside-linux.jpg/cfd86d11514e7101c38ec348b99539c0.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/inside-linux.jpg/f1c0389148b3a3a33cbfff09d577338f.jpg b/pr/131/resized/content/images/blog/thumbnails/inside-linux.jpg/f1c0389148b3a3a33cbfff09d577338f.jpg new file mode 100644 index 00000000..aed6ddb1 Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/inside-linux.jpg/f1c0389148b3a3a33cbfff09d577338f.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/lazy-ansible.jpg/0e0f1c3e64deaf88e029b72ece90497d.jpg b/pr/131/resized/content/images/blog/thumbnails/lazy-ansible.jpg/0e0f1c3e64deaf88e029b72ece90497d.jpg new file mode 100644 index 00000000..b1b423eb Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/lazy-ansible.jpg/0e0f1c3e64deaf88e029b72ece90497d.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/lazy-ansible.jpg/39c637f0a1ff260e0eacd350fd6193e4.jpg b/pr/131/resized/content/images/blog/thumbnails/lazy-ansible.jpg/39c637f0a1ff260e0eacd350fd6193e4.jpg new file mode 100644 index 00000000..5994fb97 Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/lazy-ansible.jpg/39c637f0a1ff260e0eacd350fd6193e4.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/lazy-ansible.jpg/94638efcc2e1b3d7afe8add495aa3f4d.jpg b/pr/131/resized/content/images/blog/thumbnails/lazy-ansible.jpg/94638efcc2e1b3d7afe8add495aa3f4d.jpg new file mode 100644 index 00000000..6702bf9d Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/lazy-ansible.jpg/94638efcc2e1b3d7afe8add495aa3f4d.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/lazy-ansible.jpg/da0275853f294688186b86f071c3b69c.jpg b/pr/131/resized/content/images/blog/thumbnails/lazy-ansible.jpg/da0275853f294688186b86f071c3b69c.jpg new file mode 100644 index 00000000..7378777d Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/lazy-ansible.jpg/da0275853f294688186b86f071c3b69c.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/nature.jpg/49a1600a5d20e55868ab81b9b0f988aa.jpg b/pr/131/resized/content/images/blog/thumbnails/nature.jpg/49a1600a5d20e55868ab81b9b0f988aa.jpg new file mode 100644 index 00000000..c271fbd7 Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/nature.jpg/49a1600a5d20e55868ab81b9b0f988aa.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/nature.jpg/51f1c24cccc96753857fb175bb93da75.jpg b/pr/131/resized/content/images/blog/thumbnails/nature.jpg/51f1c24cccc96753857fb175bb93da75.jpg new file mode 100644 index 00000000..dea1483a Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/nature.jpg/51f1c24cccc96753857fb175bb93da75.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/nature.jpg/592124ea920e0186fde09e7fbcb2228f.jpg b/pr/131/resized/content/images/blog/thumbnails/nature.jpg/592124ea920e0186fde09e7fbcb2228f.jpg new file mode 100644 index 00000000..3bfa2ce5 Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/nature.jpg/592124ea920e0186fde09e7fbcb2228f.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/nature.jpg/e94a5d9cdc4a77a9294acb47cc6063c7.jpg b/pr/131/resized/content/images/blog/thumbnails/nature.jpg/e94a5d9cdc4a77a9294acb47cc6063c7.jpg new file mode 100644 index 00000000..30504731 Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/nature.jpg/e94a5d9cdc4a77a9294acb47cc6063c7.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/raspberrypi.jpg/40540f49ef3f539794bd75fd51f6acfd.jpg b/pr/131/resized/content/images/blog/thumbnails/raspberrypi.jpg/40540f49ef3f539794bd75fd51f6acfd.jpg new file mode 100644 index 00000000..7dd152dd Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/raspberrypi.jpg/40540f49ef3f539794bd75fd51f6acfd.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/raspberrypi.jpg/80aae598a50641b22d1bab9f3f560087.jpg b/pr/131/resized/content/images/blog/thumbnails/raspberrypi.jpg/80aae598a50641b22d1bab9f3f560087.jpg new file mode 100644 index 00000000..6b6178bc Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/raspberrypi.jpg/80aae598a50641b22d1bab9f3f560087.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/raspberrypi.jpg/c38eae8d1b9f4b8b5e845b0cef7ff224.jpg b/pr/131/resized/content/images/blog/thumbnails/raspberrypi.jpg/c38eae8d1b9f4b8b5e845b0cef7ff224.jpg new file mode 100644 index 00000000..46fe6523 Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/raspberrypi.jpg/c38eae8d1b9f4b8b5e845b0cef7ff224.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/raspberrypi.jpg/c94b2bd8ba163dd5c3a809d26180e46c.jpg b/pr/131/resized/content/images/blog/thumbnails/raspberrypi.jpg/c94b2bd8ba163dd5c3a809d26180e46c.jpg new file mode 100644 index 00000000..c6c8fe36 Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/raspberrypi.jpg/c94b2bd8ba163dd5c3a809d26180e46c.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/thisisengineering.jpg/01fb9421473fc2004327195b8776f337.jpg b/pr/131/resized/content/images/blog/thumbnails/thisisengineering.jpg/01fb9421473fc2004327195b8776f337.jpg new file mode 100644 index 00000000..869ccacc Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/thisisengineering.jpg/01fb9421473fc2004327195b8776f337.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/thisisengineering.jpg/df62f52a1bc314f874520cf74f863d76.jpg b/pr/131/resized/content/images/blog/thumbnails/thisisengineering.jpg/df62f52a1bc314f874520cf74f863d76.jpg new file mode 100644 index 00000000..025c247f Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/thisisengineering.jpg/df62f52a1bc314f874520cf74f863d76.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/thisisengineering.jpg/ed9effd75cf35b088fcedcd589759c7b.jpg b/pr/131/resized/content/images/blog/thumbnails/thisisengineering.jpg/ed9effd75cf35b088fcedcd589759c7b.jpg new file mode 100644 index 00000000..9c2a2592 Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/thisisengineering.jpg/ed9effd75cf35b088fcedcd589759c7b.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/thisisengineering.jpg/ffbe8863d77de881d181cdd4d9d83280.jpg b/pr/131/resized/content/images/blog/thumbnails/thisisengineering.jpg/ffbe8863d77de881d181cdd4d9d83280.jpg new file mode 100644 index 00000000..10dd5496 Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/thisisengineering.jpg/ffbe8863d77de881d181cdd4d9d83280.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/zsh.jpg/00fc284e365bbe49d303c8f64ebb321d.jpg b/pr/131/resized/content/images/blog/thumbnails/zsh.jpg/00fc284e365bbe49d303c8f64ebb321d.jpg new file mode 100644 index 00000000..ad021cf1 Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/zsh.jpg/00fc284e365bbe49d303c8f64ebb321d.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/zsh.jpg/377d013ecdf113dd876ef37c3c2e12b1.jpg b/pr/131/resized/content/images/blog/thumbnails/zsh.jpg/377d013ecdf113dd876ef37c3c2e12b1.jpg new file mode 100644 index 00000000..f8af6384 Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/zsh.jpg/377d013ecdf113dd876ef37c3c2e12b1.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/zsh.jpg/82e6b7dbc2cf345a64f872d47ebf7158.jpg b/pr/131/resized/content/images/blog/thumbnails/zsh.jpg/82e6b7dbc2cf345a64f872d47ebf7158.jpg new file mode 100644 index 00000000..84149809 Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/zsh.jpg/82e6b7dbc2cf345a64f872d47ebf7158.jpg differ diff --git a/pr/131/resized/content/images/blog/thumbnails/zsh.jpg/985de3f6cb7e319c4763d6fd2c992f0d.jpg b/pr/131/resized/content/images/blog/thumbnails/zsh.jpg/985de3f6cb7e319c4763d6fd2c992f0d.jpg new file mode 100644 index 00000000..48a4759c Binary files /dev/null and b/pr/131/resized/content/images/blog/thumbnails/zsh.jpg/985de3f6cb7e319c4763d6fd2c992f0d.jpg differ diff --git a/pr/131/resized/content/images/case-study/headers/musique-music-banner.jpg/1f1cae10550c57cf5f9cd9930a1f0a65.jpg b/pr/131/resized/content/images/case-study/headers/musique-music-banner.jpg/1f1cae10550c57cf5f9cd9930a1f0a65.jpg new file mode 100644 index 00000000..6742a6a5 Binary files /dev/null and b/pr/131/resized/content/images/case-study/headers/musique-music-banner.jpg/1f1cae10550c57cf5f9cd9930a1f0a65.jpg differ diff --git a/pr/131/resized/content/images/case-study/headers/musique-music-banner.jpg/2dff0bc8922f329a819dd585cbef5754.jpg b/pr/131/resized/content/images/case-study/headers/musique-music-banner.jpg/2dff0bc8922f329a819dd585cbef5754.jpg new file mode 100644 index 00000000..abbb9f19 Binary files /dev/null and b/pr/131/resized/content/images/case-study/headers/musique-music-banner.jpg/2dff0bc8922f329a819dd585cbef5754.jpg differ diff --git a/pr/131/resized/content/images/case-study/headers/musique-music-banner.jpg/59aafcc02a4fc671d3cad25c77e637bb.jpg b/pr/131/resized/content/images/case-study/headers/musique-music-banner.jpg/59aafcc02a4fc671d3cad25c77e637bb.jpg new file mode 100644 index 00000000..015b6c39 Binary files /dev/null and b/pr/131/resized/content/images/case-study/headers/musique-music-banner.jpg/59aafcc02a4fc671d3cad25c77e637bb.jpg differ diff --git a/pr/131/resized/content/images/case-study/headers/musique-music-banner.jpg/a9958791fa4a222c5d5b17ca3b5b636e.jpg b/pr/131/resized/content/images/case-study/headers/musique-music-banner.jpg/a9958791fa4a222c5d5b17ca3b5b636e.jpg new file mode 100644 index 00000000..90ea8b2f Binary files /dev/null and b/pr/131/resized/content/images/case-study/headers/musique-music-banner.jpg/a9958791fa4a222c5d5b17ca3b5b636e.jpg differ diff --git a/pr/131/resized/content/images/case-study/headers/musique-music-banner.jpg/b78cfc3e4ee6df7bf507c4fc8f08b9a0.jpg b/pr/131/resized/content/images/case-study/headers/musique-music-banner.jpg/b78cfc3e4ee6df7bf507c4fc8f08b9a0.jpg new file mode 100644 index 00000000..9f8dd247 Binary files /dev/null and b/pr/131/resized/content/images/case-study/headers/musique-music-banner.jpg/b78cfc3e4ee6df7bf507c4fc8f08b9a0.jpg differ diff --git a/pr/131/resized/content/images/case-study/headers/musique-music-banner.jpg/ecfed8b4460fb69dc0850d025538a3fb.jpg b/pr/131/resized/content/images/case-study/headers/musique-music-banner.jpg/ecfed8b4460fb69dc0850d025538a3fb.jpg new file mode 100644 index 00000000..3ae29c3b Binary files /dev/null and b/pr/131/resized/content/images/case-study/headers/musique-music-banner.jpg/ecfed8b4460fb69dc0850d025538a3fb.jpg differ diff --git a/pr/131/resized/content/images/case-study/headers/panneaupocket-banner.jpg/343e1c894fa80ff82473b99a053bb17a.jpg b/pr/131/resized/content/images/case-study/headers/panneaupocket-banner.jpg/343e1c894fa80ff82473b99a053bb17a.jpg new file mode 100644 index 00000000..4450defb Binary files /dev/null and b/pr/131/resized/content/images/case-study/headers/panneaupocket-banner.jpg/343e1c894fa80ff82473b99a053bb17a.jpg differ diff --git a/pr/131/resized/content/images/case-study/headers/panneaupocket-banner.jpg/63f5c579c10945d2e2e72eb37d412037.jpg b/pr/131/resized/content/images/case-study/headers/panneaupocket-banner.jpg/63f5c579c10945d2e2e72eb37d412037.jpg new file mode 100644 index 00000000..a6a2500e Binary files /dev/null and b/pr/131/resized/content/images/case-study/headers/panneaupocket-banner.jpg/63f5c579c10945d2e2e72eb37d412037.jpg differ diff --git a/pr/131/resized/content/images/case-study/headers/panneaupocket-banner.jpg/91c96f92850b2a42a784cbcdd5eb03e3.jpg b/pr/131/resized/content/images/case-study/headers/panneaupocket-banner.jpg/91c96f92850b2a42a784cbcdd5eb03e3.jpg new file mode 100644 index 00000000..bab83123 Binary files /dev/null and b/pr/131/resized/content/images/case-study/headers/panneaupocket-banner.jpg/91c96f92850b2a42a784cbcdd5eb03e3.jpg differ diff --git a/pr/131/resized/content/images/case-study/headers/panneaupocket-banner.jpg/ba8f61c3337904cafd02eff92fbddc20.jpg b/pr/131/resized/content/images/case-study/headers/panneaupocket-banner.jpg/ba8f61c3337904cafd02eff92fbddc20.jpg new file mode 100644 index 00000000..6aebe46c Binary files /dev/null and b/pr/131/resized/content/images/case-study/headers/panneaupocket-banner.jpg/ba8f61c3337904cafd02eff92fbddc20.jpg differ diff --git a/pr/131/resized/content/images/case-study/headers/panneaupocket-banner.jpg/baaeaf9426a098c63b345f39f1b1cd04.jpg b/pr/131/resized/content/images/case-study/headers/panneaupocket-banner.jpg/baaeaf9426a098c63b345f39f1b1cd04.jpg new file mode 100644 index 00000000..50fa4b33 Binary files /dev/null and b/pr/131/resized/content/images/case-study/headers/panneaupocket-banner.jpg/baaeaf9426a098c63b345f39f1b1cd04.jpg differ diff --git a/pr/131/resized/content/images/case-study/headers/panneaupocket-banner.jpg/e9c7f838eea7a161a48541a001176cff.jpg b/pr/131/resized/content/images/case-study/headers/panneaupocket-banner.jpg/e9c7f838eea7a161a48541a001176cff.jpg new file mode 100644 index 00000000..bd2b18f8 Binary files /dev/null and b/pr/131/resized/content/images/case-study/headers/panneaupocket-banner.jpg/e9c7f838eea7a161a48541a001176cff.jpg differ diff --git a/pr/131/resized/content/images/case-study/musique-music-homepage.jpg/cdb54e86578cb96c05bf5e2e351d866f.jpg b/pr/131/resized/content/images/case-study/musique-music-homepage.jpg/cdb54e86578cb96c05bf5e2e351d866f.jpg new file mode 100644 index 00000000..c156e61c Binary files /dev/null and b/pr/131/resized/content/images/case-study/musique-music-homepage.jpg/cdb54e86578cb96c05bf5e2e351d866f.jpg differ diff --git a/pr/131/resized/content/images/case-study/musique-music-homepage.jpg/dd98aaa11f2cf8adfb04421cbaed50bd.jpg b/pr/131/resized/content/images/case-study/musique-music-homepage.jpg/dd98aaa11f2cf8adfb04421cbaed50bd.jpg new file mode 100644 index 00000000..5c2a3e0c Binary files /dev/null and b/pr/131/resized/content/images/case-study/musique-music-homepage.jpg/dd98aaa11f2cf8adfb04421cbaed50bd.jpg differ diff --git a/pr/131/resized/content/images/case-study/musique-music-playlists.jpg/beebf9c70e30fcecd35153abb57a12d8.jpg b/pr/131/resized/content/images/case-study/musique-music-playlists.jpg/beebf9c70e30fcecd35153abb57a12d8.jpg new file mode 100644 index 00000000..f6b09422 Binary files /dev/null and b/pr/131/resized/content/images/case-study/musique-music-playlists.jpg/beebf9c70e30fcecd35153abb57a12d8.jpg differ diff --git a/pr/131/resized/content/images/case-study/musique-music-playlists.jpg/e547a5dd2aef3b4c076d715edf38a0e5.jpg b/pr/131/resized/content/images/case-study/musique-music-playlists.jpg/e547a5dd2aef3b4c076d715edf38a0e5.jpg new file mode 100644 index 00000000..9edeea97 Binary files /dev/null and b/pr/131/resized/content/images/case-study/musique-music-playlists.jpg/e547a5dd2aef3b4c076d715edf38a0e5.jpg differ diff --git a/pr/131/resized/content/images/case-study/musique-music-results.jpg/4b23263a6df515c071c55b67785b57ff.jpg b/pr/131/resized/content/images/case-study/musique-music-results.jpg/4b23263a6df515c071c55b67785b57ff.jpg new file mode 100644 index 00000000..64fcf5c0 Binary files /dev/null and b/pr/131/resized/content/images/case-study/musique-music-results.jpg/4b23263a6df515c071c55b67785b57ff.jpg differ diff --git a/pr/131/resized/content/images/case-study/musique-music-results.jpg/73b50bff4d6508ab6cb55e3212504347.jpg b/pr/131/resized/content/images/case-study/musique-music-results.jpg/73b50bff4d6508ab6cb55e3212504347.jpg new file mode 100644 index 00000000..38b87a38 Binary files /dev/null and b/pr/131/resized/content/images/case-study/musique-music-results.jpg/73b50bff4d6508ab6cb55e3212504347.jpg differ diff --git a/pr/131/resized/content/images/case-study/schemas/m_and_m/case_studies_m_and_m.png/00a5c051980fc70656aa8fcbdb772f4b.png b/pr/131/resized/content/images/case-study/schemas/m_and_m/case_studies_m_and_m.png/00a5c051980fc70656aa8fcbdb772f4b.png new file mode 100644 index 00000000..27511ffc Binary files /dev/null and b/pr/131/resized/content/images/case-study/schemas/m_and_m/case_studies_m_and_m.png/00a5c051980fc70656aa8fcbdb772f4b.png differ diff --git a/pr/131/resized/content/images/case-study/schemas/m_and_m/case_studies_m_and_m.png/297b7509f9acc565f5a7edfa13f25857.png b/pr/131/resized/content/images/case-study/schemas/m_and_m/case_studies_m_and_m.png/297b7509f9acc565f5a7edfa13f25857.png new file mode 100644 index 00000000..9d8cd4b7 Binary files /dev/null and b/pr/131/resized/content/images/case-study/schemas/m_and_m/case_studies_m_and_m.png/297b7509f9acc565f5a7edfa13f25857.png differ diff --git a/pr/131/resized/content/images/member/avatars/gfaivre.jpg/b9e2ad5ccb2390253e2c23ccbfb41dc7.jpg b/pr/131/resized/content/images/member/avatars/gfaivre.jpg/b9e2ad5ccb2390253e2c23ccbfb41dc7.jpg new file mode 100644 index 00000000..e4a14222 Binary files /dev/null and b/pr/131/resized/content/images/member/avatars/gfaivre.jpg/b9e2ad5ccb2390253e2c23ccbfb41dc7.jpg differ diff --git a/pr/131/resized/content/images/member/avatars/gfaivre.jpg/d2d8af0b210c37c677635983f1c02d6d.jpg b/pr/131/resized/content/images/member/avatars/gfaivre.jpg/d2d8af0b210c37c677635983f1c02d6d.jpg new file mode 100644 index 00000000..d0c46bcc Binary files /dev/null and b/pr/131/resized/content/images/member/avatars/gfaivre.jpg/d2d8af0b210c37c677635983f1c02d6d.jpg differ diff --git a/pr/131/resized/content/images/member/avatars/rix.png/4f7bb694753bd129601b986c5bdaabdb.png b/pr/131/resized/content/images/member/avatars/rix.png/4f7bb694753bd129601b986c5bdaabdb.png new file mode 100644 index 00000000..f168c791 Binary files /dev/null and b/pr/131/resized/content/images/member/avatars/rix.png/4f7bb694753bd129601b986c5bdaabdb.png differ diff --git a/pr/131/resized/content/images/member/avatars/rix.png/7bdb9f34d8b53f82d37c01fd92196263.png b/pr/131/resized/content/images/member/avatars/rix.png/7bdb9f34d8b53f82d37c01fd92196263.png new file mode 100644 index 00000000..e44c1db8 Binary files /dev/null and b/pr/131/resized/content/images/member/avatars/rix.png/7bdb9f34d8b53f82d37c01fd92196263.png differ diff --git a/pr/131/robots.txt b/pr/131/robots.txt new file mode 100644 index 00000000..9cfced28 --- /dev/null +++ b/pr/131/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://www.rix.fr/sitemap.xml \ No newline at end of file diff --git a/pr/131/services/index.html b/pr/131/services/index.html new file mode 100644 index 00000000..360e9630 --- /dev/null +++ b/pr/131/services/index.html @@ -0,0 +1,396 @@ + + + + + + + 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/131/site.webmanifest b/pr/131/site.webmanifest new file mode 100644 index 00000000..ea79e55f --- /dev/null +++ b/pr/131/site.webmanifest @@ -0,0 +1,14 @@ +{ + "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/131/sitemap.xml b/pr/131/sitemap.xml new file mode 100644 index 00000000..2d1772b0 --- /dev/null +++ b/pr/131/sitemap.xml @@ -0,0 +1,95 @@ + + + + https://rix-fr.github.io/rix/pr/131/blog + 2024-01-22T00:00:00+00:00 0 monthly + + + https://rix-fr.github.io/rix/pr/131/etudes-de-cas + 2022-09-12T00:00:00+00:00 monthly + + + https://rix-fr.github.io/rix/pr/131/ + 2024-01-24T11:19:01+00:00 monthly + + + https://rix-fr.github.io/rix/pr/131/a-propos + 2024-01-24T11:19:01+00:00 monthly + + + https://rix-fr.github.io/rix/pr/131/services + 2024-01-24T11:19:01+00:00 monthly + + + https://rix-fr.github.io/rix/pr/131/contact + 2024-01-24T11:19:01+00:00 monthly + + + https://rix-fr.github.io/rix/pr/131/legal + 2024-01-24T11:19:01+00:00 monthly + + + https://rix-fr.github.io/rix/pr/131/confidentialite + 2024-01-24T11:19:01+00:00 monthly + + + https://rix-fr.github.io/rix/pr/131/blog/cours/ansible/ansible-les-playbooks + 2024-01-22T00:00:00+00:00 monthly + + + https://rix-fr.github.io/rix/pr/131/blog/cours/ansible/ansible-les-inventaires-statiques + 2023-11-27T00:00:00+00:00 monthly + + + https://rix-fr.github.io/rix/pr/131/blog/cours/ansible/ansible-premiers-pas + 2023-11-23T00:00:00+00:00 monthly + + + https://rix-fr.github.io/rix/pr/131/blog/cours/ansible/ansible-environnement-cle-en-main + 2023-11-22T00:00:00+00:00 monthly + + + https://rix-fr.github.io/rix/pr/131/blog/cours/cle-ssh-principes-de-base + 2023-10-20T00:00:00+00:00 monthly + + + https://rix-fr.github.io/rix/pr/131/blog/post-mortem/sre-interpretation-incident + 2023-10-16T00:00:00+00:00 monthly + + + https://rix-fr.github.io/rix/pr/131/blog/cours/docker-avec-windows-et-wsl + 2023-04-11T00:00:00+00:00 monthly + + + https://rix-fr.github.io/rix/pr/131/blog/cours/utiliser-la-configuration-ssh-client + 2023-01-02T00:00:00+00:00 monthly + + + https://rix-fr.github.io/rix/pr/131/blog/linux/construire-image-debian-raspberry + 2022-12-22T00:00:00+00:00 monthly + + + https://rix-fr.github.io/rix/pr/131/blog/linux/integrer-icloud-gnome-calendar + 2022-10-25T00:00:00+00:00 monthly + + + https://rix-fr.github.io/rix/pr/131/blog/page/2 + 2022-11-18T00:00:00+00:00 monthly + + + https://rix-fr.github.io/rix/pr/131/etudes-de-cas/musique-music + 2022-09-12T00:00:00+00:00 monthly + + + https://rix-fr.github.io/rix/pr/131/etudes-de-cas/panneau-pocket + 2021-04-21T00:00:00+00:00 monthly + + + https://rix-fr.github.io/rix/pr/131/blog/linux/osx-to-linux-part-1 + 2022-11-18T00:00:00+00:00 monthly + + + https://rix-fr.github.io/rix/pr/131/blog/styleguide/example + 2021-03-17T00:00:00+00:00 monthly + + diff --git a/pr/131/social/og-default.jpg b/pr/131/social/og-default.jpg new file mode 100644 index 00000000..00ed6690 Binary files /dev/null and b/pr/131/social/og-default.jpg differ diff --git a/pr/131/social/twitter-default.jpg b/pr/131/social/twitter-default.jpg new file mode 100644 index 00000000..1fbacbb7 Binary files /dev/null and b/pr/131/social/twitter-default.jpg differ