Gestion des paquets en Python 1/4

Gestion des versions et des paquets en Python

1. Le modèle mental indispensable

Les difficultés viennent souvent du fait que l’on confond plusieurs éléments indépendants :

  1. la version de Python ;
  2. l’interpréteur Python réellement exécuté ;
  3. l’environnement Python actif ;
  4. l’outil d’installation des paquets ;
  5. les versions des bibliothèques ;
  6. les formats de distribution comme les wheels ;
  7. le système d’exploitation et l’architecture du processeur.

Lorsqu’on exécute :

pip install pandas

la vraie question n’est pas seulement :

Quelle version de pandas sera installée ?

Il faut aussi demander :

Quel pip est exécuté, à quel interpréteur Python appartient-il, dans quel environnement installe-t-il, et existe-t-il un fichier binaire compatible avec mon système ?

Partie 1 — Une installation Python n’est pas un simple exécutable

2. Qu’est-ce qu’une version de Python ?

Une installation Python contient notamment :

  • un interpréteur ;
  • la bibliothèque standard ;
  • des fichiers de configuration ;
  • un répertoire destiné aux paquets externes ;
  • éventuellement pip ;
  • parfois des bibliothèques natives ;
  • des en-têtes C pour compiler des extensions.

L’exécutable peut porter différents noms :

python
python3
python3.11
python3.12
python.exe

Sous Windows, on peut également disposer du lanceur Python :

py

Le lanceur py est particulièrement utile lorsque plusieurs versions sont installées.

Exemples :

py --list
py -3.11 --version
py -3.12 --version
py -3.11 script.py

Une version Python est constituée de l’interpréteur, de la bibliothèque standard et de ses fichiers associés.

3. Version majeure, mineure et corrective

Dans :

Python 3.12.7

on distingue :

3      version majeure
12     version mineure
7      version corrective

Les incompatibilités de paquets concernent principalement la version mineure.

Par exemple :

Python 3.10
Python 3.11
Python 3.12
Python 3.13

ne doivent pas être considérés comme quatre mises à jour parfaitement interchangeables.

Une bibliothèque peut déclarer :

Requires-Python >=3.10,<3.13

Elle acceptera Python 3.10, 3.11 et 3.12, mais refusera Python 3.13.

À l’inverse, les versions correctives sont normalement compatibles entre elles :

3.12.1
3.12.7
3.12.10

Un environnement virtuel créé avec Python 3.12.1 reste généralement utilisable avec les correctifs de la branche 3.12, même s’il est souvent préférable de le recréer après un changement important de l’installation système.

4. CPython n’est pas l’unique implémentation de Python

L’implémentation utilisée dans l’immense majorité des projets est CPython.

Il existe cependant d’autres implémentations :

  • PyPy ;
  • GraalPy ;
  • MicroPython ;
  • Jython ;
  • IronPython.

Lorsqu’une wheel contient le tag :

cp312

cela signifie généralement :

CPython 3.12

Une wheel cp312 ne vise pas nécessairement PyPy 3.12.

Vérification :

python -c "import platform; print(platform.python_implementation())"

Résultat typique :

CPython

Partie 2 — Pourquoi plusieurs commandes Python coexistent

5. Le problème du PATH

Lorsque l’on tape :

python

le système cherche un exécutable dans les répertoires inscrits dans la variable PATH.

Sous Windows :

where python
where pip

Sous Linux ou macOS :

which python
which python3
which pip
type -a python
type -a pip

Il est fréquent d’obtenir plusieurs installations :

C:\Users\yvon\AppData\Local\Programs\Python\Python311\python.exe
C:\Users\yvon\AppData\Local\Programs\Python\Python312\python.exe
C:\Windows\py.exe

Le premier chemin trouvé dans PATH est généralement utilisé.

Ainsi, il est possible que :

python

désigne Python 3.12, alors que :

pip

appartienne à Python 3.11.

C’est l’une des causes les plus fréquentes de ce problème :

pip install requests

réussit, mais :

import requests

échoue avec :

ModuleNotFoundError: No module named 'requests'

6. La règle d’or : utiliser python -m pip

Au lieu de :

pip install requests

utiliser :

python -m pip install requests

Ou, sous Windows :

py -3.12 -m pip install requests

Cette syntaxe signifie :

Exécute le module pip avec cet interpréteur Python précis.

Elle garantit que le pip utilisé appartient au Python indiqué.

Commandes de diagnostic :

python --version
python -c "import sys; print(sys.executable)"
python -m pip --version

Exemple :

Python 3.12.7
C:\Python312\python.exe
pip 25.x from C:\Python312\Lib\site-packages\pip

Les trois chemins doivent être cohérents.

7. Diagnostic complet de l’interpréteur actif

Cette commande est particulièrement utile :

python -c "import sys, platform; print('Executable:', sys.executable); print('Version:', sys.version); print('Prefix:', sys.prefix); print('Base prefix:', sys.base_prefix); print('Architecture:', platform.machine())"

Informations importantes :

sys.executable

Chemin de l’interpréteur exécuté :

import sys
print(sys.executable)

sys.version

Version détaillée :

print(sys.version)

sys.prefix

Racine de l’environnement actif.

sys.base_prefix

Racine de l’installation Python d’origine.

Dans un environnement virtuel :

sys.prefix != sys.base_prefix

Test :

python -c "import sys; print(sys.prefix != sys.base_prefix)"

Résultat :

True

signifie généralement qu’un environnement virtuel est actif.

Partie 3 — Installation globale, utilisateur et virtuelle

8. Les trois emplacements principaux

Un paquet peut être installé :

  1. globalement ;
  2. dans le répertoire utilisateur ;
  3. dans un environnement virtuel.

9. Installation globale

Exemple :

python -m pip install requests

sans environnement virtuel.

Le paquet est installé dans le site-packages associé à l’interpréteur global.

Sous Windows :

C:\Python312\Lib\site-packages

Sous Linux :

/usr/local/lib/python3.12/site-packages

ou :

/usr/lib/python3/dist-packages

Pourquoi l’installation globale est problématique

Deux projets peuvent demander des versions différentes :

Projet A : Django 4.2
Projet B : Django 5.2

Un environnement global ne peut généralement contenir qu’une version de Django pour un même interpréteur.

Installer Django 5.2 pour le projet B peut donc casser le projet A.

Autre problème : sur Linux, le Python global peut être utilisé par le système d’exploitation.

Modifier ses paquets peut casser :

  • un gestionnaire de paquets ;
  • des scripts d’administration ;
  • des outils système ;
  • des composants installés par la distribution.

Les distributions modernes peuvent déclarer l’environnement Python système comme « externally managed », afin d’empêcher pip de le modifier directement.

10. Installation utilisateur

Commande :

python -m pip install --user requests

Le paquet est installé dans un répertoire appartenant à l’utilisateur.

Exemple Windows :

C:\Users\yvon\AppData\Roaming\Python\Python312\site-packages

Exemple Linux :

~/.local/lib/python3.12/site-packages

Cette méthode évite les droits administrateur, mais elle n’isole pas les projets.

Tous les projets utilisant ce Python peuvent voir ces paquets.

Elle est donc utile dans certains cas ponctuels, mais elle ne remplace pas un environnement virtuel.

11. Installation dans un environnement virtuel

Un environnement virtuel possède notamment :

  • son propre exécutable Python ou un lien vers celui-ci ;
  • son propre site-packages ;
  • son propre pip ;
  • un fichier pyvenv.cfg.

Dans sa forme minimale, un environnement virtuel contient un exécutable ou lien vers Python, un répertoire site-packages et un fichier pyvenv.cfg.

Création :

python -m venv .venv

Le module venv crée l’environnement avec la version de Python utilisée pour lancer la commande.

Ainsi :

python3.11 -m venv .venv

crée un environnement en Python 3.11.

Alors que :

python3.12 -m venv .venv

crée un environnement en Python 3.12.

Un environnement virtuel ne choisit pas lui-même une version de Python.

Il hérite de l’interpréteur qui l’a créé.

Partie 4 — Utilisation correcte de venv

12. Création sous Windows

Avec le lanceur Python :

py -3.12 -m venv .venv

Activation dans PowerShell :

.venv\Scripts\Activate.ps1

Activation dans cmd.exe :

.venv\Scripts\activate.bat

Désactivation :

deactivate

13. Création sous Linux ou macOS

python3.12 -m venv .venv

Activation :

source .venv/bin/activate

Désactivation :

deactivate

14. Ce que fait réellement l’activation

L’activation ne démarre pas une machine virtuelle.

Elle modifie principalement la variable PATH afin que :

python
pip

désignent en priorité les exécutables situés dans .venv.

Sous Windows :

.venv\Scripts\python.exe
.venv\Scripts\pip.exe

Sous Linux :

.venv/bin/python
.venv/bin/pip

Il est possible d’utiliser un environnement virtuel sans l’activer :

Sous Windows :

.venv\Scripts\python.exe script.py
.venv\Scripts\python.exe -m pip install requests

Sous Linux :

.venv/bin/python script.py
.venv/bin/python -m pip install requests

L’activation est donc un confort, pas une obligation technique.

15. Vérifier l’environnement actif

Sous Windows :

where python
python -c "import sys; print(sys.executable)"
python -m pip --version

Sous Linux :

which python
python -c "import sys; print(sys.executable)"
python -m pip --version

Résultat attendu :

...\mon-projet\.venv\Scripts\python.exe

ou :

.../mon-projet/.venv/bin/python

16. Ne jamais versionner .venv

Dans .gitignore :

.venv/
venv/
env/
__pycache__/
*.pyc

Un environnement virtuel :

  • contient des chemins absolus ;
  • dépend du système d’exploitation ;
  • dépend de l’architecture ;
  • peut contenir des binaires natifs ;
  • peut devenir très volumineux.

Il ne doit donc pas être copié entre machines ni ajouté à Git.

On versionne les fichiers décrivant les dépendances, pas l’environnement lui-même.

17. Recréer un environnement propre

Lorsqu’un environnement devient incohérent, le réparer paquet par paquet est souvent une mauvaise stratégie.

Approche propre :

Sous PowerShell :

deactivate
Remove-Item -Recurse -Force .venv
py -3.12 -m venv .venv
.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install -r requirements.txt

Sous Linux :

deactivate
rm -rf .venv
python3.12 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -r requirements.txt

Partie 5 — Que fait réellement pip install ?

18. Les grandes étapes

Lorsqu’on lance :

python -m pip install pandas

pip doit notamment :

  1. identifier la demande ;
  2. lire les métadonnées du projet ;
  3. vérifier la version de Python ;
  4. résoudre les dépendances ;
  5. choisir les versions compatibles ;
  6. sélectionner un fichier de distribution ;
  7. télécharger une wheel ou une archive source ;
  8. éventuellement compiler le paquet ;
  9. installer les fichiers ;
  10. créer les scripts exécutables ;
  11. enregistrer les métadonnées d’installation.

La documentation de pip install décrit notamment les étapes d’identification des exigences, de résolution des dépendances, de construction des wheels puis d’installation.

19. PyPI

PyPI, Python Package Index, est le dépôt public principal de paquets Python. Les auteurs y publient leurs distributions et pip peut y rechercher les fichiers adaptés.

Une page PyPI peut proposer plusieurs fichiers pour une seule version :

package-1.0.0.tar.gz
package-1.0.0-py3-none-any.whl
package-1.0.0-cp312-cp312-win_amd64.whl
package-1.0.0-cp312-cp312-manylinux_2_17_x86_64.whl

Ces fichiers ne sont pas interchangeables.

La raison est que le même paquet doit pouvoir fonctionner dans des contextes très différents. Chaque fichier correspond à un cas précis :

  • le fichier .tar.gz est le code source, utilisé quand aucune version précompilée n’est disponible ;
  • le fichier py3-none-any.whl est une version pure Python, compatible avec tous les systèmes ;
  • les fichiers cp312-... sont des versions compilées pour une version précise de Python (ici CPython 3.12) ;
  • les suffixes win_amd64 ou manylinux_2_17_x86_64 indiquent le système d’exploitation et l’architecture.

En résumé, plusieurs versions existent pour garantir que pip puisse installer le bon fichier selon :

  • ta version de Python,
  • ton système d’exploitation,
  • ton architecture matérielle,
  • et la présence ou non de code compilé.

C’est ce mécanisme qui permet à un même paquet d’être utilisable sur presque toutes les machines, sans modification manuelle.

Git cloner avec le personal access token PAT pour un repo privé

Avec un repository publique, c’est facile de cloner, c’est accessible à tout le monde. Mais quand il s’agit de repository privé,et c’est souvent le cas pour les projet d’entreprise et vos saas, c’est devenu plus compliqué.

Avant il suffisait d’entre le nom etmot de passe, masi aujourd’hui cette méthode est dépréciée, pour plus de sécurité.

Il vous faudré générer un PAT (personal access token). Et pour rendre encore plus compliqué, il y a le PAT classique (qu’on va utiliser) et le PAT avec gestion fine des accès (Fine grained token).

git clone https://refschool:ghp_TON_PAT@github.com/refschool/team-of.git .

Comment installer n8n sur O2switch (afin de ne pas payer)

J’ai eu confirmation que n8n ne peut s’installer sur O2switch. Du coup je vais le faire avec une instance DigitalOcean, vous pouvez aller sur l’offre KVM de Hostinger à 6€/mois à l’heure où j’écris ces lignes. O2Switch est vraiment taillé pour l’hébergement PHP/Mysql, hélas, si vous sortez de ce setup, comme pour NodeJs ou Python, c’est moins pratique. Notamment Phusion Passenger, qui est loin d’être aussi flexible qu’un reverse proxy Nginx.

Créez votre instance VPS, vous devez avoir accès à Linux en ligne de commande, en général ce sera un Ubuntu. Je ne détaille pas le process ici car la page n’est pas consacrée à l’hébergement.

Installation de NodeJS

sudo apt update
sudo apt install node

installer n8n avec node et npm

npm install -g n8n

// Mais il est conseillé de faire tourner n8n dans un docker, aussi, il faut d'abord installer Docker Engine. Chez DigitalOcean, vous pouvez réserver un VPS avec Docker déjà installé (avec Ubuntu)

Installation en local sur votre ordinateur

npm install -g n8n

// pour lancer
n8n

ensuite (sous Windows) appuyer sur la touche 'o' pour lancer l'application web.
Laissez la fenêtre ouverte pour faire tourner n8n.

Nginx serveur cache pour WordPress

Prérequis : vous devez avoir à minima un VPS et la liberté d’installer ce que vous voulez (ce qui n’est pas le cas de O2switch). hostinger propose un VPS de ce calibre, DigitalOcean propose des instances à la demande.

Installer Nginx et PHP-FPM

# Mettre à jour le système
sudo apt update && sudo apt upgrade -y

# Installer Nginx
sudo apt install nginx -y

# Installer PHP-FPM et extensions nécessaires pour WordPress
sudo apt install php-fpm php-mysql php-curl php-gd php-mbstring php-xml php-xmlrpc php-soap php-intl -y

Créer un dossier pour le cache Nginx

sudo mkdir -p /var/cache/nginx
sudo chown -R www-data:www-data /var/cache/nginx
sudo chmod -R 700 /var/cache/nginx

Configurer Nginx pour le cache

Éditer le fichier de site WordPress. Supposons que le site est dans /etc/nginx/sites-available/wordpress.conf.

server {
    listen 80;
    server_name ton-domaine.com www.ton-domaine.com;

    root /var/www/wordpress;
    index index.php index.html index.htm;

    # Cache configuration
    proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=WORDPRESS:100m inactive=60m use_temp_path=off;

    # Log files
    access_log /var/log/nginx/wordpress.access.log;
    error_log /var/log/nginx/wordpress.error.log;

    # Main location
    location / {
        try_files $uri $uri/ /index.php?$args;

        # Enable caching
        proxy_cache WORDPRESS;
        proxy_cache_valid 200 60m;  # Cache 200 responses 60 minutes
        proxy_cache_valid 301 302 10m;  # Cache redirects 10 minutes
        proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
    }

    # PHP-FPM handling
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.2-fpm.sock; # Adapte selon ta version de PHP
    }

    # Disable caching for admin and login
    location ~* /(wp-admin|wp-login\.php) {
        proxy_cache off;
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
    }

    # Static files caching
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|webp)$ {
        expires 30d;
        access_log off;
    }
}

Ici, proxy_cache_path définit le cache, sa taille (100m) et l’endroit où il est stocké. On active le cache pour les pages publiques, et on le désactive pour les pages d’administration ou login.

Tester la configuration Nginx

sudo nginx -t
sudo systemctl reload nginx

Activer le log de cache

tail -f /var/log/nginx/wordpress.access.log
  1. On devrait voir des en-têtes comme :
  • HIT → la page vient du cache.
  • MISS → la page vient du serveur PHP (premier chargement ou cache expiré).

Pour ajouter un en-tête qui montre si une page est en cache :

proxy_set_header X-Cache $upstream_cache_status;

Placez-le dans la section location /.

Conseils pour WordPress

  • Installe un plugin de cache léger (comme LiteSpeed Cache ou WP Super Cache) si tu veux contrôler le purging automatique, sinon Nginx ne sait pas quand le contenu change.
  • Les images et fichiers statiques (CSS/JS) peuvent être directement servis avec expires pour ne pas passer par PHP.
  • Configure un cron ou purge Nginx pour vider le cache si tu changes beaucoup de contenu.

Version sans plugin Cache côté WordPress

Il n’est pas obligé d’avoir un WP CACHE pour dire à Nginx quand c’est invalidé. La version sans WP CACHE est disponible sur cette page.

1. Nginx en mode cache simple (proxy_cache)

  • Nginx cache les réponses HTTP qu’il reçoit de ton backend (PHP-FPM pour WordPress).
  • Il ne connaît pas la logique métier de WordPress : il ne sait pas si un post a été édité, si un commentaire est ajouté, etc.
  • Donc, par défaut, Nginx utilise un TTL (proxy_cache_valid 200 60m) pour déterminer combien de temps garder la page.
  • Après expiration, il refait une requête à PHP-FPM.

💡 En résumé : Nginx “voit” seulement les codes HTTP et les headers, pas l’état de WordPress.

2. Comment invalider intelligemment le cache ?

Pour qu’un cache Nginx soit “intelligent” et se purge automatiquement quand WordPress change quelque chose :

  1. Plugins WordPress dédiés :
    • LiteSpeed Cache ou WP Rocket peuvent envoyer des commandes de purge au serveur Nginx via HTTP ou socket quand un post/page est mis à jour.
    • Le plugin sait quand une page devient “obsolète” et demande à Nginx de la supprimer du cache.
  2. Purge manuel via Nginx :
    • On peut créer une URL spéciale comme /purge/some-path que WordPress appelle via un hook save_post.
    • Cette URL est configurée pour supprimer les fichiers du cache correspondants.
  3. Cache-Control et ETag :
    • Si WordPress envoie les headers Cache-Control et ETag, Nginx peut les respecter pour savoir quand revalider une page.
    • Cela permet un cache plus dynamique sans toucher au filesystem.

En pratique : Nginx est très rapide pour servir le cache, mais il a besoin d’un signal externe pour savoir quand le contenu est obsolète. WordPress côté PHP ou un plugin fournit ce signal.

Cloner avec Git partiellement votre projet

Saviez vosu que vous pouvez ne cloner qu’un répertoire ou plusieurs répertoire de votre repository Git? Ainsi vous économiserez de la place ! Ceci est particulièrement pertinent dans les monorepos, un monorepo est un repository unique mais avec plein de projets à l’intérieur. Google par exemple travaille en monorepo.

Vous savez alors qu’on ne peut pas cloner tous les projets de Google dsur son ordinateur, telle la volumétrie est grande. Il faut cloner ce dont on a besoin.

Comment fait on alors pour ne cloner ue partiellement un repository?

Vient la commande sparse checkout

Imaginez que vous êtes sur votre ordinateur et que vous vouliez cloner partiellement un repository, en ligne de commande voilà ce que l’on va faire

# Initialiser un nouveau dépôt Git
git clone --filter=blob:none --no-checkout https://github.com/vous/ledepot.git
cd ledepot

# Activer sparse-checkout
git sparse-checkout init --cone

# Spécifier le dossier à récupérer
git sparse-checkout set htdocs/todolist.com

# Récupérer les fichiers
git checkout main

Cette méthode vous permet néanmoins d’avoir l’historique des commits complet. Vous n’avez peut être pas besoin des commit précédents (on a rarement besoin), donc il est plus léger de n’avoir que le dernier commit.

Clonage encore plus light

git clone --depth 1 --filter=blob:none --sparse https://github.com/vous/ledepot.git
cd ledepot
git sparse-checkout set htdocs/todolist.com

  • --depth 1 = télécharge uniquement le dernier commit (pas d’historique)
  • Parfait si vous voulez juste travailler sur la version actuelle

Les blob sont des fichiers binaire, on a avec commande éviter des les télécharger.

Cette méthode télécharge uniquement les fichiers du dossier spécifié au lieu de tout le repository. L’option --cone optimise les performances pour les grandes arborescences, et --filter=blob:none évite de télécharger les blobs inutiles initialement.

Si vous voulez modifier le sparse-checkout plus tard pour ajouter d’autres dossiers :

git sparse-checkout add htdocs/autre-dossier

Un mot à propos du flag –cone

--cone fait référence au mode « cone » du sparse-checkout, qui est une approche optimisée pour gérer les patterns de fichiers.

Sans --cone (mode pattern traditionnel) :

  • Vous pouvez spécifier des patterns complexes comme *.js, src/**/test
  • Plus flexible mais plus lent sur les gros repositories
  • Git doit vérifier chaque fichier individuellement

Avec --cone (mode cone) :

  • Vous ne pouvez spécifier que des dossiers entiers (pas de wildcards)
  • Beaucoup plus rapide et performant
  • Git optimise en travaillant par « cônes » de répertoires
  • C’est le mode recommandé par défaut depuis Git 2.26

Donc le flag –cone permet d’être plus rapide quand vous avez un vraiment gros repository

reconversion informatique

Reconversion en informatique

Qui suis je?

Je suis le fondateur du centre de formation Yvonh.com, un centre de formation certifié Qualiopi, dédié à l’apprentissage du développement web. Mon objectif est d’aider les personnes qui souhaitent apprendre à créer des sites web, développer des applications et comprendre les technologies qui font fonctionner internet aujourd’hui.

Les points différenciant de mon organisme de formation

Le développement web est devenu une compétence très recherchée. Aujourd’hui, presque toutes les entreprises ont besoin d’une présence en ligne, que ce soit à travers un site internet, une application web ou des services numériques. C’est pour répondre à cette demande croissante que j’ai créé Yvonh.com : un centre de formation qui permet d’apprendre le développement web de manière claire, progressive et surtout pratique.

Dans mes formations, je commence toujours par les bases fondamentales du web. J’accompagne les apprenants dans la découverte du HTML, qui permet de structurer les pages web, et du CSS, qui sert à mettre en forme les sites internet. Ces deux technologies sont indispensables pour comprendre comment un site web est construit. L’accompagnement est personnalisé.

L’un des points les plus importants dans ma pédagogie est la pratique. Je privilégie toujours l’apprentissage par projets. Les apprenants réalisent des exercices concrets et développent progressivement leurs propres projets web. Cette approche leur permet non seulement de mieux comprendre les concepts, mais aussi de développer leur autonomie et leur capacité à résoudre des problèmes. Vous disposez d’un Discord pour l’aide.

Je transfère mon savoir faire de 12 ans dans le code

Au-delà des langages et des technologies, je mets également l’accent sur les bonnes pratiques du développement web. J’enseigne comment organiser son code, utiliser des outils professionnels, gérer les versions d’un projet ou encore sécuriser une application web. Ces compétences sont essentielles pour travailler efficacement dans un environnement professionnel.

Avec Yvonh.com, mon ambition est de rendre le développement web accessible à tous : étudiants, professionnels en reconversion ou simples passionnés de technologie. Je souhaite transmettre non seulement des compétences techniques, mais aussi une manière de penser et de construire des projets numériques.

Et l’IA?

L’IA n’est pas oublié, je vous apprendrai tirer profit de l’IA dans votre apprentissage. Dans le monde de l’entreprise également vous serez demandé à utiliser l’IA, les nouveaux outils comme Antigravity ou Claude Code (le plus puissant au moment où j’écris ces lignes)

Certificat Qualiopi :

La Certification Qualiopi a été délivrée au titre de la catégorie d’action suivante : Actions de formation

Certaines formations proposées peuvent faire l’objet d’une prise en charge par des dispositifs de financement (CPF, OPCO, France Travail…), selon les conditions définies par les organismes financeurs.
Pour plus d’informations, veuillez nous contacter. (Mettre un lien vers la page Contact).

Enregistré sous le numéro de déclaration d’activité  76310966431 auprès de la DREETS Occitanie (cet enregistrement ne vaut pas agrément de l’Etat)

Siret : 510 776 529 00040 , l’avis de situation est téléchargeable sur le site de l’Insee.

Certificat Qualiopi :

Formation au vibe coding développez avec l’IA

Pourquoi apprendre le vibecoding?

Le vibecoding, qui consiste à parler à une IA pour générer du code fonctionnel, a transformé l’équilibre des forces entre les sachants et les non sachant coder. En effet le développement d’application était réservé à une minorité de personnes qui ont mis des années à parfaire leurs connaissances en code. L’arrivée des LLM (modèle larges de données) a permis à tout un chacun de développer des application web sans vraiment avoir passsé des années à apprendre et pratiquer le code.

Est ce à dire qu’il n’est plus besoin de savoir lire du code basique ou d’écrire du code basique pour développer une application? Rien n’et moins sûr, en effet, ce que vous voyez dans les réseaux sociaux montre des petites applications simples développée à partir de zéro.

Le jeu se complique lorsque l’application grandit. Or vous ne pouvez pas ignorer votre oeuvre, il faut au moins en maitriser les grandes lignes. au fur et à msur qu’il grandit, que vous ajoutez des fonctionnalité, il faut pouvoir suivre son évolution, comme un enfant qui grandit il faut savoir l’appréhender sinon il va mal finir !

Public cible :

Porteur de projet à la recherche de prototype fonctionnel, SEO, Marketeur, curieux passionné mais ne sachant pas encore coder, créateur d’entreprise, indépendants. Chef de projet, product Owner,développeur, chef d’entreprise TPE/PME

Objectif de la formation : à l’issue de la formation, vous saurez comment fonctionne un LLM, la génération de code, comment prompter un LLM pour générer une application sécurisée. Vous saurez déployer cette application en ligne. La maintenir et la faire évoluer.

Programme de formation

Comprendre les LLM et le prompting

Maitriser les outils de vibecoding : Lovable pour les interfaces, Antigravity, Cursor, Windsurf,Claude Code

Générer tester et corriger du code

construire un mini projet fonctionnel

intégrer le workflow avec Git et tests

Déployer l’application en ligne

Comment exposer votre site local au réseau local avec Laragon ou Apache

Pour exposer votre site local au réseau local (LAN) avec Laragon ou Apache, le processus est similaire à celui d’autres serveurs locaux comme Node.js. Cependant, il y a quelques ajustements spécifiques à faire selon que vous utilisez le serveur intégré de Laragon ou une installation Apache autonome.

Exposer Laragon au LAN

Laragon est un environnement de développement populaire sous Windows, incluant Apache, MySQL, PHP, etc. Voici comment vous pouvez exposer un site Laragon à votre réseau local.

Étape 1 : Modifier la configuration Apache de Laragon

Par défaut, Laragon est configuré pour n’écouter que sur localhost. Vous devez modifier la configuration d’Apache pour qu’il écoute sur toutes les interfaces (0.0.0.0), permettant ainsi l’accès depuis le réseau local. Ouvrez le fichier de configuration Apache dans Laragon : Allez dans Menu > Apache > httpd.conf. Recherchez la ligne suivante (généralement définie sur localhost)

Listen 127.0.0.1:80
Changez 127.0.0.1 en 0.0.0.0 pour qu'Apache écoute sur toutes les interfaces :

Listen 0.0.0.0:80

Étape 2 : Permettre aux hôtes virtuels de fonctionner sur le LAN

Si vous utilisez des hôtes virtuels avec Laragon (par exemple, monsite.test), vous devez également modifier la configuration des hôtes virtuels :
Ouvrez votre fichier de configuration des hôtes virtuels (Menu > Apache > sites-enabled > 00-default.conf ou httpd-vhosts.conf).
Trouvez la directive VirtualHost, qui ressemble à ceci :

<VirtualHost 127.0.0.1:80>
Changez 127.0.0.1 en 0.0.0.0 :

<VirtualHost 0.0.0.0:80>

Étape 3 : Trouver votre adresse IP locale

Vous devez maintenant trouver l’adresse IP locale de votre machine. Sous Windows, exécutez la commande ipconfig. Sous Linux ou Mac, utilisez ifconfig ou ip a. Une fois que vous avez votre adresse IP (par exemple, 192.168.1.100), les autres appareils du réseau local peuvent accéder à votre site hébergé sur Laragon en allant à l’adresse suivante :

http://192.168.1.100
Si vous utilisez un hôte virtuel comme monsite.test, vous y accéderez avec cette adresse :


http://192.168.1.100

Étape 4 : Configurer le pare-feu (si nécessaire)

Assurez-vous que votre pare-feu ne bloque pas l’accès au port (généralement 80 pour HTTP) :

Sous Windows :
Allez dans Panneau de configuration > Système et sécurité > Pare-feu Windows Defender > Autoriser une application ou une fonctionnalité via le pare-feu Windows Defender.
Ajoutez une règle pour Apache HTTP Server ou autorisez les connexions entrantes sur le port 80.

Étape 5 : Accéder depuis des appareils sur le LAN

Depuis n’importe quel appareil sur le même réseau local, accédez à votre site Laragon local en saisissant l’adresse IP de votre machine dans le navigateur, comme décrit ci-dessus.

Exposer un Apache autonome au LAN

Si vous utilisez une installation Apache autonome, le processus est similaire, mais implique la modification directe des fichiers de configuration Apache.

Étape 1 : Modifier la configuration Apache

Ouvrez votre fichier de configuration Apache. Il est généralement situé à :

Sous Windows (XAMPP/WAMP) : C:\xampp\apache\conf\httpd.conf ou C:\wamp\bin\apache\apache2.x.x\conf\httpd.conf
Sous Linux/Mac : /etc/httpd/httpd.conf ou /etc/apache2/apache2.conf
Recherchez la directive Listen, qui ressemble à ceci :

Listen 127.0.0.1:80
Modifiez-la pour écouter sur toutes les interfaces (0.0.0.0) :


Listen 0.0.0.0:80

Étape 2 : Mettre à jour les hôtes virtuels (si applicable)

Si vous utilisez des hôtes virtuels, ouvrez votre fichier d’hôtes virtuels (par exemple, httpd-vhosts.conf) et changez l’hôte virtuel de 127.0.0.1 à 0.0.0.0 :

<VirtualHost 0.0.0.0:80>
  DocumentRoot "C:/xampp/htdocs/monsite"
  ServerName monsite.local
</VirtualHost>

Cela garantit que les hôtes virtuels sont accessibles sur le réseau local.

Étape 3 : Configuration du pare-feu

Assurez-vous que votre pare-feu permet les connexions entrantes sur le port 80 :

Sous Linux (avec ufw) :

sudo ufw allow 80/tcp
Sous Windows : Suivez les étapes mentionnées plus haut pour autoriser le trafic via le port 80.

Étape 4 : Accéder depuis des appareils sur le LAN

Une fois votre serveur Apache configuré pour écouter sur 0.0.0.0, vous pouvez y accéder depuis d’autres appareils sur le LAN en utilisant l’adresse IP locale de votre machine :

http://192.168.1.x
Si vous avez configuré un hôte virtuel, cela fonctionnera de manière similaire.

En conclusion

  • Pour Laragon : Modifiez Listen 127.0.0.1:80 en Listen 0.0.0.0:80 dans le fichier httpd.conf ainsi que dans les fichiers des hôtes virtuels si nécessaire.
  • Pour Apache autonome : Suivez un processus similaire en modifiant la configuration pour qu’Apache écoute sur 0.0.0.0 et mettez à jour les hôtes virtuels.

Après avoir configuré votre serveur, accédez au site via l’adresse IP locale de votre machine (192.168.x.x) depuis d’autres appareils sur le réseau local.
De cette manière, vous pouvez exposer votre environnement de développement local, que ce soit avec Laragon ou Apache, à votre réseau local (LAN)

Connaitre la différence entre NodeList et HTMLCollection

Lorsque vous sélectionnez plusieurs éléments HTML en se basant sur le nom de la balise ou d’un nom de classe, vous recevez plusieurs éléments. Par exemple on a le code ci-dessous :

<ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
</ul>

let liste = document.querySelectorAll('li')

let liste2 = document.getElementsByTagName('li')

A priori liste 1 et liste2 sont pareil, mais si nous loggons chaque variable on a HTMLCollection pour liste2 et NodeList pour liste. Quelle est la différence entre les deux?

NodeList est plus général que HTMLCollection

En effet un node peut désigner un élément HTML certes, mais peut aussi désigner un commentaire HTML qui est on le sait pas un élément HTML. Le texte à l’intérieur d’un élément HTML est un node, mais pas un élément HTML

<div>
Je suis un texte
    <p>Une phrase</p>
    <p>une seconde phrase</p>
</div>

Dans l’exemple ci-dessus « Je suis un texte » n’est pas un élément HTML mais un Node.

Un HTMLCollection ou un Nodelist n’est pas un tableau mais un Iterable !

Vous connaissez sans doute la méthode map relative aux tableaux, si vous essayer d’appliquer map à une Nodelist ou un HTMLCollection, vous aurez une erreur.

liste.map( e=> e ) // erreur
for(let i=0;i< liste.length;i++){
    console.log(liste[i])
}

ça marchera aussi pour liste2.

Les index et la propriété length marchent mais c’est tout, ce n’est donc pas un tableau ! On peut boucler dessu, donc c’est un Itérable. Un tableau est un Iterable mais avec plus de propriété.

On peut cependant transformer ces Iterable en tableau avec la fonction Array.from.

HTMLCollection reflète le changement dans le DOM

Soit le HTML:

<p>Paragraph Un</p>
<p>Paragraph Deux</p>
<p>Paragraph Trois</p>


// returns an HTMLCollection
const paragraphs = document.getElementsByTagName('p')

console.log("BEFORE UPDATE: ", paragraphs)   // 3 paragraphes
  
const newParagraph = document.createElement('p')
document.body.appendChild(newParagraph)

console.log("AFTER UPDATE: ", paragraphs) // 4 paragraphes

NodeList quant à lui est généralement statique

Soit le HTML:

<p>Paragraph Un</p>
<p>Paragraph Deux</p>
<p>Paragraph Trois</p>


// returns an HTMLCollection
const paragraphs = document.querySelectorAll('p')

console.log("BEFORE UPDATE: ", paragraphs)   // 3 paragraphes
  
const newParagraph = document.createElement('p')
document.body.appendChild(newParagraph)

console.log("AFTER UPDATE: ", paragraphs) // 3 paragraphes

Exception avec getElementsByName

<body>
    <p name="toto">Paragraph Un</p>
    <p name="toto">Paragraph Deux</p>
    <p name="toto">Paragraph Trois</p>
</body>

    // returns an HTMLCollection
    const paragraphs = document.getElementsByName('toto')

    console.log("BEFORE UPDATE: ", paragraphs)   // 3 paragraphes

    const newParagraph = document.createElement('p')
    newParagraph.setAttribute('name', 'toto')
    document.body.appendChild(newParagraph)

    console.log("AFTER UPDATE: ", paragraphs) // 4 paragraphes

Mettre en place un serveur MCP en Python 3.13

Qu’est ce qu’un serveur MCP (Model Context Protocole?)

Un serveur MCP est un webservice qui fournit de la data, cette data va servir de contexte pour l’IA générative. A la différence du RAG (Retrieval Augmented Generation), il n’est pas besoin de base vectorielle pour stocker des informations.

Le workflow est le suivant, le client IA (Claude Desktop) va requêter le serveur MCP pour retirer les informations dont il a besoin pour envoyer au LLM.(chatGPT par exemple).

Cette technologie inventée par Anthropic (à l’origine de Claude) est devenue très populaire. Je pense que c’est dû au fait qu’il n’est besoin que de connaissances existantes (Webservice) par les développeurs pour pouvoir taquiner les IA afin d’éviter les hallucination.

Mais cela va au delà des hallucination, un serveur MCP est un webservice à destination non du programmeur mais de l’IA générative. Je pense que c’est une bonne comparaison, l’IA (ou plutôt l’agent IA) va pouvoir décider en autonomie de requêter un serveur MCP pour chercher les information dont il a besoin?

Donc un serveur MCP est un webservice pour agent IA, au même titre qu’un webservice est un …. webservice pour un programmeur. Dans notre cas on va utiliser Claude Desktop comme « agent IA » ou plutôt comme Client IA.

Mise en place du serveur MCP dans Pycharm

Création de mon environnement virtuel

On crée le répertoire MCP puis on fait la commande :

vous avez un répertoire appelé MCP rentrez dedans

$ python -m venv .venv

# activez l'environnement virtuel (sous powershell)
$ .\.venv\Scripts\Activate.ps1

#c'est activé (normalement) vous avez accès à la commande pip
$ pip --version

Installation de uv

uv est un gestionnaire de paquet nouvelle génération écrite en RUST.

pip install uv

puis avec uv on installe mcp 

uv add "mcp[cli]"

A partir de maintenant on va utiliser uv pour installer notre projet

uv init 

# la commande tree -L 2 nous donne la structure suivante
.
|-- README.md
|-- main.py
|-- .venv
|   |-- Include
|   |-- Lib
|   |-- Scripts
|   `-- pyvenv.cfg
|-- pyproject.toml
`-- uv.lock

Pour install tree dans Windows Gitbash, il faut télécharger l’EXE et le mettre dans C:\Program Files\Git\usr\bin, un tuto est accessible sur ce lien.

Le soucis c’est que mcp s’attends à ce que le dossier environnement virtuel soit nommé .venv, si vous faites la commande:

$ uv run mcp
warning: `VIRTUAL_ENV=mcpvenv` does not match the project environment path `.venv` and will be ignored; use `--active` to target the active environment instead


Désativez l'environnement avec 
$ deactivate
si un .venv est présent, exécutez le venv  de ce répertoire.

Exemple rapide de serveur MCP:

#server.py

from mcp.server.fastmcp import FastMCP

# Create an MCP server
mcp = FastMCP("Demo")


# Add an addition tool
@mcp.tool()
def add(a: int, b: int) -> int:
    """Add two numbers"""
    return a + b


# Add a dynamic greeting resource
@mcp.resource("greeting://{name}")
def get_greeting(name: str) -> str:
    """Get a personalized greeting"""
    return f"Hello, {name}!"


# Add a prompt
@mcp.prompt()
def greet_user(name: str, style: str = "friendly") -> str:
    """Generate a greeting prompt"""
    styles = {
        "friendly": "Please write a warm, friendly greeting",
        "formal": "Please write a formal, professional greeting",
        "casual": "Please write a casual, relaxed greeting",
    }

    return f"{styles.get(style, styles['friendly'])} for someone named {name}."

Pour le faire tourner

mcp dev server.py

#il vous sera demandé d'installer le paquet @modelcontextprotocol/inspector@0.17.0

Source: https://composio.dev/blog/mcp-server-step-by-step-guide-to-building-from-scrtch

Connaitre l’objet global Javascript Object et ses propriétés

L’objet de tous les objets

En Javascript vous avez dû sans doute croiser Object, c’est un peu l’origine de tous les objets e, Javascript.

const obj = new Object()
console.log(obj)
{}
[[Prototype]]:Object

Si on creuse un peu plus l’objet :

Vous voyez toutes les propriétés nombreuse de cet object même primitif. Tous les objet en Javascript héritent de cet objet (Date, String etc).

Méthodes statique d Object

Les méthodes static peuvent peuvent être invoquées depuis Object sans instanciation

Object.keys({a:1,b:2})  // ['a','b']

Object.assign(target,…sources)

Copie des prpriétés d’un objet à un autre

const user = { name: "John" };
const details = { age: 30, country: "France" };

const merged = Object.assign({}, user, details);
console.log(merged);
// { name: 'John', age: 30, country: 'France' }

Object.create(proto,[propertiesObject])

Crée un nouvel objet avec en paramètre un prototype

const person = { greet() { console.log("Hello"); } };
const user = Object.create(person);
user.greet(); // Hello

Object.keys(obj)

Retourne un tableau de clé de l’objet

Object.keys({a:1, b:2}); // ['a', 'b']

Object.values(obj)

Retourne un tableau de valeurs de l’objet

Object.values({a:1, b:2}); // [1, 2]

Object.entries(obj)

Retourne un tableau de clé / valeur

Object.entries({a:1, b:2});
// [['a',1], ['b',2]]
// avec destructuring
for (const [key, val] of Object.entries({a:1, b:2})) {
  console.log(key, val);
}

Object.freeze(obj)

Après un freeze, on ne peut plus ajouter,effacer ou modifier une propriété

const config = { debug: true };
Object.freeze(config);
config.debug = false; // inopérant

Intéressant pour un objet configuration que vous voulez rendre immuable

Object.seal(obj)

Empêche l’ajout ou suppression de propriété, mais autorise la modification

const user = { name: "John" };
Object.seal(user);
user.name = "Jane";  // Ok
user.age = 30;       //  ignoré

Object.getOwnPropertyNames(obj)

Retourne tous les noms des propriétés (énumérable et non énumérables)

const obj = Object.create({}, { hidden: { value: 42, enumerable: false } });
console.log(Object.getOwnPropertyNames(obj)); // ['hidden']

Ne donne que les nom de niveau 1

Object.getOwnPropertyDescriptors(obj)

Retourne les informations de chaque propriété

const obj = { a: 1 };
console.log(Object.getOwnPropertyDescriptors(obj));

Utile pour le clonage incluant getter et setters

Object.getPrototypeOf(obj)

Retourne le prototype (en gros le parent, ou parent du parent etc) d’un objet donné

const arr = [];
console.log(Object.getPrototypeOf(arr) === Array.prototype); // true

Object.setPrototypeOf(obj,prototype)

Sette le prototype à un objet

const animal = { speak() { console.log("hi"); } };
const dog = {};
Object.setPrototypeOf(dog, animal);
dog.speak(); // hi

C’est comme ça qu’on fait de l’orienté objet en Javascript et pas avec des classes, qui sont plus familières à la plupart des programmeurs.

Object.hasOwn(obj,prop)

(Ecmascript 2022 ) alternative plus safe pour hasOwnProperty

const user = { name: "Alice" };
console.log(Object.hasOwn(user, "name")); // true

Object.fromEntries(entries)

Convertit un tableau de [key,value] en un objet

const entries = [["name", "Bob"], ["age", 25]];
const user = Object.fromEntries(entries);
console.log(user); // { name: 'Bob', age: 25 }

Object.is(value1,value2)

Dit si deux objets sont les même (comme ===, mais dans le cas de NaN est plus safe

Object.is(NaN, NaN); // true
Object.is(0, -0);    // false

Object.defineProperty(obj,prop,descriptor)

Définit ou modifie une propriété avec descripteur.

const user = {};
Object.defineProperty(user, "name", {
  value: "Alice",
  writable: false,
  enumerable: true
});

Object.defineProperties(obj,props)

Définit de multiple propriétés

Méthodes d’instance

Les méthodes d’instance ne sont invocable que sur un objet instantié.

hasOwnProperty(prop)

Vérifie si la propriété existe directement dans l’objet.

const user = { name: "Alice" };
console.log(user.hasOwnProperty("name")); // true

isPrototypeOf(obj)

Vérifie si l’objet existe dans la chaine d eprototype d’un autre objet

function Animal() {}
function Dog() {}

Dog.prototype = Object.create(Animal.prototype);

const d = new Dog();
console.log(Animal.prototype.isPrototypeOf(d)); // true

propertyIsEnumerable(prop)

regarde si la propriété est énumérable

const obj = { x: 1 };
console.log(obj.propertyIsEnumerable("x")); // true

toString()

Retourne une représentation string de l’objet

console.log({}.toString()); // [object Object]

Utiliser Object.prototype.toString.call(value) pour détecter le type:

Object.prototype.toString.call([]);      // [object Array]
Object.prototype.toString.call(123);     // [object Number]

valueOf()

retourne la valeur primitive d’un objet (dans un contexte arithmétique)

const obj = { valueOf: () => 42 };
console.log(obj + 8); // 50

Applications pratiques de quelques méthodes

Clone an Object (shallow)

const copy = Object.assign({}, original);

Deep Clone (simple version)

const deepCopy = JSON.parse(JSON.stringify(original));

Merge Configurations

const defaultConfig = { debug: false, port: 8080 };
const userConfig = { debug: true };
const config = Object.assign({}, defaultConfig, userConfig);

Create Object from Key/Value Pairs

const pairs = [["id", 1], ["name", "John"]];
const obj = Object.fromEntries(pairs);

Immutable Constant Object

const roles = Object.freeze({ ADMIN: "admin", USER: "user" });

Installer et utiliser l’ORM Prisma avec NodeJS en Typescript

La base de données est MySQL

Installation de Prisma

npm init -y

npm install express @prisma/client

npx tsc --init   // va créer le fichier tsconfig.json

npx prisma --init  //


Fetching latest updates for this subcommand...

✔ Your Prisma schema was created at prisma/schema.prisma
  You can now open it in your favorite editor.
warn You already have a .gitignore file. Don't forget to add `.env` in it to not commit any private information.

Next steps:
1. Run prisma dev to start a local Prisma Postgres server.
2. Define models in the schema.prisma file.
3. Run prisma migrate dev to migrate your local Prisma Postgres database.
4. Tip: Explore how you can extend the ORM with scalable connection pooling, global caching, and a managed serverless Postgres database. Read: https://pris.ly/cli/beyond-orm

More information in our documentation:
https://pris.ly/d/getting-started

A ce stade un fichier .env et un répertoire prisma a été créé. Le .env contient la chaine de connexion, par défaut c’est pour du postgres, mais on va changer en mysql. Il faut créer la base prisma_demo_ts avant de faire les opérations de migration.

DATABASE_URL="mysql://root:password@localhost:3306/prisma_demo_ts"

Maintenant on va constituer un fichier de modèle pour créer les tables dans

Création des fichiers de migration et migration dans la même commande !

npx prisma migrate dev --name add-user-contact-table 


Environment variables loaded from .env
Prisma schema loaded from prisma\schema.prisma
Datasource "db": MySQL database "prisma_demo_ts" at "localhost:3306"

Applying migration `20251018092422_add_user_contact_table`

The following migration(s) have been created and applied from new schema changes:

prisma\migrations/
  └─ 20251018092422_add_user_contact_table/
    └─ migration.sql

Your database is now in sync with your schema.

Le script index.ts

import express from 'express'
import { PrismaClient } from '@prisma/client'

const app = express()
const prisma = new PrismaClient()

app.use(express.json())

// GET all users
app.get('/users', async (req, res) => {
    const users = await prisma.user.findMany()
    res.json(users)
})

// POST new user
app.post('/users', async (req, res) => {
    const { name, email } = req.body
    const user = await prisma.user.create({ data: { name, email } })
    res.json(user)
})

app.listen(3000, () => console.log('🚀 Serveur démarré sur http://localhost:3000'))

Si vous avez une erreur de type commonJS blabla, c’est que la syntaxe import n’est pas commonJs. Pour voir la différence entre commonJS et ESM voir cet article.

Si vous avez un message d’avertissement relatif à verbatimModuleSyntax, commentez la ligne dans tsconfig.json.

Pour lancer la compilation du fichier index.ts, désignez le répertoire de sortie des fichier JS, c’est dans tsconfig.json, la clé outDir doit être décommentée :  « outDir »: « ./dist »,

npx tsc

ensuite faites
node node dist/index.js

Installer et utiliser l’ORM Prisma avec NodeJS

Installations de Prisma

// Créez un répertoire pour votre projet Prisma et entrez dedans

npm init -y
npm i prisma --save-dev

Créez la base de données

CREATE DATABASE prisma_demo;

// Dans le fichier .env mettre cette chaine de connexion

DATABASE_URL="mysql://root:password@localhost:3306/prisma_demo"

npm install  @prisma/client   

npx prisma init


✔ Your Prisma schema was created at prisma/schema.prisma
  You can now open it in your favorite editor.
Next steps:
1. Run prisma dev to start a local Prisma Postgres server.
2. Define models in the schema.prisma file.
3. Run prisma migrate dev to migrate your local Prisma Postgres database.
4. Tip: Explore how you can extend the ORM with scalable connection pooling, global caching, and a managed serverless Postgres database. Read: https://pris.ly/cli/beyond-orm

Un répertoire prisma a été créé dans le projet. Dans le fichier schema.prisma mettez le code ci-dessous :

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  createdAt DateTime @default(now())
}

Ce modèle crée une table User avec :

  • id auto-incrémenté
  • email unique
  • name optionnel
  • createdAt avec la date de création

Faire la migration

npx prisma migrate dev --name init

Cela crée :

  • table User
  • Génère le client Prisma dans node_modules/.prisma/client
  • un fichier de migration

Execution du script d’exploitation de la base de donnée :

import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

async function main() {
    // Créer un utilisateur
    const newUser = await prisma.user.create({
        data: {
            email: 'alice@example.com',
            name: 'Alice',
        },
    })
    console.log('Utilisateur créé :', newUser)

    // Lire tous les utilisateurs
    const users = await prisma.user.findMany()
    console.log('Liste des utilisateurs :', users)
}

main()
    .catch((e) => console.error(e))
    .finally(async () => {
        await prisma.$disconnect()
    })

Ce script une fois exécuté va créer un utilisateur dans la base de données.

Visualiser la base de données avec Studio

npx prisma studio
Vue de Prisma Studio sur http://localhost:5555/

Syntaxe de l’ORM Prisma

// Trouver un user par email
const user = await prisma.user.findUnique({
  where: { email: 'alice@example.com' },
})

    // Mettre à jour un user
    const updatedRecord = await prisma.user.update({
        where: { id: 1 },
        data: { email: 'yvon.huynh@hotmail.com' },
    })
    console.log('Updated utilisateur :', updatedRecord )

// Supprimer un user
    // const deletedRecord = await prisma.user.delete({
    //     where: { email: 'yvon.huynh@gmail.com' },
    // })

    // console.log('Effacement utilisateur :', deletedRecord)

Rafraichir le navigateur avec nodeJS

Récemment pour les besoin de développement d’une extension Chrome, j’ai cherché un moyen de rafraichir l’extension car le process de rafraichissement était un peu fastidieux. Il fallait aller dans le menu des extensions (chrome://extensions) et rafraichir en cliquant sur un bouton. Je voulais automatiser cette procédure comme le fait le plugin Liveserver.

Installation du projet NodeJS

npm init -y
npm install --save-dev nodemon chokidar

#pour setup avancé (optionnel)
npm install --save-dev chrome-extension-cli

Création du script de reload

import chokidar from "chokidar";
import { exec } from "child_process";

const EXTENSION_PATH = "./dist"; // or your extension folder
const CHROME_PROFILE = "--profile-directory=Default"; // optional

// watch all files in your extension folder
chokidar.watch(EXTENSION_PATH, { ignoreInitial: true }).on("all", (event, path) => {
  console.log(`File ${path} changed — reloading Chrome extension...`);

  // command to reload the extension via Chrome debugger API
  exec(`osascript -e 'tell application "Google Chrome" to reload active tab'`);

  // alternative: use chrome-cli on macOS (brew install chrome-cli)
  // exec(`chrome-cli reload`);
});

Mise en place de nodemon

Dans package.json ajouter le script :

"scripts": {
  "dev": "nodemon --watch src --exec \"node reload-extension.js\""
}

et faites npm run dev

Dorénavant, il vous suffit de fermer et rouvrir pour réactiver l’extension si c’est une extension pour les devtools.

Installer votre propre IA avec Ollama sur votre ordinateur (Windows)

Installation de Ollama

Pour commencer il faut aller sur le site Ollama.com (à ne pas confondre avec Llama.com). Le lien Github, pour consulter les sources et la documentation sur les modèles

Téléchargez et installez. Une fois installé, vous aurez une opup comme ci-dessous :

Vous allez vous perdre dans la jungle des modèles. Vous connaissez sans doute aussi de nom Hugging Face, c’est un repository de modèle LLM open source. Une fois que vous avez installé Ollama, ouvrez un terminal et tapez la commande $ ollama, une liste de commande sera affichée. Allez sur le Github, vous verrz un tableau de modèles LLM, différentes capacité et taille, ça va de 1 Go à 404 Go ! En fonction de votre machine téléchargez celui que vous pouvez faire fonctionner !

La colonne parameters indique combien de paramètres est considéré par le modèle, comptez environs 1 giga de RAM pour 1 Giga (Billion milliards) de paramètres. Mon ordinateur fait 32 Giga de RAM, je peux aller avec le modèle à 30B paramètres.

Les commandes pour installer les LLM

ollama run llama2


Pour connaitres les différentes commande au sein du chat
/?
>>> /?
Available Commands:
  /set            Set session variables
  /show           Show model information
  /load <model>   Load a session or model
  /save <model>   Save your current session
  /clear          Clear session context
  /bye            Exit
  /?, /help       Help for a command
  /? shortcuts    Help for keyboard shortcuts

Use """ to begin a multi-line message.


Pour quitter :
/bye

Pour lister les modèles :
ollama list

.

Ecran du téléchargement du modèle, à la vue de la sortie terminal, ça ressemble à une image Docker

Test de llama2

La première chose que je constate, est que le modèle est vraiment limité, surtout quand vous avez l’habitude de vous amuser avec chatGPT ou Claude sur le web, donc sur les modèles les plus sophistiqués.

Par exemple je lui demande de me donner la recette du roesti, il me répond qu’il ne sait pas

Quand je lui demande la recette de la tarte tatin, il me liste en anglais (pas vraiment multilingue, ok pour la taille du modèle cependant !). Par contre comme il est hébergé en local il est très rapide. Je lui demande pourquoi il me répond qu’il n’a pas réussi à détecter (alors que je lui ai demandé en français), mais il me propose de donner la version française ce qu’il fit très bien.

Nous allons télécharger un autre modèle

Cette fois ci nous allons télécharger le modèle Mistral, équivalent en taille. Sur le repository, vous trouverez la commande dans le tableau

ollama run mistral

Après le téléchargement, il démarre le prompt automatiquement. Quand vous ferez bye, vous pourrez démarrer entre les deux modèles (Mistral ou llma2 en ligne de commande)
Pour effacer un modèle, vous avez la comamnde remove, mais pensez à faire ollama pour afficher les différentes commandes.

Et maintenant explorons l’API !

Eh oui c’est là que cela devient très intéressant car nous allons pouvoir exploiter un LLM depuis un programme pour automatiser nos tâche (notre GRAAL)

Ollama expose une API sur le localhost. Dans la barre des tâches, vous pouvez voir une image de Llama, cela veut dire que l’application Desktop est lancée, et donc que l’API est active. Mais si le desktop n’est pas llancé, vous pouvez toujours lancer le serveur en ligne de commande

ollama serve

En parlant de l’icone, vous pouvez accéder aux paramètres

Vous remarquez (j’espère) le slider pour définir la « mémoire » du LLM (la taille du contexte), ceci est utile pour se souvenir d’une conversation !

Vous pouvez exposer le LLM au réseau local (très intéressant !)

Installer le package Python ollama

Très simplement on va faire un script pour requêter Ollama par API avec pip install ollama. Si vous installez le paquet après avoir démarré l’environnement virtuel, il sera en local, si vous le faites hors environnement virtuels, il sera dans le système. Pour connaitre où est installé un package

#affiche les emplacements pouvant héberger les paquets
python -m site

# cherche un paquet en particuliers
python -m pip show <paquet>

#lister les paquets de l'environnement virtuel en cours
pip list
import ollama
client = ollama.Client()

model = "llama2"
prompt = "How to set up a venv?"

response = client.generate(model, prompt)
print(f"Response from {model}:")
print(response.response)

Si vous préférez que la réponse arrive morceau par morceau voici le script (il suffit de passer stream= True)

import ollama

client = ollama.Client()

model = "llama2"
prompt = "How to set up a venv?"

# Use the stream=True option
stream = client.generate(model=model, prompt=prompt, stream=True)

print(f"Response from {model}:\n")

for chunk in stream:
    # Each chunk is a small piece of the model's output
    print(chunk['response'], end='', flush=True)

print()  # final newline

Ok là c’est mieux mais on peut wrapper les phrases (retour à la ligne quand ça déborde)

Avec retour à la ligne sans streaming

import ollama
import textwrap
import shutil

client = ollama.Client()
model = "llama2"
prompt = "How to set up a venv?"

response = client.generate(model=model, prompt=prompt)
terminal_width = shutil.get_terminal_size().columns

print(f"Response from {model}:\n")
print(textwrap.fill(response['response'], width=terminal_width))

Avec retour à la ligne avec streaming

import ollama
import shutil
import sys

client = ollama.Client()
model = "llama2"
prompt = "How to set up a venv?"

stream = client.generate(model=model, prompt=prompt, stream=True)

print(f"Response from {model}:\n")

terminal_width = shutil.get_terminal_size().columns
current_line = ""

for chunk in stream:
    text = chunk["response"]
    for char in text:
        current_line += char
        if len(current_line) >= terminal_width - 1:
            print(current_line)
            current_line = ""
# print remaining text
if current_line:
    print(current_line)

Personnalisation du modèle

Avec un fichier de configuration, on peut agir sur la façon dont les réponse vont être apportées, il y a de nombreux paramètres qui font qu’un LLM va se comporer d’une certianes façon. Vous avez peut être remarqué que ChatGPT était plus jovial que Claude qui est plus sérieux. Perso j’aim bien cette touche.

Mettez Le fichier de personnalisation Modefile dans un répertoire et invoquez une commande sur ce fichier dans ce répertoire.

$ ollama create formabot -f ./Modelfile

# ici j'ai invoqué un modèle non présent ça met du temps à télécharger le modèle

On va lancer avec la commande run

$ ollama run formabot

>>> qui es tu?



# pour effacer le modèle
$ ollama rm formabot

Conclusion :

Dans sa forme, l’outil ressemble beaucoup à Docker (les commandes, les modèles). Voilà pour une première introduction de Ollama. Pourquoi est ce important d’avoir un LLM en local? Si vous voulez rester privé, avoir des temps de réponse rapide, et ne pas vous ruiner en crédit token, alors n’hésitez pas à utiliser un LLM open source, il y en a d’autres allez voir du côté de Deepseek et de HuggingFace.

Mettre en place une authentification JWT avec Node/express Typescript

Mettons nous dans le contexte d’une application avec front et back découplé. NodeJS/Express est le back avec une base de données, et le front par exemple est une application ReactJS. Toute l’application n’est que webservice, et l’on pourra tester l’application rien qu’avec Postman.

Ce dernier prend en charge l’authentification, grâce à la possibilité de stocker le token JWT et de le renvoyer à chaque requête.

Installation des packages NPM

Je suppose que vous avez déjà bootstrappé une application node/Express/Typescript, sinon allez visiter la page du lien. LEs paquet à installer sont jsonwebtoken, bcrypt et comme on travaille en typescript il faudra les fichier type pour l’autocomplétion. Je mets aussi mysql2 au cas où vous ne l’auriez pas installé.

npm install mysql2 bcrypt jsonwebtoken
npm install --save-dev @types/bcrypt @types/jsonwebtoken

Configurer le fichier d’environnement .env

JWT_SECRET=ta_cle_secrete_ici_change_la
JWT_EXPIRES_IN=1h
BCRYPT_SALT_ROUNDS=10

Voici le code de la route (!) qui s’occupe de l’authentification

app.post('/api/auth', async (req: Request, res: Response) => {

    const { email, password } = req.body

    if (!email || !password) {
        res.status(400).json({ message: "Email et mot de passe requis." });
    }
    try {
        const [rows] = await pool.execute(
            "SELECT id, email, password FROM utilisateurs WHERE email = ?", [email]
        )

        const users = rows as any[];

        if (users.length === 0) {
            res.status(401).json({ message: "Utilisateur inconnu." })
        }
        // vérification du mot de passe
        const user = users[0]
        const match = await bcrypt.compare(password, user.password);

        if (!match) {
            res.status(401).json({ message: "Mot de passe incorrect." });
        }

        // sinon on génère un JWT token
        const token = jwt.sign(
            { id: user.id, email: user.email },
            process.env.JWT_SECRET || "secretKey",
            { expiresIn: "24h" }
        );

        res.json({ token });

    } catch (err) {
        console.log(error);
        res.status(500).json({ message: 'Erreur serveur' })
    }

})

Je vous laisse mettre les imports qu’il faut. Aussi il vous faudra avoir la table utilisateurs pour

Requêter avec Postman.

Postman c’est comme un navigateur sans écran, en fait un navigateur est composé d’un écran et d’une antenne émettrice-réceptrice. Installez Postman, et démarrez votre serveur nodeJS, la route de l’authentification est http://localhost:3000/api/auth, faites une requête POST

Vous recevrez le JWT en réponse.

Stocker le JWT dans Postman

Copiez le token (sans les guillements et collez dans le champs avec à gauche la liste déroulante mis sur l’item Bearer Token.

Comprendre le fonctionnement du JWT

A chaque requêtes le serveur, en en-tête Authorization sera envoyé

Authorization: JhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwiZW1haWwiOiJ5dm9uLmh1eW5oQGdtYWlsLmNvbSIsImlhdCI6MTc1ODQ3NzgwOSwiZXhwIjoxNzU4NTY0MjA5fQ.JZ2Z5WRzCdSYiFcbIxknV5B9byJ_Fo9ueNDT9-iH40U

Côté serveur, on crée un middleware qui va :

  • Vérifier la présence du token.
  • Le valider avec jsonwebtoken.verify.
  • Attacher les infos décodées (userId, email, …) à req.user.
  • Laisser passer la requête si le token est valide.

Création du middleware pour authentifier la requête

Nous avons une route protégée, toute requête doit passer par la vérification de la validité du token, non expiré et valide.

app.get('/api/utilisateur/add', requireAuth, (req: AuthRequest, res: Response) => {
    res.json({ message: "accès  réussi" })
})

requireAuth est un middleware, il va filtrer toutes les requêtes qui viennent d’un client, l’avantage du middleware est qu’on n’a pas besoin de faire la validation manuellement dans la route, c’est automatique. Je le mets dans le répertoire src/middleware

fichier middleware/auth.ts

import { Request, Response, NextFunction } from "express";
import jwt from "jsonwebtoken";
import dotenv from "dotenv";

dotenv.config();

// solution JWT Stateless simple
const JWT_SECRET = process.env.JWT_SECRET || "dev_secret";
;

const verifyJwt = <T = object>(token: string): T => {
    return jwt.verify(token, JWT_SECRET) as T
}


export interface AuthRequest extends Request {
    user?: any;
}

export const requireAuth = (req: AuthRequest, res: Response, next: NextFunction) => {
    const authHeader = req.headers.authorization;
    if (!authHeader || !authHeader.startsWith("Bearer ")) {
        res.status(401).json({ message: "Missing or invalid authorization header" });
        return;//le simple fait de mettre return permet à authHeader de ne plus avoir de undefined possible (enlever le return pour montrer l'erreur)
    }

    const token = authHeader.split(" ")[1];

    if (!token) {
        return res.status(401).json({ message: "Token invalide" });
    }
    try {
        const payload = verifyJwt(token);
        req.user = payload;
        next();
    } catch (err) {
        res.status(403).json({ message: "Forbidden, vous êtes authentifié mais n'avez pas accès à la ressource, problème de permission" });
    }
}

Pour aller plus loin : https://www.youtube.com/watch?v=AU5WLAJkaC8

Créer une extension Google Chrome

Je démarre une nouvelle série passionnante sur les extensions Google Chrome !

Les extension Google Chrome (ou sur autre navigateur) sont des script HTML/CSS/ Javascript qui permettent d’interagir avec le DOM du navigateur.

Ce que j’adore c’est l’interaction avec les éléments du DOM, quelques soit le site web, ce qui permet une grande variété d’application, pour être plus productif !

Nous allons démarrer avec une extension très simple : une extension qui permet de mettre en local les prompts qu’on a fait avec chatGPT. Il servira à organiser les prompts, marque ceux qu’on aime, sauvegarder la liste en local, et pourquoi pas en ligne pour permettre de les partager. Il permettra peut être de créer une branche pour faire des variantes de prompt, ce qui est impossible au moment où j’écris ces lignes.

Je tiens à souligner que c’est grâce à un LLM que je progresse plus vite dans l’élaboration d’une extension Google Chrome.

J4ai fait des extension très simple par le passé, avec TamperMonkey, mais je ne me suis jamais aventuré à faire des extension pour de vrai. Mais avec l’arrivée de chatGPT, je peux maintenant m’attaquer à des projets ambitieux. Attention je ne dis pas que je vais demander à chatGPT de tout faire, ici l’utilisatoin de chatGPT que je fais est en mode « discovery ». J’utilise chatGPT pour aller beaucoup plus vite pour ingurgiter de l’information éparpillée sur le net, ce que mettrait des jours à faire, désormais je ne mettrait qu’une demi-journée.

Mais cet article n’est pas de parler de chatGPT, mais des extensions de navigateur Google Chrome.

Quels fichiers faut il pour faire une extension Google Chrome?

A minima il vous faudra le fichier manifest.json qui décrit l’extension, et un fichier javascript de point d’entrée.

manifest.json

{
    "manifest_version": 3,
    "name": "SEO Tools",
    "version": "1.0",
    "description": "Analyse les tags d'une page HTML",
    "permissions": [
        "storage"
    ],
    "content_scripts": [
        {
            "matches": [
                "*://*/*"
            ],
            "js": [
                "content.js"
            ]
        }
    ]
}

Lien : Automating terminals dans VsCode

Créer un utilisateur pour se connecter à Postgresql en bash

Pour les besoins de code PHP, vous devez vous connecter à la base de données avec PDO. Si vous n’avez jusqu’à présent fait une connexion qu’avec l’utilisateur postgres, vous ne pouvez faire ça avec ce dernier dans vos scripts PHP. Nous allons donc créer un utilisateur refschool.

Créer un utilisateur dans postgresql

Vous devez vous connecter avec l’utilisateur postgres, qui agit comme le superadministrateur, car vous ne pouvez pas vous connecter en root à postgresql, en fait c’est l’équivalent du root dans Mysql

sudo -u postgres psql  // attention c'est un u minuscule
// postgresql

CREATE USER refschool WITH LOGIN PASSWORD '123';

GRANT CONNECT ON DATABASE devdb TO refschool;
GRANT USAGE ON SCHEMA public TO refschool;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO refschool;

Notez que public est un SCHEMA et pas une table ! Si vous venez de MySQL comme moi, un SCHEMA est une table. Apparemment la logique de Postgresql est plus répandue du moins chez les grands systèmes de SGBDR comme SQLSERVER et ORACLE.

Création d’une table dans le SCHEMA public

CREATE TABLE public.users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(150) UNIQUE,
    age INTEGER,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

// mention du SCHEMA public, ce dernier étant par défaut dans Postgesql
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(150) UNIQUE,
    age INTEGER,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Autres commandes de Postgresql

//

SELECT current_database();

//

Créer une paire de clés SSH pour s’authentifier sans mot de passe

A quoi ça sert?

Principalement pour vous connecter à un terminal distant sans mot de passe. Le principe étant d’avoir une paire de clé privée/publique. Vosu déposez la clé publique sur le terminal distant, ensuite pour vous y connecter il suffit de ssh à l’adresse IP

Il faut d’abord installer OpenSSH, qui va vous donner les outils

Génération de la paire de clé SSH

ssh-keygen -t rsa -C "your_email@example.com"

Vous serez amené à entrer un mot de passe (optionnel) appelée passphrase.

Uploader la clé publique sur le serveur distant

Il s’agit d’uploader la clé dans le répertoire /home/user. An l’absence de logiciel FTP, vous pouvez faire un SSH. LE plus simple est de se logger en root dans le serveur distant et de copier à la mains les texte des clés publiques. Le plus simple eset de copier coller le texte de la clé publique et le coller dans le fichier authorized_keys du serveur.

Connectez vous en root et éditez le fichier authorized_keys.

ssh-copy-id (source)

// syntaxe générale
ssh-copy-id [-f] [-n] [-i identity file] [-p port] [-o ssh_option] [user@]hostname

ssh-copy-id root@123.123.123.123

Créer un serveur FTP dans un Droplet Digital Ocean

Vous pouvez essayer de faire ce tuto dans un container docker sous Debian

On va d’abord créer un droplet, puis mettre à jour le package et installer Proftpd

apt update
apt install proftpd

On va créer un utilisateur FTP (pas un utiliser avec Shell), on est sous root

useradd yvon -d /home/yvon -m -s /bin/false yvon  // ce user ne possède pas de shell, et son répertoire home est /home/yvon
passwd yvon

//définir le mot de passe

Le but est que l’utilisateur lorsqu’il se connecte avec Filezilla, voit le répertoire /home/yvon, pour y mettre ses fichiers

Puis on va configurer le fichier /etc/proftpd/proftpd.conf

//Afin de contraindre le répertoire à /home/yvon on doit avoir

DefaultRoot ~

// comme yvon n'a pas accès au shell
RequireValidShell off

// spécifier les port passif (intervalle de ports)
PassivePorts 49152 65534

Il faut ouvrir les port 21 et la plage de port dans

ufw allow 21/tcp
ufw allow 49152:65534/tcp

// vérifier le status
ufw status

To                         Action      From
--                         ------      ----
21/tcp                     ALLOW       Anywhere                  
49152:65534/tcp            ALLOW       Anywhere                  
21/tcp (v6)                ALLOW       Anywhere (v6)             
49152:65534/tcp (v6)       ALLOW       Anywhere (v6)   

Faire un script bash pour envoyer des fichiers vers ce serveur FTP

Nous allons d’abord installer le client ftp si ce n’est déjà fait

apt install ftp

// upload d'un fichier (il faut créer le user et le login)
ftp -n 165.22.194.80 <<EOF
user yvon motdepasse
put fichier_local.txt
quit
EOF

On peut faire l’équivalent en script bash c’est plus sympa pour la simplicité

//script bash  syncftp.sh

// on définit les variables
HOST=165.22.194.80
USER=yvon
PASS=123
FILE=date.log

ftp -n $HOST << EOF
user $USER $PASS
put $FILE
quit
EOF

Mettez les lignes dans l’ordre, au début j’ai fait une bêtise j’ai mis le put avant le user !

Voici une version un peu améliorée, va setter le mode de transfert en binary malgré que ce soit du texte, ceci pour ménager les caractères spéciaux, les retour chariots CLRF <-> LF, le ls va lister le fichier pour vérifier que le fichier est bien uploadé !

HOST=165.22.194.80
USER=yvon
PASS=123
FILE=date.log

ftp -n $HOST << EOF
user $USER $PASS
binary
put $FILE
ls
quit
EOF

Cours administrateur de serveur Web Dans un Linux Debian Dans un Docker

Que contient ce cours Linux et administration de serveur web?

A partir d’une image Docker de Debian, on va installer différents éléments tels nginx, php-fpm, postgreSQL et faire tourner un serveur web. Nous allons voir la configuration d’un vhost, expliquer l’architecture client serveur, les DNS?

Puis on va installer un serveur FTP proftpD, nftable, fail2ban pour protéger le serveur web.

Puis on va faire un backup et un certificat autosigné, puis un certificat Let’s Encrypt

Nous allons aussi voir les commandes de base de Linux, les outils de base de Linux,

Sommaire et liens vers les cours

Article bonus sur Nginx

Introduction à nftables le remplaçant de iptables

Installation de nftables

Visualisation des règles (rules)

nft list ruleset


#Si vous avez le message même en étant root :

netlink: Error: cache initialization failed: Operation not permitted 

#il faut lancer le container avec le flag --privileged

docker run --privileged -it <image_docker>

Rien n’apparait, il n’y a pas de règles on va en créer une, d’abord on va créer une table

nft add table inet filter

puis nft list ruleset

table inet filter {
}

C’est vide pour l’instant, on va ajouter une règle pour autoriser les connexion sur le port 22

nft add rule inet filter input tcp dport 22 accept

mais vous risquez d'avoir l'erreur

Error: Could not process rule: No such file or directory
add rule inet filter input tcp dport 22 accept
                     ^^^^^                                                         

Comment ajouter une règle

nftable organise les règle en hiérarchie : table (conteneur de niveau supérieur) > chaine (groupe de règle dans une table) > règle (instruction de filtrage)

Types de tables:

  • ip : ipv4
  • ip6 : ipv6
  • inet : ipv4 + ipv6
  • arp : ARP
  • bridge : bridge
  • netdev : interface réseau

Création table puis chaine puis règle

# Créer la table inet filter (on vient de le faire)
nft add table inet filter

# Créer la chaîne input
nft add chain inet filter input { type filter hook input priority 0 \; policy accept \; }

# Maintenant ajouter votre règle SSH
nft add rule inet filter input tcp dport 22 accept

# lister les règles
nft list ruleset

table inet filter {
        chain input {
                type filter hook input priority filter; policy accept;
                tcp dport 22 accept
        }
}

Commandes de diagnostic

# voir une table spécifique
nft list table inet filter

# Voir le contenu d'une table spécifique
nft list table inet filter

# Voir les chaînes d'une table
nft list chains inet filter

# Voir une chaîne spécifique
nft list chain inet filter input

Générer un certificat autosigné dans Debian et mapper les ports docker et hôte

Cet article fait suite à l’article sur la mise en place d’un serveur web Nginx et php-fpm, suivi de Postgresql.

J’avais déjà fait par le passé un blog sur comment configurer pour Apache un https, il suffisait de pointer vers les certificats SSL dans le fichier vhost

Un certificat autosigné ne sert que lors de la phase de développement. EN effet si vous l’utilisez en production, il y a aura des avertissements. Les certificats permettent une marque de confiance, donc d’une autorité tierce de confiance. De ce fait un certificat autosigné n’aura aucue valeur, puisuqe c’est l’éditeur de site qui le génère. Il faut que ce soit un tiers indépendant qui le fasse, comme par exemple Lets Encrypt. Ce dernier ne génère que des certificat ordinaires, mais utilisable en production. Si vous voulez des certificats plus qualitatifs, comme les certificats EV, il faudra les acheter sur des sites spécialisés.

Nous allons générer un certificat SSL autosigné

mkdir -p /etc/ssl/private/mycert && cd /etc/ssl/private/mycert

ou vous pouvez aller dans le répertoire mycert pour avoir une commande plus courte
cd /etc/ssl/private

mkdir -p /mycert && cd mycert

Le flag -p permet de créer les dossier parents manquant dans la commande mkdir

Génération du certificat (ce sont deux fichiers)

openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout selfsigned.key \
  -out selfsigned.crt \
  -subj "/C=FR/ST=France/L=Paris/O=MonEntreprise/CN=localhost"
  • -x509 : pour un certificat autosigné.
  • -nodes : ne pas chiffrer la clé privée (pas de mot de passe).
  • -days 365 : durée de validité (365 jours).
  • -newkey rsa:2048 : crée une nouvelle clé RSA 2048 bits.
  • -keyout : chemin vers la clé privée.
  • -out : chemin vers le fichier de certificat.
  • -subj : informations du certificat.

Maintenant dans le répertoire mycert vous avez les fichiers selfsigned.crt et selfsigned.key

Modification du fichier de configuration Nginx qu’on a vu dans cet article

# Redirection HTTP vers HTTPS (optionnel mais recommandé)
server {
    listen 80;
    server_name phpsite;
    return 301 https://$host$request_uri;
}

# Bloc HTTPS avec certificat autosigné
server {
    listen 443 ssl;
    server_name phpsite;

    ssl_certificate     /etc/ssl/private/mycert/selfsigned.crt;
    ssl_certificate_key /etc/ssl/private/mycert/selfsigned.key;

    root /var/www/phpsite;
    index index.php index.html;

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_pass 127.0.0.1:9000;
    }
}

Votre site non https va rediriger vers le https.

$ curl https://phpsite/info.php
// néanmoins vous aurez un erreur de type 
curl: (60) SSL certificate problem: self-signed certificate
More details here: https://curl.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

Ceci est dû au fait que ce soit un certificat autocertifié. Pour bypasser cette vérification, il faut rajouter le flag -k à la requête

curl -k https://phpsite/info.php

Redémarrer votre container docker et redémarrer les services

vous avez installé nginx, php-fpm, postgresql, et configuré votre vhost. Le soucis c’est la prochaine que vous faites un docker start <Id container> (après avoir stoppé votre container (ce ne sera pas le cas si vous fiates un exit de votre container), il faut redémarrer les services. Vous pouvez intégrer une commande

De même le vhost que vous avez ajouté dans /etc/hosts a disparu ! ceci est dû à la nature éphémère du container docker, mais heureusement il existe un flag pour ajouter le vhost quand vous faites un run

docker run -d \
  --name mon-site \
  -p 80:80 \
  --add-host=phpsite:127.0.0.1 \
  mon-image

// ce qui donne dans notre cas sauf qu'on est en mode interactif

docker run -it \
  --name phpsite \
  -p 80:80 \
  --add-host=phpsite:127.0.0.1 \
  debian-nginx

// il nous manque la commande bash pour démarrer php-fpm et nginx (remplacez le nom de l'image par votre image)
docker run -it \
  --name phpsite \
  -p 80:80 \
  --add-host=phpsite:127.0.0.1 \
  nginx_postgres_image \
  bash -c "/usr/sbin/php-fpm8.2 -F && nginx -g 'daemon off;'"


// ce qui donne en une seul ligne
docker run -it --name phpsite -p 80:80 --add-host=phpsite:127.0.0.1 nginx_postgres_image  bash -c "/usr/sbin/php-fpm8.2 -F && nginx -g 'daemon off;'"

// en non interactif
docker run -d --name phpsite -p 80:80 -p 443:443 --add-host=phpsite:127.0.0.1 nginx_postgres_image  bash -c "/usr/sbin/php-fpm8.2 -F && nginx -g 'daemon off;'"

Attention avec la syntaxe ci-dessus pour le bash bash -c « /usr/sbin/php-fpm8.2 -F && nginx -g ‘daemon off;' », ça ne va pas marcher car php-fpm étant lancé en foreground, il ne laissera pas la main à nginx pour démarrer, donc il faut remplacer le double && par le simple & , qui va lancer php-fpm en background

docker run -d --name phpsite -p 80:80 -p 443:443 --add-host=phpsite:127.0.0.1 nginx_postgres_image  bash -c "/usr/sbin/php-fpm8.2 -F & nginx -g 'daemon off;'"

On peut améliorer ceci en mettant dans un script bash. Nous pouvons aussi inclure le démarrage de postgresql afin d’avoir une stack complète pour développer.

Accéder au site phpsite depuis l’hôte

La partie intéressante est de se connecter à notre site dans docker depuis notre navigateur ou curl, car on a mappé les ports de docker et de l’hôte. Pour ce faire nous devons ajouter au fichier hosts de l’hôte : 127.0.0.1 phpsite.

//  depuis l'hôte

curl -k https://phpsite/info.php

// depuis votre navigateur vous aurez un avertissement de site non sécurisé, confirmez l'exception.

Prochaine étape nous allons plutôt explorer les volumes docker afin de pouvoir héberger les scripts php sur notre hôte.

Les volumes sous Docker c’est quoi?

C’est un répertoire sur l’hôte, qui sert à herberger des fichiers de code, mais accessible par le container docker, voyez ça comme un mount d’un drive sur un système de fichier Linux. Cela permet de travailler plus facilement et efficacement avec des container Docker. En effet notreIDE préféré est sur notre hôte et pas sur un container docker.

Dans la suite, mon répertoire se trouve dans le drive E: sur Windows dans le répertoire volume_nginx

docker run -d --name phpsite -p 80:80 -p 443:443 -v E:\volume_nginx:/var/www/phpsite --add-host=phpsite:127.0.0.1 nginx_postgres_image  bash -c "/usr/sbin/php-fpm8.2 -F & nginx -g 'daemon off;'"


// avec la ligne -v E:\volume_nginx:/var/www/phpsite on monte le répertoire hôte dans le container docker, du coup ce dernier va être masqué par le répertoire windows

// connextez vous en interactif dans le container en question

$ docker exec -it <idContainer> bash
// allez dans le répertoire /var/www/phpsite, il est vide pour l'instant mais créez un fichier index.php dedans et faites un ls vous verrez ce fichier. Félicitation vous avez réussi à connecter un volume ! 

Avouez que maintenant vous allez être beaucoup plus confortable pour faire un site web en php !

Générer un meilleur certificat SSL avec Let’s Encrypt

Avec une autorité externe les certificats sont de milleurs qualité. Let’s Encrypt délivre des certificats gratuitement et de grade production. MAIS il faut que votre site soit accessible depuis internet, car Let’s Encrypt va accéder à votre site pour installer des fichiers.

#installation de certbot
apt install certbot

# Génération du certificat
certbot certonly --webroot -w /var/www/html -d votre-domaine.com -d www.votre-domaine.com

Installation de Postgresql dans un container docker Debian

Installation de postgresSQL

apt update
apt install postgresql

Nous sommes dans le cas où il n’y a pas systemd ou supervisor, et cet article reprend la suite de la procédure d’installation de nginx et php-fpm

Normalement si vous avez installé avec apt, pas besoin de créer un répertoire ni d’initialiser une base de données.

Nous allons maintenant lancer postgresql, la commande est un peut longue et vous ne pouvez pas lancer en root

postgres /usr/lib/postgresql/15/bin/postgres -D /var/lib/postgresql/15/main


"root" execution of the PostgreSQL server is not permitted.
The server must be started under an unprivileged user ID to prevent
possible system security compromise.  See the documentation for
more information on how to properly start the server.

De plus si on veut ne pas taper cette commande il vaut mieux ajouter au PATH

export PATH="$PATH:/usr/lib/postgresql/15/bin"

La commande à faire est via un utilisateur non root, il faut créer un utilisateur

// création d'utilisateur 
adduser refschool   // il vaut mieux utiliser adduser car useradd nécessite de setter le password avec la commande passwd

// on ajouter refschool dans la liste de sudoer
usermod -aG sudo refschool  // en fait on l'ajoute au groupe sudoer

On exécute en tant que utilisateur postgres

sudo -u postgres /usr/lib/postgresql/15/bin/postgres -D /var/lib/postgresql/15/main

Si vous avez l’erreur suivante : postgres: could not access the server configuration file « /var/lib/postgresql/15/main/postgresql.conf »: No such file or directory

c’est que la base de donnée a été initialisée mais ne contient pas le fichier de configuration postgresql.conf.

Nous allons initialiser la base dans un autre répertoire

mkdir -p /opt/postgres_data
chown postgres:postgres /opt/postgres_data
sudo -u postgres /usr/lib/postgresql/15/bin/initdb -D /opt/postgres_data

Une base de données sera initialisée dans le répertoire /opt/postgres_data

Sortie de cette commande

The files belonging to this database system will be owned by user "postgres".
This user must also own the server process.

The database cluster will be initialized with locale "C".                                                               The default database encoding has accordingly been set to "SQL_ASCII".                                                  The default text search configuration will be set to "english".

Data page checksums are disabled.

fixing permissions on existing directory /opt/postgres_data ... ok
creating subdirectories ... ok
selecting dynamic shared memory implementation ... posix
selecting default max_connections ... 100
selecting default shared_buffers ... 128MB
selecting default time zone ... Etc/UTC
creating configuration files ... ok
running bootstrap script ... ok
performing post-bootstrap initialization ... ok
syncing data to disk ... ok

initdb: warning: enabling "trust" authentication for local connections
initdb: hint: You can change this by editing pg_hba.conf or using the option -A, or --auth-local and --auth-host, the next time you run initdb.

Success. You can now start the database server using:

    /usr/lib/postgresql/15/bin/pg_ctl -D /opt/postgres_data -l logfile start

Attention lorsque vous arrêtez Postgresql, la commande pour le redémarrer est différente, en effet, on fait ici (ci-dessus) un initdb, qu’on fait une seule fois, pour les fois suivantes:

// à exécuter lorsque vous êtes loggé en utilisateur refschool (mais pas root)


sudo -u postgres /usr/lib/postgresql/15/bin/postgres -D /opt/postgres_data

Pratique des commandes principales de Postgresql

Une fois que vous êtes dans le prompt de Postgresql, vous pouvez faire les commandes suivantes:

Je rappelle qu’il faut démarrer postgresql avant, vous ne pouvez le faire en root, donc switchez sur un user normal et lancez la commande suivante :

// vous êtes en user normal (non root)

sudo -u postgres /usr/lib/postgresql/15/bin/postgres -D /opt/postgres_data

//puis dans un autre shell connectez vous avec la commande psql en tant que user postgres

psql -U postgres

lister les bases de données

\l  ou \list

postgres=# \list
                                             List of databases
   Name    |  Owner   | Encoding  | Collate | Ctype | ICU Locale | Locale Provider |   Access privileges   
-----------+----------+-----------+---------+-------+------------+-----------------+-----------------------
 devdb     | devuser  | SQL_ASCII | C       | C     |            | libc            | =Tc/devuser          +
           |          |           |         |       |            |                 | devuser=CTc/devuser
 postgres  | postgres | SQL_ASCII | C       | C     |            | libc            | 
 template0 | postgres | SQL_ASCII | C       | C     |            | libc            | =c/postgres          +
           |          |           |         |       |            |                 | postgres=CTc/postgres
 template1 | postgres | SQL_ASCII | C       | C     |            | libc            | =c/postgres          +
           |          |           |         |       |            |                 | postgres=CTc/postgres
(4 rows)

Choisir une base de donnés

Nous devons comme dans tous les sytème sde base de données choisir une base pour faire des requêtes

postgres=# \c devdb
You are now connected to database "devdb" as user "postgres".

// à noter que le prompt a changé

Créer une table et lister les tables

Coller ce code

devdb=# CREATE TABLE produit (
    id SERIAL PRIMARY KEY,
    nom VARCHAR(100) NOT NULL,
    description TEXT,
    prix NUMERIC(10,2) NOT NULL,
    stock INTEGER DEFAULT 0,
    date_creation TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);


devdb=# \dt
          List of relations
 Schema |  Name   | Type  |  Owner   
--------+---------+-------+----------
 public | produit | table | postgres
(1 row)

Accessoirement pour lister les schémas dans toutes les bases de données
\dt *.*

pour sortir du mode "pager" appuyez sur la touche "q"

Exécuter une requête SQL pour insérer des données

On va insérer des données :

devdb=# INSERT INTO produit (nom, description, prix, stock)
VALUES 
('Iphone', 'Description du Iphone', 1999, 10),
('Samsung', 'Description du Samsung', 299, 5);

// afficher les données de la table avec un SELECT
// attention il faut respecter la casse

SELECT * FROM produit

Faire un dump de la base de données

On va utiliser l’utilitaire pg_dump

pg_dump -U postgres -d devdb -f sav.sql
Retour en haut