Compare commits

5 Commits

Author SHA1 Message Date
f19239837c fix: patch .Site.Data references in Stack theme at build time
All checks were successful
Blog Test / Check-Rebuild (push) Successful in 8s
Blog Test / Build (push) Successful in 19s
Blog Test / Deploy-Test (push) Successful in 11s
Blog Test / Test (push) Successful in 3s
2026-04-01 06:42:49 +00:00
7baf7162b7 fix: revert local submodule commit 2026-04-01 06:40:30 +00:00
7d7b1aa627 fix: patch remaining .Site.Data references in Stack theme
Some checks failed
Blog Test / Check-Rebuild (push) Successful in 15s
Blog Test / Build (push) Has been skipped
Blog Test / Deploy-Test (push) Successful in 14s
Blog Test / Test (push) Failing after 5s
2026-04-01 06:25:37 +00:00
e170345fdd fix: revert to git clone instead of checkout
Some checks failed
Blog Test / Check-Rebuild (push) Successful in 8s
Blog Test / Build (push) Has been skipped
Blog Test / Deploy-Test (push) Successful in 11s
Blog Test / Test (push) Failing after 5s
2026-03-30 09:29:42 +00:00
44bf161765 update: theme/stack version
Some checks failed
Blog Test / Check-Rebuild (push) Failing after 5s
Blog Test / Build (push) Has been skipped
Blog Test / Deploy-Test (push) Has been skipped
Blog Test / Test (push) Has been skipped
2026-03-30 09:03:58 +00:00
218 changed files with 660 additions and 952 deletions

View File

@@ -24,7 +24,7 @@ jobs:
docker_folder_changed: ${{ steps.docker_folder.outputs.changed }} docker_folder_changed: ${{ steps.docker_folder.outputs.changed }}
steps: steps:
- name: Checkout Repository - name: Checkout Repository
run: git clone --branch ${{ gitea.ref_name }} https://${{ secrets.REPO_TOKEN }}@git.vezpi.com/Vezpi/blog.git . run: git clone --branch preview https://${{ secrets.REPO_TOKEN }}@git.vezpi.com/Vezpi/blog.git .
- name: Check Latest Hugo Version - name: Check Latest Hugo Version
id: get_latest id: get_latest
@@ -79,7 +79,7 @@ jobs:
shell: sh shell: sh
steps: steps:
- name: Checkout Repository - name: Checkout Repository
run: git clone --branch ${{ gitea.ref_name }} https://${{ secrets.REPO_TOKEN }}@git.vezpi.com/Vezpi/blog.git . run: git clone --branch preview https://${{ secrets.REPO_TOKEN }}@git.vezpi.com/Vezpi/blog.git .
- name: Build Docker Image - name: Build Docker Image
run: | run: |
@@ -108,7 +108,7 @@ jobs:
cd /blog cd /blog
docker compose down ${CONTAINER_NAME} docker compose down ${CONTAINER_NAME}
docker compose up -d ${CONTAINER_NAME} docker compose up -d ${CONTAINER_NAME}
sleep 30 sleep 5
echo "- Displaying container logs" echo "- Displaying container logs"
docker compose logs ${CONTAINER_NAME} docker compose logs ${CONTAINER_NAME}
@@ -116,7 +116,7 @@ jobs:
needs: Deploy-Staging needs: Deploy-Staging
runs-on: ubuntu runs-on: ubuntu
env: env:
URL: "https://blog-staging.vezpi.com/en/" URL: "https://blog-dev.vezpi.com/en/"
steps: steps:
- name: Check HTTP Response - name: Check HTTP Response
run: | run: |
@@ -143,7 +143,7 @@ jobs:
- name: Merge preview Branch on main - name: Merge preview Branch on main
run: | run: |
git merge --ff-only origin/${{ gitea.ref_name }} git merge --ff-only origin/preview
git push origin main git push origin main
Deploy-Production: Deploy-Production:
@@ -163,7 +163,7 @@ jobs:
cd /blog cd /blog
docker compose down ${CONTAINER_NAME} docker compose down ${CONTAINER_NAME}
docker compose up -d ${CONTAINER_NAME} docker compose up -d ${CONTAINER_NAME}
sleep 30 sleep 5
echo "- Displaying container logs" echo "- Displaying container logs"
docker compose logs ${CONTAINER_NAME} docker compose logs ${CONTAINER_NAME}
@@ -194,10 +194,7 @@ jobs:
steps: steps:
- name: Remove Old Docker Image - name: Remove Old Docker Image
run: | run: |
IMAGE_IDS=$(docker image ls "${DOCKER_IMAGE}" 2>/dev/null | awk '$NF != "U" && NR>1 {print $2}') docker image rm ${{ needs.Check-Rebuild.outputs.current_docker_image }} --force
if [ -n "$IMAGE_IDS" ]; then
docker image rm $IMAGE_IDS
fi
Notify: Notify:
needs: [Check-Rebuild, Build, Deploy-Staging, Test-Staging, Merge, Deploy-Production, Test-Production, Clean] needs: [Check-Rebuild, Build, Deploy-Staging, Test-Staging, Merge, Deploy-Production, Test-Production, Clean]

View File

@@ -87,7 +87,7 @@ jobs:
--build-arg HUGO_VERSION=${{ needs.Check-Rebuild.outputs.latest_hugo_version }} \ --build-arg HUGO_VERSION=${{ needs.Check-Rebuild.outputs.latest_hugo_version }} \
--tag ${DOCKER_IMAGE}:${{ needs.Check-Rebuild.outputs.latest_hugo_version }} \ --tag ${DOCKER_IMAGE}:${{ needs.Check-Rebuild.outputs.latest_hugo_version }} \
. .
docker tag ${DOCKER_IMAGE}:${{ needs.Check-Rebuild.outputs.latest_hugo_version }} ${DOCKER_IMAGE}:dev docker tag ${DOCKER_IMAGE}:${{ needs.Check-Rebuild.outputs.latest_hugo_version }} ${DOCKER_IMAGE}:test
Deploy-Test: Deploy-Test:
needs: needs:
@@ -102,14 +102,14 @@ jobs:
run: run:
shell: sh shell: sh
env: env:
CONTAINER_NAME: blog_dev CONTAINER_NAME: blog_test
steps: steps:
- name: Launch Blog Test Deployment - name: Launch Blog Test Deployment
run: | run: |
cd /blog cd /blog
docker compose down ${CONTAINER_NAME} docker compose down ${CONTAINER_NAME}
BLOG_TEST_BRANCH=${{ gitea.ref_name }} docker compose up -d ${CONTAINER_NAME} BLOG_TEST_BRANCH=${{ gitea.ref_name }} docker compose up -d ${CONTAINER_NAME}
sleep 30 sleep 5
echo "- Displaying container logs" echo "- Displaying container logs"
docker compose logs ${CONTAINER_NAME} docker compose logs ${CONTAINER_NAME}
@@ -117,7 +117,7 @@ jobs:
needs: Deploy-Test needs: Deploy-Test
runs-on: ubuntu runs-on: ubuntu
env: env:
URL: "https://blog-dev.vezpi.com/en/" URL: "https://blog-test.vezpi.com/en/"
steps: steps:
- name: Check HTTP Response - name: Check HTTP Response
run: | run: |

View File

@@ -3,15 +3,9 @@
} }
.lang-toggle-icon { .lang-toggle-icon {
margin-left: 0; margin-left: auto;
svg { svg {
width: 64px; width: 64px;
height: 24px; height: 24px;
} }
} }
.container {
.left-sidebar {
width: unset;
}
}

View File

@@ -122,9 +122,9 @@ La meilleure solution que j'ai trouvée a été de percer deux trous de 40 mm a
Voici à quoi ça ressemble : Voici à quoi ça ressemble :
![Vue de face de mon homelab avec légende](images/homelab-rack-legend.png) ![Front view of my homelab with legend](img/homelab-rack-legend.png)
![Différentes vues de mon homelab avec armoire ouverte et fermée](images/homelab-enclore-open-closed.png) ![Different views of my homelab with open and closed enclosure](img/homelab-enclore-open-closed.png)
--- ---
## Stack Logicielle ## Stack Logicielle
@@ -183,7 +183,7 @@ Cette configuration de proxy à deux couches centralise la gestion des certifica
Pour un accès distant sécurisé, j'ai configuré **WireGuard** sur OPNsense. Ce VPN léger fournit une connectivité chiffrée à mon lab où que je sois, permettant ainsi de gérer tous mes services sans les exposer directement à Internet. Pour un accès distant sécurisé, j'ai configuré **WireGuard** sur OPNsense. Ce VPN léger fournit une connectivité chiffrée à mon lab où que je sois, permettant ainsi de gérer tous mes services sans les exposer directement à Internet.
#### Schéma Réseau #### Schéma Réseau
![Schéma de mon réseau domestique](images/homelab-network-schema.png) ![Diagram of my home network ](img/homelab-network-schema.png)
### Application ### Application

View File

@@ -121,9 +121,9 @@ Inside the rack, I also added two 80mm fans to help with airflow. To keep everyt
Here what is look like: Here what is look like:
![Front view of my homelab with legend](images/homelab-rack-legend.png) ![Front view of my homelab with legend](img/homelab-rack-legend.png)
![Different views of my homelab with open and closed enclosure](images/homelab-enclore-open-closed.png) ![Different views of my homelab with open and closed enclosure](img/homelab-enclore-open-closed.png)
--- ---
@@ -184,7 +184,7 @@ This two-layer proxy setup centralizes SSL certificate management in **Caddy** w
For secure remote access, I configured **WireGuard** on OPNsense. This lightweight VPN provides encrypted connectivity to my lab from anywhere, allowing management of all my services without exposing them all directly to the internet. For secure remote access, I configured **WireGuard** on OPNsense. This lightweight VPN provides encrypted connectivity to my lab from anywhere, allowing management of all my services without exposing them all directly to the internet.
#### Network Diagram #### Network Diagram
![Diagram of my home network ](images/homelab-network-schema.png) ![Diagram of my home network ](img/homelab-network-schema.png)
### Application ### Application
Let's dive into the fun part! What started as a modest setup meant to serve a few personal needs quickly turned into a full ecosystem of open source services, each solving a specific need or just satisfying curiosity. Let's dive into the fun part! What started as a modest setup meant to serve a few personal needs quickly turned into a full ecosystem of open source services, each solving a specific need or just satisfying curiosity.

View File

@@ -32,7 +32,7 @@ Tout d'abord, nous devons télécharger une image compatible cloud-init. Bien qu
Trouvez des images compatibles cloud dans le [Guide des images OpenStack](https://docs.openstack.org/image-guide/obtain-images.html). Trouvez des images compatibles cloud dans le [Guide des images OpenStack](https://docs.openstack.org/image-guide/obtain-images.html).
Dans Proxmox, accédez à **Storage > ISO Images > Upload** pour uploader l'image téléchargée. Dans Proxmox, accédez à **Storage > ISO Images > Upload** pour uploader l'image téléchargée.
![Fenêtre de téléchargement pour images ISO dans Proxmox](images/proxmox-download-iso-img.png) ![Download window for ISO images in Proxmox](img/proxmox-download-iso-img.png)
## Créer la VM ## Créer la VM
Ensuite, on crée une VM en utilisant la ligne de commande (CLI) depuis le nœud Proxmox avec la commande suivantes : Ensuite, on crée une VM en utilisant la ligne de commande (CLI) depuis le nœud Proxmox avec la commande suivantes :

View File

@@ -32,7 +32,7 @@ First, we need to download an image with cloud-init support. Although Rocky Linu
Find cloud-ready images from the [OpenStack Image Guide](https://docs.openstack.org/image-guide/obtain-images.html). Find cloud-ready images from the [OpenStack Image Guide](https://docs.openstack.org/image-guide/obtain-images.html).
In Proxmox, navigate to **Storage > ISO Images > Upload** to upload the downloaded image. In Proxmox, navigate to **Storage > ISO Images > Upload** to upload the downloaded image.
![Download window for ISO images in Proxmox](images/proxmox-download-iso-img.png) ![Download window for ISO images in Proxmox](img/proxmox-download-iso-img.png)
## Create the VM ## Create the VM

View File

@@ -21,7 +21,7 @@ Ce genre dexercice est la pire chose que vous souhaitez voir arriver, parce q
Ma box OPNsense tournait parfaitement depuis des mois. Routeur, pare-feu, DNS, DHCP, VLANs, VPN, reverse proxy et même contrôleur UniFi : toutes les pièces de mon homelab passe par elle. Mais pas seulement, elle fournit aussi Internet à la maison. Ma box OPNsense tournait parfaitement depuis des mois. Routeur, pare-feu, DNS, DHCP, VLANs, VPN, reverse proxy et même contrôleur UniFi : toutes les pièces de mon homelab passe par elle. Mais pas seulement, elle fournit aussi Internet à la maison.
![Le schéma réseau de mon Homelab](images/homelab-network-schema.png) ![Diagram of my home network ](img/homelab-network-schema.png)
Cette box est le cœur de mon réseau, sans elle, je ne peux quasiment rien faire. Jai détaillé son fonctionnement dans ma section [Homelab]({{< ref "page/homelab" >}}). Tout “fonctionnait juste”, et je ne men inquiétait pas. Jétais confiant, sa sauvegarde vivait uniquement à lintérieur de la machine… Cette box est le cœur de mon réseau, sans elle, je ne peux quasiment rien faire. Jai détaillé son fonctionnement dans ma section [Homelab]({{< ref "page/homelab" >}}). Tout “fonctionnait juste”, et je ne men inquiétait pas. Jétais confiant, sa sauvegarde vivait uniquement à lintérieur de la machine…
@@ -61,7 +61,7 @@ pkg: sqlite error while executing iterator in file pkgdb_iterator.c:1110: databa
``` ```
🚨 Mon alarme interne s'est déclenchée. Jai pensé aux sauvegardes et jai immédiatement téléchargé la dernière : 🚨 Mon alarme interne s'est déclenchée. Jai pensé aux sauvegardes et jai immédiatement téléchargé la dernière :
![Sauvegarde de configuration dans OPNsense](images/opnsense-download-backup.png) ![Backup configuration in OPNsense](img/opnsense-download-backup.png)
En cliquant sur le bouton `Download configuration`, jai récupéré le `config.xml` en cours dutilisation. Je pensais que ça suffirait. En cliquant sur le bouton `Download configuration`, jai récupéré le `config.xml` en cours dutilisation. Je pensais que ça suffirait.

View File

@@ -21,7 +21,7 @@ This kind of exercise is the worst thing you want to happen because it's never f
My OPNsense box had been running smoothly for months. Router, firewall, DNS, DHCP, VLANs, VPN, reverse proxy and even UniFi controller: all the pieces of my homelab run through it. but not only, it is also serving internet at home. My OPNsense box had been running smoothly for months. Router, firewall, DNS, DHCP, VLANs, VPN, reverse proxy and even UniFi controller: all the pieces of my homelab run through it. but not only, it is also serving internet at home.
![My Homelab network diagram](images/homelab-network-schema.png) ![Diagram of my home network ](img/homelab-network-schema.png)
This box is the heart of my network, without it, I can hardly do anything. I have detailed how this is working in my [Homelab]({{< ref "page/homelab" >}}) section. It was “just working,” and I wasnt worried about it. I felt confident, its backup was living only inside the machine... This box is the heart of my network, without it, I can hardly do anything. I have detailed how this is working in my [Homelab]({{< ref "page/homelab" >}}) section. It was “just working,” and I wasnt worried about it. I felt confident, its backup was living only inside the machine...
@@ -62,7 +62,7 @@ pkg: sqlite error while executing iterator in file pkgdb_iterator.c:1110: databa
``` ```
🚨 My internal alarm sensor triggered, I wondered about backups, I immediately decided to download the latest backup: 🚨 My internal alarm sensor triggered, I wondered about backups, I immediately decided to download the latest backup:
![Backup configuration in OPNsense](images/opnsense-download-backup.png) ![Backup configuration in OPNsense](img/opnsense-download-backup.png)
Clicking the `Download configuration` button, I downloaded the current `config.xml` in use my the instance, I though it was enough. Clicking the `Download configuration` button, I downloaded the current `config.xml` in use my the instance, I though it was enough.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 KiB

View File

@@ -13,7 +13,7 @@ categories:
## Intro ## Intro
Quand jai construit mon cluster **Proxmox VE 8** pour la première fois, le réseau nétait pas ma priorité. Je voulais simplement remplacer rapidement un vieux serveur physique, alors jai donné la même configuration de base à chacun de mes trois nœuds, créé le cluster et commencé à créer des VM : Quand jai construit mon cluster **Proxmox VE 8** pour la première fois, le réseau nétait pas ma priorité. Je voulais simplement remplacer rapidement un vieux serveur physique, alors jai donné la même configuration de base à chacun de mes trois nœuds, créé le cluster et commencé à créer des VM :
![Configuration réseau dun nœud Proxmox](images/proxmox-node-network-configuration.png) ![Configuration réseau dun nœud Proxmox](img/proxmox-node-network-configuration.png)
Cela a bien fonctionné pendant un moment. Mais comme je prévois de virtualiser mon routeur **OPNsense**, jai besoin de quelque chose de plus structuré et cohérent. Cest là que la fonctionnalité **S**oftware-**D**efined **N**etworking (SDN) de Proxmox entre en jeu. Cela a bien fonctionné pendant un moment. Mais comme je prévois de virtualiser mon routeur **OPNsense**, jai besoin de quelque chose de plus structuré et cohérent. Cest là que la fonctionnalité **S**oftware-**D**efined **N**etworking (SDN) de Proxmox entre en jeu.
@@ -21,7 +21,7 @@ Cela a bien fonctionné pendant un moment. Mais comme je prévois de virtualiser
## Mon Réseau Homelab ## Mon Réseau Homelab
Par défaut, chaque nœud Proxmox dispose de sa propre zone locale, appelée `localnetwork`, qui contient le pont Linux par défaut (`vmbr0`) comme VNet : Par défaut, chaque nœud Proxmox dispose de sa propre zone locale, appelée `localnetwork`, qui contient le pont Linux par défaut (`vmbr0`) comme VNet :
![Zones `localnetwork` par défaut dans Proxmox](images/proxmox-default-localnetwork-zone.png) ![Proxmox default `localnetwork` zones](img/proxmox-default-localnetwork-zone.png)
Cest suffisant pour des configurations isolées, mais rien nest coordonné au niveau du cluster. Cest suffisant pour des configurations isolées, mais rien nest coordonné au niveau du cluster.
@@ -61,29 +61,29 @@ Proxmox prend en charge plusieurs types de zones :
- **EVPN** : VXLAN avec BGP pour du routage L3 dynamique - **EVPN** : VXLAN avec BGP pour du routage L3 dynamique
Comme mon réseau domestique utilise déjà des VLAN, jai créé une **zone VLAN** appelée `homelan`, en utilisant `vmbr0` comme pont et en lappliquant à tout le cluster : Comme mon réseau domestique utilise déjà des VLAN, jai créé une **zone VLAN** appelée `homelan`, en utilisant `vmbr0` comme pont et en lappliquant à tout le cluster :
![Création dune zone VLAN dans Proxmox SDN](images/proxmox-create-vlan-zone-homelan.png) ![Create a VLAN zone in the Proxmox SDN](img/proxmox-create-vlan-zone-homelan.png)
### VNets ### VNets
Un **VNet** est un réseau virtuel à lintérieur dune zone. Dans une zone VLAN, chaque VNet correspond à un ID VLAN spécifique. Un **VNet** est un réseau virtuel à lintérieur dune zone. Dans une zone VLAN, chaque VNet correspond à un ID VLAN spécifique.
Jai commencé par créer `vlan55` dans la zone `homelan` pour mon réseau DMZ : Jai commencé par créer `vlan55` dans la zone `homelan` pour mon réseau DMZ :
![Création dun VNet pour le VLAN 55 dans la zone homelan](images/proxmox-create-vlan-vnet-homelan.png) ![Create a VNet for VLAN 55 in the homelan zone](img/proxmox-create-vlan-vnet-homelan.png)
Puis jai ajouté les VNets correspondant à la plupart de mes VLAN, puisque je prévois de les rattacher à une VM OPNsense : Puis jai ajouté les VNets correspondant à la plupart de mes VLAN, puisque je prévois de les rattacher à une VM OPNsense :
![Tous mes VLANs créés dans le Proxmox SDN](images/proxmox-sdn-all-vlan-homelan.png) ![All my VLANs created in the Proxmox SDN](img/proxmox-sdn-all-vlan-homelan.png)
Enfin, jai appliqué la configuration dans **Datacenter → SDN** : Enfin, jai appliqué la configuration dans **Datacenter → SDN** :
![Application de la configuration SDN dans Proxmox](images/proxmox-apply-sdn-homelan-configuration.png) ![Application de la configuration SDN dans Proxmox](img/proxmox-apply-sdn-homelan-configuration.png)
--- ---
## Test de la Configuration Réseau ## Test de la Configuration Réseau
Dans une vieille VM que je n'utilise plus, je remplace l'actuel `vmbr0` avec le VLAN tag 66 par mon nouveau VNet `vlan66`: Dans une vieille VM que je n'utilise plus, je remplace l'actuel `vmbr0` avec le VLAN tag 66 par mon nouveau VNet `vlan66`:
![Changement du pont réseau dans une VM](images/proxmox-change-vm-nic-vlan-vnet.png) ![Change the network bridge in a VM](img/proxmox-change-vm-nic-vlan-vnet.png)
Après l'avoir démarrée, la VM obtient une IP du DHCP d'OPNsense sur ce VLAN, ce qui est super. J'essaye également de ping une autre machine et ça fonctionne : Après l'avoir démarrée, la VM obtient une IP du DHCP d'OPNsense sur ce VLAN, ce qui est super. J'essaye également de ping une autre machine et ça fonctionne :
![Ping dune autre machine dans le même VLAN](images/proxmox-console-ping-vm-vlan-66.png) ![Ping another machine in the same VLAN](img/proxmox-console-ping-vm-vlan-66.png)
--- ---
## Mise à jour de Cloud-Init et Terraform ## Mise à jour de Cloud-Init et Terraform
@@ -92,7 +92,7 @@ Pour aller plus loin, jai mis à jour le pont réseau utilisé dans mon **tem
Comme avec la VM précédente, jai remplacé `vmbr0` et le tag VLAN 66 par le nouveau VNet `vlan66`. Comme avec la VM précédente, jai remplacé `vmbr0` et le tag VLAN 66 par le nouveau VNet `vlan66`.
Jai aussi adapté mon code **Terraform** pour refléter ce changement : Jai aussi adapté mon code **Terraform** pour refléter ce changement :
![Mise à jour du code Terraform pour vlan66](images/terraform-code-update-vlan66.png) ![Mise à jour du code Terraform pour vlan66](img/terraform-code-update-vlan66.png)
Ensuite, jai validé quaucune régression nétait introduite en déployant une VM de test : Ensuite, jai validé quaucune régression nétait introduite en déployant une VM de test :
```bash ```bash
@@ -129,7 +129,7 @@ vm_ip = "192.168.66.181"
``` ```
La création sest déroulée sans problème, tout est bon : La création sest déroulée sans problème, tout est bon :
![VM déployée par Terraform sur vlan66](images/proxmox-terraform-test-deploy-vlan66.png) ![VM déployée par Terraform sur vlan66](img/proxmox-terraform-test-deploy-vlan66.png)
--- ---
## Conclusion ## Conclusion

View File

@@ -13,7 +13,7 @@ categories:
## Intro ## Intro
When I first built my **Proxmox VE 8** cluster, networking wasnt my main concern. I just wanted to replace an old physical server quickly, so I gave each of my three nodes the same basic config, created the cluster, and started running VMs: When I first built my **Proxmox VE 8** cluster, networking wasnt my main concern. I just wanted to replace an old physical server quickly, so I gave each of my three nodes the same basic config, created the cluster, and started running VMs:
![Proxmox node network configuration](images/proxmox-node-network-configuration.png) ![Configuration réseau dun nœud Proxmox](img/proxmox-node-network-configuration.png)
That worked fine for a while. But as I plan to virtualize my **OPNsense** router, I need something more structured and consistent. This is where Proxmox **S**oftware-**D**efined **N**etworking (SDN) feature comes in. That worked fine for a while. But as I plan to virtualize my **OPNsense** router, I need something more structured and consistent. This is where Proxmox **S**oftware-**D**efined **N**etworking (SDN) feature comes in.
@@ -21,7 +21,7 @@ That worked fine for a while. But as I plan to virtualize my **OPNsense** router
## My Homelab Network ## My Homelab Network
By default, every Proxmox node comes with its own local zone, called `localnetwork`, which contains the default Linux bridge (`vmbr0`) as a VNet: By default, every Proxmox node comes with its own local zone, called `localnetwork`, which contains the default Linux bridge (`vmbr0`) as a VNet:
![Proxmox default `localnetwork` zones](images/proxmox-default-localnetwork-zone.png) ![Proxmox default `localnetwork` zones](img/proxmox-default-localnetwork-zone.png)
Thats fine for isolated setups, but at the cluster level nothing is coordinated. Thats fine for isolated setups, but at the cluster level nothing is coordinated.
@@ -61,29 +61,29 @@ Proxmox supports several zone types:
- **EVPN**: VXLAN with BGP to establish Layer 3 routing - **EVPN**: VXLAN with BGP to establish Layer 3 routing
Since my home network already relies on VLANs, I created a **VLAN Zone** named `homelan`, using `vmbr0` as the bridge and applying it cluster-wide: Since my home network already relies on VLANs, I created a **VLAN Zone** named `homelan`, using `vmbr0` as the bridge and applying it cluster-wide:
![Create a VLAN zone in the Proxmox SDN](images/proxmox-create-vlan-zone-homelan.png) ![Create a VLAN zone in the Proxmox SDN](img/proxmox-create-vlan-zone-homelan.png)
### VNets ### VNets
A **VNet** is a virtual network inside a zone. In a VLAN zone, each VNet corresponds to a specific VLAN ID. A **VNet** is a virtual network inside a zone. In a VLAN zone, each VNet corresponds to a specific VLAN ID.
I started by creating `vlan55` in the `homelan` zone for my DMZ network: I started by creating `vlan55` in the `homelan` zone for my DMZ network:
![Create a VNet for VLAN 55 in the homelan zone](images/proxmox-create-vlan-vnet-homelan.png) ![Create a VNet for VLAN 55 in the homelan zone](img/proxmox-create-vlan-vnet-homelan.png)
Then I added VNets for most of my VLANs, since I plan to attach them to an OPNsense VM: Then I added VNets for most of my VLANs, since I plan to attach them to an OPNsense VM:
![All my VLANs created in the Proxmox SDN](images/proxmox-sdn-all-vlan-homelan.png) ![All my VLANs created in the Proxmox SDN](img/proxmox-sdn-all-vlan-homelan.png)
Finally, I applied the configuration in **Datacenter → SDN**: Finally, I applied the configuration in **Datacenter → SDN**:
![Apply SDN configuration in Proxmox](images/proxmox-apply-sdn-homelan-configuration.png) ![Application de la configuration SDN dans Proxmox](img/proxmox-apply-sdn-homelan-configuration.png)
--- ---
## Test the Network Configuration ## Test the Network Configuration
In a old VM which I don't use anymore, I replace the current `vmbr0` with VLAN tag 66 to my new VNet `vlan66`: In a old VM which I don't use anymore, I replace the current `vmbr0` with VLAN tag 66 to my new VNet `vlan66`:
![Change the network bridge in a VM](images/proxmox-change-vm-nic-vlan-vnet.png) ![Change the network bridge in a VM](img/proxmox-change-vm-nic-vlan-vnet.png)
After starting it, the VM gets an IP from the DHCP on OPNsense on that VLAN, which sounds good. I also try to ping another machine and it works: After starting it, the VM gets an IP from the DHCP on OPNsense on that VLAN, which sounds good. I also try to ping another machine and it works:
![Ping another machine in the same VLAN](images/proxmox-console-ping-vm-vlan-66.png) ![Ping another machine in the same VLAN](img/proxmox-console-ping-vm-vlan-66.png)
--- ---
## Update Cloud-Init Template and Terraform ## Update Cloud-Init Template and Terraform
@@ -91,7 +91,7 @@ After starting it, the VM gets an IP from the DHCP on OPNsense on that VLAN, whi
To go further, I update the bridge used in my **cloud-init** template, which I detailed the creation in that [post]({{< ref "post/1-proxmox-cloud-init-vm-template" >}}). Pretty much the same thing I've done with the VM, I replace the current `vmbr0` with VLAN tag 66 with my new VNet `vlan66`. To go further, I update the bridge used in my **cloud-init** template, which I detailed the creation in that [post]({{< ref "post/1-proxmox-cloud-init-vm-template" >}}). Pretty much the same thing I've done with the VM, I replace the current `vmbr0` with VLAN tag 66 with my new VNet `vlan66`.
I also update the **Terrafom** code to take this change into account: I also update the **Terrafom** code to take this change into account:
![Terraform code change for the vlan66](images/terraform-code-update-vlan66.png) ![Mise à jour du code Terraform pour vlan66](img/terraform-code-update-vlan66.png)
I quicky check if I don't have regression and can still deploy a VM with Terraform: I quicky check if I don't have regression and can still deploy a VM with Terraform:
```bash ```bash
@@ -128,7 +128,7 @@ vm_ip = "192.168.66.181"
``` ```
The VM is deploying without any issue, everything is OK: The VM is deploying without any issue, everything is OK:
![VM hardware in Proxmox deployed by Terraform](images/proxmox-terraform-test-deploy-vlan66.png) ![VM déployée par Terraform sur vlan66](img/proxmox-terraform-test-deploy-vlan66.png)
--- ---
## Conclusion ## Conclusion

View File

@@ -29,7 +29,7 @@ Au sommet de mon installation, mon modem FAI, une _Freebox_ en mode bridge, reli
Ce switch relie également mes trois nœuds Proxmox, chacun sur un port trunk avec le même VLAN natif. Chaque nœud dispose de deux cartes réseau : une pour le trafic général, et lautre dédiée au réseau de stockage Ceph, connecté à un switch séparé de 2,5 Gbps. Ce switch relie également mes trois nœuds Proxmox, chacun sur un port trunk avec le même VLAN natif. Chaque nœud dispose de deux cartes réseau : une pour le trafic général, et lautre dédiée au réseau de stockage Ceph, connecté à un switch séparé de 2,5 Gbps.
Depuis le crash dOPNsense, jai simplifié larchitecture en supprimant le lien LACP, qui napportait pas de réelle valeur : Depuis le crash dOPNsense, jai simplifié larchitecture en supprimant le lien LACP, qui napportait pas de réelle valeur :
![Schéma réseau du homelab actuel](images/homelan-current-physical-layout.png) ![Current homelab network diagram](img/homelan-current-physical-layout.png)
Jusquà récemment, le réseau Proxmox de mon cluster était très basique : chaque nœud était configuré individuellement sans véritable logique commune. Cela a changé après la découverte du SDN Proxmox, qui ma permis de centraliser les définitions de VLAN sur lensemble du cluster. Jai décrit cette étape dans [cet article]({{< ref "post/11-proxmox-cluster-networking-sdn" >}}). Jusquà récemment, le réseau Proxmox de mon cluster était très basique : chaque nœud était configuré individuellement sans véritable logique commune. Cela a changé après la découverte du SDN Proxmox, qui ma permis de centraliser les définitions de VLAN sur lensemble du cluster. Jai décrit cette étape dans [cet article]({{< ref "post/11-proxmox-cluster-networking-sdn" >}}).
@@ -43,7 +43,7 @@ Place au lab. Voici les étapes principales :
4. Configurer la haute disponibilité 4. Configurer la haute disponibilité
5. Tester la bascule 5. Tester la bascule
![Schéma du POC pour OPNsense en haute disponibilité](images/poc-opnsense-diagram.png) ![Diagram of the POC for OPNsense high availability](img/poc-opnsense-diagram.png)
### Ajouter des VLANs dans mon homelab ### Ajouter des VLANs dans mon homelab
@@ -53,7 +53,7 @@ Pour cette expérimentation, je crée trois nouveaux VLANs :
- **VLAN 103** : _POC pfSync_ - **VLAN 103** : _POC pfSync_
Dans linterface Proxmox, je vais dans `Datacenter` > `SDN` > `VNets` et je clique sur `Create` : Dans linterface Proxmox, je vais dans `Datacenter` > `SDN` > `VNets` et je clique sur `Create` :
![Création des VLANs POC dans le SDN Proxmox](images/proxmox-sdn-create-poc-vlans.png) ![Create POC VLANs in the Proxmox SDN](img/proxmox-sdn-create-poc-vlans.png)
Une fois les trois VLANs créés, japplique la configuration. Une fois les trois VLANs créés, japplique la configuration.
@@ -114,7 +114,7 @@ La VM `fake-freebox` est maintenant prête à fournir du DHCP sur le VLAN 101, a
### Construire les VMs OPNsense ### Construire les VMs OPNsense
Je commence par télécharger lISO dOPNsense et je lupload sur un de mes nœuds Proxmox : Je commence par télécharger lISO dOPNsense et je lupload sur un de mes nœuds Proxmox :
![Upload de lISO OPNsense dans Proxmox](images/proxmox-upload-opnsense-iso.png) ![Upload de lISO OPNsense dans Proxmox](img/proxmox-upload-opnsense-iso.png)
#### Création de la VM #### Création de la VM
@@ -128,69 +128,69 @@ Je crée la première VM `poc-opnsense-1` avec les paramètres suivants :
1. VLAN 101 (_POC WAN_) 1. VLAN 101 (_POC WAN_)
2. VLAN 102 (_POC LAN_) 2. VLAN 102 (_POC LAN_)
3. VLAN 103 (_POC pfSync_) 3. VLAN 103 (_POC pfSync_)
![Paramètres de la VM OPNsense dans Proxmox](images/proxmox-create-poc-vm-opnsense.png) ![OPNsense VM settings in Proxmox](img/proxmox-create-poc-vm-opnsense.png)
Avant de la démarrer, je clone cette VM pour préparer la seconde : `poc-opnsense-2` Avant de la démarrer, je clone cette VM pour préparer la seconde : `poc-opnsense-2`
Au premier démarrage, je tombe sur une erreur “access denied”. Pour corriger, jentre dans le BIOS, **Device Manager > Secure Boot Configuration**, je décoche _Attempt Secure Boot_ et je redémarre : Au premier démarrage, je tombe sur une erreur “access denied”. Pour corriger, jentre dans le BIOS, **Device Manager > Secure Boot Configuration**, je décoche _Attempt Secure Boot_ et je redémarre :
![Désactiver Secure Boot dans le BIOS de Proxmox](images/proxmox-disable-secure-boot-option.png) ![Disable Secure Boot in Proxmox BIOS](img/proxmox-disable-secure-boot-option.png)
#### Installation dOPNsense #### Installation dOPNsense
La VM démarre sur lISO, je ne touche à rien jusquà lécran de login : La VM démarre sur lISO, je ne touche à rien jusquà lécran de login :
![Écran de login OPNsense en mode LiveCD](images/opnsense-vm-installation-welcome.png) ![OPNsense CLI login screen in LiveCD](img/opnsense-vm-installation-welcome.png)
Je me connecte avec `installer` / `opnsense` et je lance linstallateur. Je sélectionne le disque QEMU de 20 Go comme destination et je démarre linstallation : Je me connecte avec `installer` / `opnsense` et je lance linstallateur. Je sélectionne le disque QEMU de 20 Go comme destination et je démarre linstallation :
![Barre de progression de linstallation OPNsense](images/opnsense-vm-installation-progress-bar.png) ![Barre de progression de linstallation OPNsense](img/opnsense-vm-installation-progress-bar.png)
Une fois terminé, je retire lISO du lecteur et je redémarre la machine. Une fois terminé, je retire lISO du lecteur et je redémarre la machine.
#### Configuration de Base dOPNsense #### Configuration de Base dOPNsense
Au redémarrage, je me connecte avec `root` / `opnsense` et jarrive au menu CLI : Au redémarrage, je me connecte avec `root` / `opnsense` et jarrive au menu CLI :
![Menu CLI après une installation fraîche dOPNsense](images/opnsense-vm-installation-cli-menu.png) ![Menu CLI après une installation fraîche dOPNsense](img/opnsense-vm-installation-cli-menu.png)
Avec loption 1, je réassigne les interfaces : Avec loption 1, je réassigne les interfaces :
![Configuration des interfaces dans OPNsense via le CLI](images/opnsense-vm-installation-assign-interfaces.png) ![Configuration des interfaces dans OPNsense via le CLI](img/opnsense-vm-installation-assign-interfaces.png)
Linterface WAN récupère bien `10.101.0.150/24` depuis la `fake-freebox`. Je configure le LAN sur `10.102.0.2/24` et jajoute un pool DHCP de `10.102.0.10` à `10.102.0.99` : Linterface WAN récupère bien `10.101.0.150/24` depuis la `fake-freebox`. Je configure le LAN sur `10.102.0.2/24` et jajoute un pool DHCP de `10.102.0.10` à `10.102.0.99` :
![Interface WAN OPNsense recevant une IP depuis la VM `fake-freebox`](images/opnsense-vm-installation-interfaces-configured.png) ![Interface WAN OPNsense recevant une IP depuis la VM `fake-freebox`](img/opnsense-vm-installation-interfaces-configured.png)
✅ La première VM est prête, je reproduis lopération pour la seconde OPNsense `poc-opnsense-2`, qui aura lIP `10.102.0.3`. ✅ La première VM est prête, je reproduis lopération pour la seconde OPNsense `poc-opnsense-2`, qui aura lIP `10.102.0.3`.
### Configurer OPNsense en Haute Disponibilité ### Configurer OPNsense en Haute Disponibilité
Avec les deux VMs OPNsense opérationnelles, il est temps de passer à la configuration via le WebGUI. Pour y accéder, jai connecté une VM Windows au VLAN _POC LAN_ et ouvert lIP de lOPNsense sur le port 443 : Avec les deux VMs OPNsense opérationnelles, il est temps de passer à la configuration via le WebGUI. Pour y accéder, jai connecté une VM Windows au VLAN _POC LAN_ et ouvert lIP de lOPNsense sur le port 443 :
![OPNsense WebGUI depuis une VM Windows](images/opnsense-vm-webgui-from-poc-lan.png) ![OPNsense WebGUI depuis une VM Windows](img/opnsense-vm-webgui-from-poc-lan.png)
#### Ajouter lInterface pfSync #### Ajouter lInterface pfSync
La troisième carte réseau (`vtnet2`) est assignée à linterface _pfSync_. Ce réseau dédié permet aux deux firewalls de synchroniser leurs états via le VLAN _POC pfSync_ : La troisième carte réseau (`vtnet2`) est assignée à linterface _pfSync_. Ce réseau dédié permet aux deux firewalls de synchroniser leurs états via le VLAN _POC pfSync_ :
![Ajouter linterface pfSync dans OPNsense](images/opnsense-vm-assign-pfsync-interface.png) ![Add pfSync interface in OPNsense](img/opnsense-vm-assign-pfsync-interface.png)
Jactive linterface sur chaque instance et je leur attribue une IP statique : Jactive linterface sur chaque instance et je leur attribue une IP statique :
- **poc-opnsense-1** : `10.103.0.2/24` - **poc-opnsense-1** : `10.103.0.2/24`
- **poc-opnsense-2** : `10.103.0.3/24` - **poc-opnsense-2** : `10.103.0.3/24`
Puis, jajoute une règle firewall sur chaque nœud pour autoriser tout le trafic provenant de ce réseau sur linterface _pfSync_ : Puis, jajoute une règle firewall sur chaque nœud pour autoriser tout le trafic provenant de ce réseau sur linterface _pfSync_ :
![Règle firewall pour autoriser tout le trafic pfSync](images/opnsense-vm-firewall-allow-pfsync.png) ![Create new firewall rule on pfSync interface to allow any traffic in that network](img/opnsense-vm-firewall-allow-pfsync.png)
#### Configurer la Haute Disponibilité #### Configurer la Haute Disponibilité
Direction `System` > `High Availability` > `Settings`. Direction `System` > `High Availability` > `Settings`.
- Sur le master (`poc-opnsense-1`), je configure les `General Settings` et les `Synchronization Settings`. - Sur le master (`poc-opnsense-1`), je configure les `General Settings` et les `Synchronization Settings`.
- Sur le backup (`poc-opnsense-2`), seuls les `General Settings` suffisent (on ne veut pas quil écrase la config du master). - Sur le backup (`poc-opnsense-2`), seuls les `General Settings` suffisent (on ne veut pas quil écrase la config du master).
![Paramètres de Haute Disponibilité dans OPNsense](images/opnsense-vm-high-availability-settings.png) ![OPNsense High Availability settings](img/opnsense-vm-high-availability-settings.png)
Une fois appliqué, je vérifie la synchro dans longlet `Status` : Une fois appliqué, je vérifie la synchro dans longlet `Status` :
![Statut de la Haute Disponibilité OPNsense](images/opnsense-vm-high-availability-status.png) ![OPNsense High Availability status](img/opnsense-vm-high-availability-status.png)
#### Créer une IP Virtuelle #### Créer une IP Virtuelle
Pour fournir une passerelle partagée aux clients, je crée une IP virtuelle (VIP) en **CARP** (Common Address Redundancy Protocol) sur linterface LAN. LIP est portée par le nœud actif et bascule automatiquement en cas de failover. Pour fournir une passerelle partagée aux clients, je crée une IP virtuelle (VIP) en **CARP** (Common Address Redundancy Protocol) sur linterface LAN. LIP est portée par le nœud actif et bascule automatiquement en cas de failover.
Menu : `Interfaces` > `Virtual IPs` > `Settings` : Menu : `Interfaces` > `Virtual IPs` > `Settings` :
![Création dune VIP CARP dans OPNsense](images/opnsense-vm-create-vip-carp.png) ![Create CARP virtual IP in OPNsense](img/opnsense-vm-create-vip-carp.png)
Je réplique ensuite la config depuis `System > High Availability > Status` avec le bouton `Synchronize and reconfigure all`. Je réplique ensuite la config depuis `System > High Availability > Status` avec le bouton `Synchronize and reconfigure all`.
@@ -205,7 +205,7 @@ Sur le master :
- `DHCP ranges` : cocher aussi `Disable HA sync` - `DHCP ranges` : cocher aussi `Disable HA sync`
- `DHCP options` : ajouter loption `router [3]` avec la valeur `10.102.0.1` (VIP LAN) - `DHCP options` : ajouter loption `router [3]` avec la valeur `10.102.0.1` (VIP LAN)
- `DHCP options` : cloner la règle pour `dns-server [6]` vers la même VIP. - `DHCP options` : cloner la règle pour `dns-server [6]` vers la même VIP.
![Options DHCP dans Dnsmasq](images/opnsense-vm-dnsmasq-add-option.png) ![Edit DHCP options for Dnsmasq in OPNsense](img/opnsense-vm-dnsmasq-add-option.png)
Sur le backup : Sur le backup :
- `Services` > `Dnsmasq DNS & DHCP` > `General` : cocher `Disable HA sync` - `Services` > `Dnsmasq DNS & DHCP` > `General` : cocher `Disable HA sync`
@@ -262,7 +262,7 @@ if ($type === "MASTER") {
Passons aux tests ! Passons aux tests !
OPNsense propose un _CARP Maintenance Mode_. Avec le master actif, seul lui avait son WAN monté. En activant le mode maintenance, les rôles basculent : le master devient backup, son WAN est désactivé et celui du backup est activé : OPNsense propose un _CARP Maintenance Mode_. Avec le master actif, seul lui avait son WAN monté. En activant le mode maintenance, les rôles basculent : le master devient backup, son WAN est désactivé et celui du backup est activé :
![Mode maintenance CARP dans OPNsense](images/opnsense-vm-carp-status.png) ![Mode maintenance CARP dans OPNsense](img/opnsense-vm-carp-status.png)
Pendant mes pings vers lextérieur, aucune perte de paquets au moment du basculement. Pendant mes pings vers lextérieur, aucune perte de paquets au moment du basculement.

View File

@@ -29,7 +29,7 @@ On top of my setup, my ISP modem, a *Freebox* in bridge mode, connects directly
The switch also connects my three Proxmox nodes, each on trunk ports with the same native VLAN. Every node has two NICs: one for general networking and the other dedicated to the Ceph storage network, which runs through a separate 2.5 Gbps switch. The switch also connects my three Proxmox nodes, each on trunk ports with the same native VLAN. Every node has two NICs: one for general networking and the other dedicated to the Ceph storage network, which runs through a separate 2.5 Gbps switch.
Since the OPNsense crash, Ive simplified things by removing the LACP link, it wasnt adding real value: Since the OPNsense crash, Ive simplified things by removing the LACP link, it wasnt adding real value:
![Current homelab network diagram](images/homelan-current-physical-layout.png) ![Current homelab network diagram](img/homelan-current-physical-layout.png)
Until recently, Proxmox networking on my cluster was very basic: each node was configured individually with no real overlay logic. That changed after I explored Proxmox SDN, where I centralized VLAN definitions across the cluster. I described that step in [this article]({{< ref "post/11-proxmox-cluster-networking-sdn" >}}). Until recently, Proxmox networking on my cluster was very basic: each node was configured individually with no real overlay logic. That changed after I explored Proxmox SDN, where I centralized VLAN definitions across the cluster. I described that step in [this article]({{< ref "post/11-proxmox-cluster-networking-sdn" >}}).
@@ -44,7 +44,7 @@ Time to move into the lab. Here are the main steps:
4. Configure high availability 4. Configure high availability
5. Test failover 5. Test failover
![Diagram of the POC for OPNsense high availability](images/poc-opnsense-diagram.png) ![Diagram of the POC for OPNsense high availability](img/poc-opnsense-diagram.png)
### Add VLANs in my Homelab ### Add VLANs in my Homelab
@@ -54,7 +54,7 @@ For this experiment, I create 3 new VLANs:
- **VLAN 103**: *POC pfSync* - **VLAN 103**: *POC pfSync*
In the Proxmox UI, I navigate to `Datacenter` > `SDN` > `VNets` and I click `Create`: In the Proxmox UI, I navigate to `Datacenter` > `SDN` > `VNets` and I click `Create`:
![Create POC VLANs in the Proxmox SDN](images/proxmox-sdn-create-poc-vlans.png) ![Create POC VLANs in the Proxmox SDN](img/proxmox-sdn-create-poc-vlans.png)
Once the 3 new VLAN have been created, I apply the configuration. Once the 3 new VLAN have been created, I apply the configuration.
@@ -115,7 +115,7 @@ The `fake-freebox` VM is now ready to serve DHCP on VLAN 101 and serve only one
### Build OPNsense VMs ### Build OPNsense VMs
First I download the OPNsense ISO and upload it to one of my Proxmox nodes: First I download the OPNsense ISO and upload it to one of my Proxmox nodes:
![Upload the OPNsense ISO into Proxmox](images/proxmox-upload-opnsense-iso.png) ![Upload de lISO OPNsense dans Proxmox](img/proxmox-upload-opnsense-iso.png)
#### VM Creation #### VM Creation
@@ -129,69 +129,69 @@ I create the first VM `poc-opnsense-1`, with the following settings:
1. VLAN 101 (POC WAN) 1. VLAN 101 (POC WAN)
2. VLAN 102 (POC LAN) 2. VLAN 102 (POC LAN)
3. VLAN 103 (POC pfSync) 3. VLAN 103 (POC pfSync)
![OPNsense VM settings in Proxmox](images/proxmox-create-poc-vm-opnsense.png) ![OPNsense VM settings in Proxmox](img/proxmox-create-poc-vm-opnsense.png)
Before booting it, I clone this VM to prepare the second one: `poc-opnsense-2` Before booting it, I clone this VM to prepare the second one: `poc-opnsense-2`
On first boot, I hit an “access denied” error. To fix this, I enter the BIOS, go to **Device Manager > Secure Boot Configuration**, uncheck _Attempt Secure Boot_, and restart the VM: On first boot, I hit an “access denied” error. To fix this, I enter the BIOS, go to **Device Manager > Secure Boot Configuration**, uncheck _Attempt Secure Boot_, and restart the VM:
![Disable Secure Boot in Proxmox BIOS](images/proxmox-disable-secure-boot-option.png) ![Disable Secure Boot in Proxmox BIOS](img/proxmox-disable-secure-boot-option.png)
#### OPNsense Installation #### OPNsense Installation
The VM boots on the ISO, I touch nothing until I get into the login screen: The VM boots on the ISO, I touch nothing until I get into the login screen:
![OPNsense CLI login screen in LiveCD](images/opnsense-vm-installation-welcome.png) ![OPNsense CLI login screen in LiveCD](img/opnsense-vm-installation-welcome.png)
I log in as `installer` / `opnsense` and launch the installer. I select the QEMU hard disk of 20GB as destination and launch the installation: I log in as `installer` / `opnsense` and launch the installer. I select the QEMU hard disk of 20GB as destination and launch the installation:
![OPNsense installation progress bar](images/opnsense-vm-installation-progress-bar.png) ![Barre de progression de linstallation OPNsense](img/opnsense-vm-installation-progress-bar.png)
Once the installation is finished, I remove the ISO from the drive and restart the machine. Once the installation is finished, I remove the ISO from the drive and restart the machine.
#### OPNsense Basic Configuration #### OPNsense Basic Configuration
After reboot, I log in as `root` / `opnsense` and get into the CLI menu: After reboot, I log in as `root` / `opnsense` and get into the CLI menu:
![OPNsense CLI login screen after fresh installation](images/opnsense-vm-installation-cli-menu.png) ![Menu CLI après une installation fraîche dOPNsense](img/opnsense-vm-installation-cli-menu.png)
Using option 1, I reassigned interfaces: Using option 1, I reassigned interfaces:
![OPNsense interface configuration using CLI](images/opnsense-vm-installation-assign-interfaces.png) ![Configuration des interfaces dans OPNsense via le CLI](img/opnsense-vm-installation-assign-interfaces.png)
The WAN interface successfully pulled `10.101.0.150/24` from the `fake-freebox`. I set the LAN interface to `10.102.0.2/24` and configured a DHCP pool from `10.102.0.10` to `10.102.0.99`: The WAN interface successfully pulled `10.101.0.150/24` from the `fake-freebox`. I set the LAN interface to `10.102.0.2/24` and configured a DHCP pool from `10.102.0.10` to `10.102.0.99`:
![OPNsense WAN interface getting IP from `fake-freebox` VM](images/opnsense-vm-installation-interfaces-configured.png) ![Interface WAN OPNsense recevant une IP depuis la VM `fake-freebox`](img/opnsense-vm-installation-interfaces-configured.png)
✅ The first VM is ready, I start over for the second OPNsense VM, `poc-opnsense-2` which will have the IP `10.102.0.3` ✅ The first VM is ready, I start over for the second OPNsense VM, `poc-opnsense-2` which will have the IP `10.102.0.3`
### Configure OPNsense Highly Available ### Configure OPNsense Highly Available
With both OPNsense VMs operational, its time to configure them from the WebGUI. To access the interface, I connected a Windows VM into the _POC LAN_ VLAN and browsed to the OPNsense IP on port 443: With both OPNsense VMs operational, its time to configure them from the WebGUI. To access the interface, I connected a Windows VM into the _POC LAN_ VLAN and browsed to the OPNsense IP on port 443:
![OPNsense WebGUI from Windows VM](images/opnsense-vm-webgui-from-poc-lan.png) ![OPNsense WebGUI depuis une VM Windows](img/opnsense-vm-webgui-from-poc-lan.png)
#### Add pfSync Interface #### Add pfSync Interface
The third NIC (`vtnet2`) is assigned to the _pfSync_ interface. This dedicated network allows the two firewalls to synchronize states on the VLAN *POC pfSync*: The third NIC (`vtnet2`) is assigned to the _pfSync_ interface. This dedicated network allows the two firewalls to synchronize states on the VLAN *POC pfSync*:
![Add pfSync interface in OPNsense](images/opnsense-vm-assign-pfsync-interface.png) ![Add pfSync interface in OPNsense](img/opnsense-vm-assign-pfsync-interface.png)
I enable the interface on each instance and configure it with a static IP address: I enable the interface on each instance and configure it with a static IP address:
- **poc-opnsense-1**: `10.103.0.2/24` - **poc-opnsense-1**: `10.103.0.2/24`
- **poc-opnsense-2**: `10.103.0.3/24` - **poc-opnsense-2**: `10.103.0.3/24`
Then, I add a firewall rule on each node to allow all traffic coming from this network on that *pfSync* interface: Then, I add a firewall rule on each node to allow all traffic coming from this network on that *pfSync* interface:
![Create new firewall rule on pfSync interface to allow any traffic in that network](images/opnsense-vm-firewall-allow-pfsync.png) ![Create new firewall rule on pfSync interface to allow any traffic in that network](img/opnsense-vm-firewall-allow-pfsync.png)
#### Setup High Availability #### Setup High Availability
Next, in `System` > `High Availability` > `Settings`. Next, in `System` > `High Availability` > `Settings`.
- On the master (`poc-opnsense-1`), I configure both the `General Settings` and the `Synchronization Settings`. - On the master (`poc-opnsense-1`), I configure both the `General Settings` and the `Synchronization Settings`.
- On the backup (`poc-opnsense-2`), only `General Settings` are needed, you don't want your backup overwrite the master config. - On the backup (`poc-opnsense-2`), only `General Settings` are needed, you don't want your backup overwrite the master config.
![OPNsense High Availability settings](images/opnsense-vm-high-availability-settings.png) ![OPNsense High Availability settings](img/opnsense-vm-high-availability-settings.png)
Once applied, I verify synchronization on the `Status` page: Once applied, I verify synchronization on the `Status` page:
![OPNsense High Availability status](images/opnsense-vm-high-availability-status.png) ![OPNsense High Availability status](img/opnsense-vm-high-availability-status.png)
#### Create Virtual IP Address #### Create Virtual IP Address
To provide a shared gateway for clients, I create a CARP Virtual IP (VIP) on the LAN interface. It is using the Common Address Redundancy Protocol. This IP is claimed by the active node and automatically fails over. To provide a shared gateway for clients, I create a CARP Virtual IP (VIP) on the LAN interface. It is using the Common Address Redundancy Protocol. This IP is claimed by the active node and automatically fails over.
Navigate to `Interfaces` > `Virtual IPs` > `Settings`: Navigate to `Interfaces` > `Virtual IPs` > `Settings`:
![Create CARP virtual IP in OPNsense](images/opnsense-vm-create-vip-carp.png) ![Create CARP virtual IP in OPNsense](img/opnsense-vm-create-vip-carp.png)
To replicate the config, I go to `System > High Availability > Status` and click the button next to `Synchronize and reconfigure all`. To replicate the config, I go to `System > High Availability > Status` and click the button next to `Synchronize and reconfigure all`.
@@ -206,7 +206,7 @@ On the master:
- `DHCP ranges`: also tick the `Disable HA sync` box - `DHCP ranges`: also tick the `Disable HA sync` box
- `DHCP options`: add the option `router [3]` with the value `10.102.0.1` (LAN VIP) - `DHCP options`: add the option `router [3]` with the value `10.102.0.1` (LAN VIP)
- `DHCP options`: clone the rule for `router [6]` pointing to the same VIP. - `DHCP options`: clone the rule for `router [6]` pointing to the same VIP.
![Edit DHCP options for Dnsmasq in OPNsense](images/opnsense-vm-dnsmasq-add-option.png) ![Edit DHCP options for Dnsmasq in OPNsense](img/opnsense-vm-dnsmasq-add-option.png)
On the backup: On the backup:
- `Services` > `Dnsmasq DNS & DHCP` > `General`: also tick the `Disable HA sync` box - `Services` > `Dnsmasq DNS & DHCP` > `General`: also tick the `Disable HA sync` box
@@ -264,7 +264,7 @@ if ($type === "MASTER") {
Time for the real test! Time for the real test!
OPNsense provides a _CARP Maintenance Mode_. With the master active, WAN was enabled only on that node. Entering maintenance mode flipped the roles: the master became backup, its WAN disabled, while the backup enabled its WAN: OPNsense provides a _CARP Maintenance Mode_. With the master active, WAN was enabled only on that node. Entering maintenance mode flipped the roles: the master became backup, its WAN disabled, while the backup enabled its WAN:
![OPNsense CARP maintenance mode](images/opnsense-vm-carp-status.png) ![Mode maintenance CARP dans OPNsense](img/opnsense-vm-carp-status.png)
While pinging outside the network, I observed zero packet loss during the failover. While pinging outside the network, I observed zero packet loss during the failover.

View File

@@ -84,7 +84,7 @@ Il est temps de mettre à jour, dans `System` > `Firmware` > `Status`, je v
Une fois mis à jour et redémarré, je vais dans `System` > `Firmware` > `Plugins`, je coche l'option pour afficher les plugins communautaires. J'installe que le **QEMU Guest Agent**, `os-qemu-guest-agent`, pour permettre la communication entre la VM et l'hôte Proxmox. Une fois mis à jour et redémarré, je vais dans `System` > `Firmware` > `Plugins`, je coche l'option pour afficher les plugins communautaires. J'installe que le **QEMU Guest Agent**, `os-qemu-guest-agent`, pour permettre la communication entre la VM et l'hôte Proxmox.
Cela nécessite un arrêt. Dans Proxmox, j'active le `QEMU Guest Agent` dans les options de la VM : Cela nécessite un arrêt. Dans Proxmox, j'active le `QEMU Guest Agent` dans les options de la VM :
![Options d'une VM Proxmox avec QEMU Guest Agent activé](images/proxmox-opnsense-enable-qemu-guest-agent.png) ![Options d'une VM Proxmox avec QEMU Guest Agent activé](img/proxmox-opnsense-enable-qemu-guest-agent.png)
Finalement je redémarre la VM. Une fois démarrée, depuis la WebGUI de Proxmox, je peux voir les IPs de la VM ce qui confirme que le guest agent fonctionne. Finalement je redémarre la VM. Une fois démarrée, depuis la WebGUI de Proxmox, je peux voir les IPs de la VM ce qui confirme que le guest agent fonctionne.
@@ -92,7 +92,7 @@ Finalement je redémarre la VM. Une fois démarrée, depuis la WebGUI de Proxmox
## Interfaces ## Interfaces
Sur les deux parefeu, j'assigne les NIC restantes à de nouvelles interfaces en ajoutant une description. Les VMs ont 7 interfaces, je compare attentivement les adresses MAC pour éviter de mélanger les interfaces : Sur les deux parefeu, j'assigne les NIC restantes à de nouvelles interfaces en ajoutant une description. Les VMs ont 7 interfaces, je compare attentivement les adresses MAC pour éviter de mélanger les interfaces :
![Assignement des interfaces dans OPNsense](images/opnsense-assign-interfaces.png) ![Assign interfaces menu in OPNsense](img/opnsense-assign-interfaces.png)
Au final, la configuration des interfaces ressemble à ceci : Au final, la configuration des interfaces ressemble à ceci :
@@ -160,13 +160,13 @@ La HA est configurée dans `System` > `High Availability` > `Settings`
### Statut de la HA ### Statut de la HA
Dans `System` > `High Availability` > `Status`, je peux vérifier si la synchronisation fonctionne. Sur cette page je peux répliquer un ou tous les services du master vers le nœud backup : Dans `System` > `High Availability` > `Status`, je peux vérifier si la synchronisation fonctionne. Sur cette page je peux répliquer un ou tous les services du master vers le nœud backup :
![Page de statut de la haute disponibilité dans OPNsense](images/opnsense-high-availability-status.png) ![OPNsense high availability status page](img/opnsense-high-availability-status.png)
--- ---
## IPs Virtuelles ## IPs Virtuelles
Maintenant que la HA est configurée, je peux attribuer à mes réseaux une IP virtuelle partagée entre mes nœuds. Dans `Interfaces` > `Virtual IPs` > `Settings`, je crée un VIP pour chacun de mes réseaux en utilisant **CARP** (Common Address Redundancy Protocol). L'objectif est de réutiliser les adresses IP utilisées par mon instance OPNsense actuelle, mais comme elle route encore mon réseau, j'utilise des IP différentes pour la phase de configuration : Maintenant que la HA est configurée, je peux attribuer à mes réseaux une IP virtuelle partagée entre mes nœuds. Dans `Interfaces` > `Virtual IPs` > `Settings`, je crée un VIP pour chacun de mes réseaux en utilisant **CARP** (Common Address Redundancy Protocol). L'objectif est de réutiliser les adresses IP utilisées par mon instance OPNsense actuelle, mais comme elle route encore mon réseau, j'utilise des IP différentes pour la phase de configuration :
![Liste des IPs virtuelles dans OPNsense](images/opnsense-interface-virtual-ips.png) ![Liste des IPs virtuelles dans OPNsense](img/opnsense-interface-virtual-ips.png)
OPNsense permet CARP par défaut, aucune règle de parefeu spéciale requise OPNsense permet CARP par défaut, aucune règle de parefeu spéciale requise
@@ -242,7 +242,7 @@ Pour commencer, dans `Firewall` > `Groups`, je crée 2 zones pour regrouper m
### Network Aliases ### Network Aliases
Ensuite, dans `Firewall` > `Aliases`, je crée un alias `InternalNetworks` pour regrouper tous mes réseaux internes : Ensuite, dans `Firewall` > `Aliases`, je crée un alias `InternalNetworks` pour regrouper tous mes réseaux internes :
![Création d'alias pour les réseaux locaux dansOPNsense](images/opnsense-create-alias-internalnetworks.png) ![Création d'alias pour les réseaux locaux dansOPNsense](img/opnsense-create-alias-internalnetworks.png)
### Règles de Pare-feu Rules ### Règles de Pare-feu Rules
@@ -345,17 +345,17 @@ Sur le nœud backup, je le configure de la même manière, la seule différence
### Plages DHCP ### Plages DHCP
Ensuite je configure les plages DHCP. Les deux parefeu auront des plages différentes, le nœud backup aura des plages plus petites (10 baux devraient suffire). Sur le master, elles sont configurées comme suit : Ensuite je configure les plages DHCP. Les deux parefeu auront des plages différentes, le nœud backup aura des plages plus petites (10 baux devraient suffire). Sur le master, elles sont configurées comme suit :
![Plages DHCP de Dnsmasq dans OPNsense](images/opnsense-dnsmasq-dhcp-ranges.png) ![OPNsense DHCP ranges in Dnsmasq](img/opnsense-dnsmasq-dhcp-ranges.png)
### Options DHCP ### Options DHCP
Puis je définis quelques options DHCP pour chaque domaine : le `router`, le `dns-server` et le `domain-name`. Je pointe les adresses IP vers la VIP de l'interface : Puis je définis quelques options DHCP pour chaque domaine : le `router`, le `dns-server` et le `domain-name`. Je pointe les adresses IP vers la VIP de l'interface :
![Options DHCP de Dnsmasq dans OPNsense](images/opnsense-dnsmasq-dhcp-options.png) ![OPNsense DHCP options in Dnsmasq](img/opnsense-dnsmasq-dhcp-options.png)
### Hôtes ### Hôtes
Enfin, dans l'onglet `Hosts`, je définis des mappings DHCP statiques mais aussi des IP statiques non gérées par le DHCP, pour qu'elles soient enregistrées dans le DNS : Enfin, dans l'onglet `Hosts`, je définis des mappings DHCP statiques mais aussi des IP statiques non gérées par le DHCP, pour qu'elles soient enregistrées dans le DNS :
![Hôtes DHCP de Dnsmasq dans OPNsense](images/opnsense-dnsmasq-dhcp-hosts.png) ![Hôtes DHCP de Dnsmasq dans OPNsense](img/opnsense-dnsmasq-dhcp-hosts.png)
--- ---
## DNS ## DNS
@@ -372,7 +372,7 @@ Unbound est le résolveur récursif, pour les zones locales j'effectue un forwar
### Paramètres Généraux d'Unbound ### Paramètres Généraux d'Unbound
Configurons-le, dans `Services` > `Unbound DNS` > `General` : Configurons-le, dans `Services` > `Unbound DNS` > `General` :
![Paramètres généraux d'Unbound DNS dans OPNsense](images/opnsense-unbound-general-settings.png) ![OPNsense Unbound DNS general settings](img/opnsense-unbound-general-settings.png)
### Liste de Blocage DNS ### Liste de Blocage DNS
@@ -383,7 +383,7 @@ Pour maintenir le service à jour, dans `System` > `Settings` > `Cron`, j'a
### Transfert de Requêtes ### Transfert de Requêtes
Enfin je configure le transfert de requêtes pour mes domaines locaux vers Dnsmasq. Dans `Services` > `Unbound DNS` > `Query Forwarding`, j'ajoute chacun de mes domaines locaux avec leurs reverse lookups (enregistrements PTR) : Enfin je configure le transfert de requêtes pour mes domaines locaux vers Dnsmasq. Dans `Services` > `Unbound DNS` > `Query Forwarding`, j'ajoute chacun de mes domaines locaux avec leurs reverse lookups (enregistrements PTR) :
![Configuration du transfert de requêtes d'Unbound DNS dans OPNsense](images/opnsense-unbound-dns-query-forwarding.png) ![Configuration du transfert de requêtes d'Unbound DNS dans OPNsense](img/opnsense-unbound-dns-query-forwarding.png)
--- ---
## VPN ## VPN

View File

@@ -92,7 +92,7 @@ Finally I restart the VM. Once started, from the Proxmox WebGUI, I can see the I
## Interfaces ## Interfaces
On both firewalls, I assign the remaining NICs to new interfaces adding a description. The VMs have 7 interfaces, I carefully compare MAC addresses to avoid mixing interfaces: On both firewalls, I assign the remaining NICs to new interfaces adding a description. The VMs have 7 interfaces, I carefully compare MAC addresses to avoid mixing interfaces:
![Assign interfaces menu in OPNsense](images/opnsense-assign-interfaces.png) ![Assign interfaces menu in OPNsense](img/opnsense-assign-interfaces.png)
In the end, the interfaces configuration looks like this: In the end, the interfaces configuration looks like this:
@@ -157,13 +157,13 @@ The HA is setup in `System` > `High Availability` > `Settings`
### HA Status ### HA Status
In the section `System` > `High Availability` > `Status`, I can verify if the synchronization is working. On this page I can replicate any or all services from my master to my backup node: In the section `System` > `High Availability` > `Status`, I can verify if the synchronization is working. On this page I can replicate any or all services from my master to my backup node:
![OPNsense high availability status page](images/opnsense-high-availability-status.png) ![OPNsense high availability status page](img/opnsense-high-availability-status.png)
--- ---
## Virtual IPs ## Virtual IPs
Now that HA is configured, I can give my networks a virtual IP shared across my nodes. In `Interfaces` > `Virtual IPs` > `Settings`, I create one VIP for each of my networks using **CARP** (Common Address Redundancy Protocol). The target is to reuse the IP addresses used by my current OPNsense instance, but as it is still routing my network, I use different IPs for the configuration phase: Now that HA is configured, I can give my networks a virtual IP shared across my nodes. In `Interfaces` > `Virtual IPs` > `Settings`, I create one VIP for each of my networks using **CARP** (Common Address Redundancy Protocol). The target is to reuse the IP addresses used by my current OPNsense instance, but as it is still routing my network, I use different IPs for the configuration phase:
![OPNsense virtual IPs list](images/opnsense-interface-virtual-ips.png) ![Liste des IPs virtuelles dans OPNsense](img/opnsense-interface-virtual-ips.png)
OPNsense allows CARP by default, no special firewall rule required OPNsense allows CARP by default, no special firewall rule required
@@ -239,7 +239,7 @@ To begin, in `Firewall` > `Groups`, I create 2 zones to regroup my interfaces:
### Network Aliases ### Network Aliases
Next, in `Firewall` > `Aliases`, I create an alias `InternalNetworks` to regroup all my internal networks: Next, in `Firewall` > `Aliases`, I create an alias `InternalNetworks` to regroup all my internal networks:
![OPNsense alias creation for internal networks ](images/opnsense-create-alias-internalnetworks.png) ![Création d'alias pour les réseaux locaux dansOPNsense](img/opnsense-create-alias-internalnetworks.png)
### Firewall Rules ### Firewall Rules
@@ -343,17 +343,17 @@ On the backup node, I configure it the same, the only difference will be the **D
### DHCP Ranges ### DHCP Ranges
Next I configure the DHCP ranges. Both firewalls will have different ranges, the backup node will have smaller ones (only 10 leases should be enough). On the master, they are configured as follow: Next I configure the DHCP ranges. Both firewalls will have different ranges, the backup node will have smaller ones (only 10 leases should be enough). On the master, they are configured as follow:
![OPNsense DHCP ranges in Dnsmasq](images/opnsense-dnsmasq-dhcp-ranges.png) ![OPNsense DHCP ranges in Dnsmasq](img/opnsense-dnsmasq-dhcp-ranges.png)
### DHCP Options ### DHCP Options
Then I set some DHCP options for each domain: the `router`, the `dns-server` and the `domain-name`. I'm pointing the IP addresses to the interface's VIP: Then I set some DHCP options for each domain: the `router`, the `dns-server` and the `domain-name`. I'm pointing the IP addresses to the interface's VIP:
![OPNsense DHCP options in Dnsmasq](images/opnsense-dnsmasq-dhcp-options.png) ![OPNsense DHCP options in Dnsmasq](img/opnsense-dnsmasq-dhcp-options.png)
### Hosts ### Hosts
Finally in in the `Hosts` tab, I define static DHCP mappings but also static IP not managed by the DHCP, to have them registered in the DNS: Finally in in the `Hosts` tab, I define static DHCP mappings but also static IP not managed by the DHCP, to have them registered in the DNS:
![OPNsense DHCP hosts in Dnsmasq](images/opnsense-dnsmasq-dhcp-hosts.png) ![Hôtes DHCP de Dnsmasq dans OPNsense](img/opnsense-dnsmasq-dhcp-hosts.png)
--- ---
## DNS ## DNS
@@ -370,7 +370,7 @@ Unbound is the recursive resolver, for local zones I forward queries to Dnsmasq.
### Unbound General Settings ### Unbound General Settings
Let's configure it, in `Services` > `Unbound DNS` > `General`: Let's configure it, in `Services` > `Unbound DNS` > `General`:
![OPNsense Unbound DNS general settings](images/opnsense-unbound-general-settings.png) ![OPNsense Unbound DNS general settings](img/opnsense-unbound-general-settings.png)
### DNS Blocklist ### DNS Blocklist
@@ -381,7 +381,7 @@ To maintain the service up to date, in `System` > `Settings` > `Cron`, I add my
### Query Forwarding ### Query Forwarding
Finally I configure query forwarding for my local domains to Dnsmasq. In `Services` > `Unbound DNS` > `Query Forwarding`, I add each of my local domains with their reverse lookup (PTR record): Finally I configure query forwarding for my local domains to Dnsmasq. In `Services` > `Unbound DNS` > `Query Forwarding`, I add each of my local domains with their reverse lookup (PTR record):
![OPNsense Unbound DNS query forwarding configuration](images/opnsense-unbound-dns-query-forwarding.png) ![Configuration du transfert de requêtes d'Unbound DNS dans OPNsense](img/opnsense-unbound-dns-query-forwarding.png)
--- ---
## VPN ## VPN

View File

@@ -108,7 +108,7 @@ apt full-upgrade -y
``` ```
Après la mise à niveau sur le premier nœud, la version Ceph affiche maintenant `19.2.3`, je peux voir mes OSD apparaître comme obsolètes, les moniteurs nécessitent soit une mise à niveau soit un redémarrage : Après la mise à niveau sur le premier nœud, la version Ceph affiche maintenant `19.2.3`, je peux voir mes OSD apparaître comme obsolètes, les moniteurs nécessitent soit une mise à niveau soit un redémarrage :
![État du stockage Ceph dans Proxmox après la mise à jour des paquets Ceph du premier nœud](images/proxmox-ceph-version-upgrade.png) ![Ceph storage status in Proxmox after first node Ceph package udpate](img/proxmox-ceph-version-upgrade.png)
Je poursuis et mets à niveau les paquets sur les 2 autres nœuds. Je poursuis et mets à niveau les paquets sur les 2 autres nœuds.
@@ -132,7 +132,7 @@ systemctl restart ceph-osd.target
``` ```
Je surveille le statut Ceph avec la WebGUI Proxmox. Après le redémarrage, elle affiche quelques couleurs fancy. Jattends juste que les PG redeviennent verts, cela prend moins dune minute : Je surveille le statut Ceph avec la WebGUI Proxmox. Après le redémarrage, elle affiche quelques couleurs fancy. Jattends juste que les PG redeviennent verts, cela prend moins dune minute :
![État du stockage Ceph dans Proxmox lors du premier redémarrage d'un OSD](images/proxmox-ceph-status-osd-restart.png) ![Ceph storage status in Proxmox during the first OSD restart](img/proxmox-ceph-status-osd-restart.png)
Un avertissement apparaît : `HEALTH_WARN: all OSDs are running squid or later but require_osd_release < squid` Un avertissement apparaît : `HEALTH_WARN: all OSDs are running squid or later but require_osd_release < squid`

View File

@@ -108,7 +108,7 @@ apt full-upgrade -y
``` ```
After the upgrade on the first node, the Ceph version now shows `19.2.3`, I can see my OSDs appear as outdated, the monitors need either an upgrade or a restart: After the upgrade on the first node, the Ceph version now shows `19.2.3`, I can see my OSDs appear as outdated, the monitors need either an upgrade or a restart:
![Ceph storage status in Proxmox after first node Ceph package udpate](images/proxmox-ceph-version-upgrade.png) ![Ceph storage status in Proxmox after first node Ceph package udpate](img/proxmox-ceph-version-upgrade.png)
I carry on and upgrade the packages on the 2 other nodes. I carry on and upgrade the packages on the 2 other nodes.
@@ -132,7 +132,7 @@ systemctl restart ceph-osd.target
``` ```
I monitor the Ceph status with the Proxmox WebGUI. After the restart, it is showing some fancy colors. I'm just waiting for the PGs to be back to green, it takes less than a minute: I monitor the Ceph status with the Proxmox WebGUI. After the restart, it is showing some fancy colors. I'm just waiting for the PGs to be back to green, it takes less than a minute:
![Ceph storage status in Proxmox during the first OSD restart](images/proxmox-ceph-status-osd-restart.png) ![Ceph storage status in Proxmox during the first OSD restart](img/proxmox-ceph-status-osd-restart.png)
A warning shows up: `HEALTH_WARN: all OSDs are running squid or later but require_osd_release < squid` A warning shows up: `HEALTH_WARN: all OSDs are running squid or later but require_osd_release < squid`

View File

@@ -34,12 +34,12 @@ D'abord, je configure mon réseau de couche 2 qui est géré par UniFi. Là, je
- _pfSync_ (44), communication entre mes nœuds OPNsense. - _pfSync_ (44), communication entre mes nœuds OPNsense.
Dans le contrôleur UniFi, dans `Paramètres` > `Réseaux`, j'ajoute un `New Virtual Network`. Je le nomme `WAN` et lui donne l'ID VLAN 20 : Dans le contrôleur UniFi, dans `Paramètres` > `Réseaux`, j'ajoute un `New Virtual Network`. Je le nomme `WAN` et lui donne l'ID VLAN 20 :
![Création du VLAN WAN dans le contrôleur UniFi](images/unifi-add-vlan-for-wan.png) ![Creation of the WAN VLAN in the UniFi Controller](img/unifi-add-vlan-for-wan.png)
Je fais la même chose pour le VLAN `pfSync` avec l'ID VLAN 44. Je fais la même chose pour le VLAN `pfSync` avec l'ID VLAN 44.
Je prévois de brancher ma box FAI sur le port 15 de mon switch, qui est désactivé pour l'instant. Je l'active, définis le VLAN natif sur le nouveau `WAN (20)` et désactive le trunking : Je prévois de brancher ma box FAI sur le port 15 de mon switch, qui est désactivé pour l'instant. Je l'active, définis le VLAN natif sur le nouveau `WAN (20)` et désactive le trunking :
![Configuration du port du switch UniFi pour la liaison WAN](images/unifi-enable-port-wan-vlan.png) ![Configuration du port du switch UniFi pour la liaison WAN](img/unifi-enable-port-wan-vlan.png)
Une fois ce réglage appliqué, je m'assure que seules les ports où sont connectés mes nœuds Proxmox propagent ces VLANs sur leur trunk. Une fois ce réglage appliqué, je m'assure que seules les ports où sont connectés mes nœuds Proxmox propagent ces VLANs sur leur trunk.
@@ -50,7 +50,7 @@ J'ai fini la configuration UniFi.
Maintenant que le VLAN peut atteindre mes nœuds, je veux le gérer dans le SDN de Proxmox. J'ai configuré le SDN dans [cet article]({{< ref "post/11-proxmox-cluster-networking-sdn" >}}). Maintenant que le VLAN peut atteindre mes nœuds, je veux le gérer dans le SDN de Proxmox. J'ai configuré le SDN dans [cet article]({{< ref "post/11-proxmox-cluster-networking-sdn" >}}).
Dans `Datacenter` > `SDN` > `VNets`, je crée un nouveau VNet, je l'appelle `vlan20` pour suivre ma propre convention de nommage, je lui donne l'alias _WAN_ et j'utilise le tag (ID VLAN) 20 : Dans `Datacenter` > `SDN` > `VNets`, je crée un nouveau VNet, je l'appelle `vlan20` pour suivre ma propre convention de nommage, je lui donne l'alias _WAN_ et j'utilise le tag (ID VLAN) 20 :
![Création du VNet pour le WAN dans le SDN Proxmox](images/proxmox-sdn-new-vnet-wan.png) ![Creation of the VNet for the WAN in the Proxmox SDN](img/proxmox-sdn-new-vnet-wan.png)
Je crée aussi le `vlan44` pour le VLAN _pfSync_, puis j'applique cette configuration et nous avons terminé avec le SDN. Je crée aussi le `vlan44` pour le VLAN _pfSync_, puis j'applique cette configuration et nous avons terminé avec le SDN.
@@ -75,7 +75,7 @@ La première VM s'appelle `cerbere-head1` (je ne vous l'ai pas dit ? Mon firew
6. `vlan55` _(DMZ)_ 6. `vlan55` _(DMZ)_
7. `vlan66` _(Lab)_ 7. `vlan66` _(Lab)_
![Paramètres matériels de la VM OPNsense dans Proxmox](images/proxmox-cerbere-vm-settings.png) ![Hardware settings of the OPNsense VM in Proxmox](img/proxmox-cerbere-vm-settings.png)
Maintenant je clone cette VM pour créer `cerbere-head2`, puis je procède à l'installation d'OPNsense. Je ne veux pas entrer trop dans les détails de l'installation d'OPNsense, je l'ai déjà documentée dans le [proof of concept]({{< ref "post/12-opnsense-virtualization-highly-available" >}}). Maintenant je clone cette VM pour créer `cerbere-head2`, puis je procède à l'installation d'OPNsense. Je ne veux pas entrer trop dans les détails de l'installation d'OPNsense, je l'ai déjà documentée dans le [proof of concept]({{< ref "post/12-opnsense-virtualization-highly-available" >}}).
@@ -117,7 +117,7 @@ Dans Proxmox VE 8, il était possible de créer des groupes HA, en fonction de l
Le cluster Proxmox est capable de fournir de la HA pour les ressources, mais vous devez définir les règles. Le cluster Proxmox est capable de fournir de la HA pour les ressources, mais vous devez définir les règles.
Dans `Datacenter` > `HA`, vous pouvez voir le statut et gérer les ressources. Dans le panneau `Resources` je clique sur `Add`. Je dois choisir la ressource à configurer en HA dans la liste, ici `cerbere-head1` avec l'ID 122. Puis dans l'infobulle je peux définir le maximum de redémarrages et de relocations, je laisse `Failback` activé et l'état demandé à `started` : Dans `Datacenter` > `HA`, vous pouvez voir le statut et gérer les ressources. Dans le panneau `Resources` je clique sur `Add`. Je dois choisir la ressource à configurer en HA dans la liste, ici `cerbere-head1` avec l'ID 122. Puis dans l'infobulle je peux définir le maximum de redémarrages et de relocations, je laisse `Failback` activé et l'état demandé à `started` :
![Créer une ressource HA dans Proxmox](images/proxmox-add-vm-ha.png) ![Create HA resource in Proxmox](img/proxmox-add-vm-ha.png)
Le cluster Proxmox s'assurera maintenant que cette VM est démarrée. Je fais de même pour l'autre VM OPNsense, `cerbere-head2`. Le cluster Proxmox s'assurera maintenant que cette VM est démarrée. Je fais de même pour l'autre VM OPNsense, `cerbere-head2`.
@@ -126,7 +126,7 @@ Le cluster Proxmox s'assurera maintenant que cette VM est démarrée. Je fais de
Super, mais je ne veux pas qu'elles tournent sur le même nœud. C'est là qu'intervient la nouvelle fonctionnalité des règles d'affinité HA de Proxmox VE 9. Proxmox permet de créer des règles d'affinité de nœud et de ressource. Peu m'importe sur quel nœud elles tournent, mais je ne veux pas qu'elles soient ensemble. J'ai besoin d'une règle d'affinité de ressource. Super, mais je ne veux pas qu'elles tournent sur le même nœud. C'est là qu'intervient la nouvelle fonctionnalité des règles d'affinité HA de Proxmox VE 9. Proxmox permet de créer des règles d'affinité de nœud et de ressource. Peu m'importe sur quel nœud elles tournent, mais je ne veux pas qu'elles soient ensemble. J'ai besoin d'une règle d'affinité de ressource.
Dans `Datacenter` > `HA` > `Affinity Rules`, j'ajoute une nouvelle règle d'affinité de ressource HA. Je sélectionne les deux VMs et choisis l'option `Keep Separate` : Dans `Datacenter` > `HA` > `Affinity Rules`, j'ajoute une nouvelle règle d'affinité de ressource HA. Je sélectionne les deux VMs et choisis l'option `Keep Separate` :
![Créer une affinité de ressource HA dans Proxmox](images/proxmox-ha-resource-affinity-rule.png) ![Create HA resource affinity in Proxmox](img/proxmox-ha-resource-affinity-rule.png)
✅ Mes VMs OPNsense sont maintenant entièrement prêtes ! ✅ Mes VMs OPNsense sont maintenant entièrement prêtes !
@@ -393,7 +393,7 @@ En entrant manuellement en mode maintenance CARP depuis l'interface WebGUI, aucu
Pour simuler un failover, je tue la VM OPNsense active. Ici j'observe une seule perte de paquet. Génial. Pour simuler un failover, je tue la VM OPNsense active. Ici j'observe une seule perte de paquet. Génial.
![Test de ping pendant le CARP failover d'OPNsense](images/opnsense-ping-failover.png) ![Ping test during OPNsense CARP failover](img/opnsense-ping-failover.png)
3. **Reprise après sinistre** 3. **Reprise après sinistre**

View File

@@ -33,12 +33,12 @@ First, I configure my layer 2 network which is managed by UniFi. There I need to
- *pfSync* (44), communication between my OPNsense nodes. - *pfSync* (44), communication between my OPNsense nodes.
In the UniFi controller, in `Settings` > `Networks`, I add a `New Virtual Network`. I name it `WAN` and give it the VLAN ID 20: In the UniFi controller, in `Settings` > `Networks`, I add a `New Virtual Network`. I name it `WAN` and give it the VLAN ID 20:
![Creation of the WAN VLAN in the UniFi Controller](images/unifi-add-vlan-for-wan.png) ![Creation of the WAN VLAN in the UniFi Controller](img/unifi-add-vlan-for-wan.png)
I do the same thing again for the `pfSync` VLAN with the VLAN ID 44. I do the same thing again for the `pfSync` VLAN with the VLAN ID 44.
I plan to plug my ISP box on the port 15 of my switch, which is disabled for now. I set it as active, set the native VLAN on the newly created one `WAN (20)` and disable trunking: I plan to plug my ISP box on the port 15 of my switch, which is disabled for now. I set it as active, set the native VLAN on the newly created one `WAN (20)` and disable trunking:
![Configuration of the UniFi switch port for the WAN uplink](images/unifi-enable-port-wan-vlan.png) ![Configuration du port du switch UniFi pour la liaison WAN](img/unifi-enable-port-wan-vlan.png)
Once this setting applied, I make sure that only the ports where are connected my Proxmox nodes propagate these VLAN on their trunk. Once this setting applied, I make sure that only the ports where are connected my Proxmox nodes propagate these VLAN on their trunk.
@@ -49,7 +49,7 @@ I'm done with UniFi configuration.
Now that the VLAN can reach my nodes, I want to handle it in the Proxmox SDN. I've configured the SDN in [that article]({{< ref "post/11-proxmox-cluster-networking-sdn" >}}). Now that the VLAN can reach my nodes, I want to handle it in the Proxmox SDN. I've configured the SDN in [that article]({{< ref "post/11-proxmox-cluster-networking-sdn" >}}).
In `Datacenter` > `SDN` > `VNets`, I create a new VNet, call it `vlan20` to follow my own naming convention, give it the *WAN* alias and use the tag (VLAN ID) 20: In `Datacenter` > `SDN` > `VNets`, I create a new VNet, call it `vlan20` to follow my own naming convention, give it the *WAN* alias and use the tag (VLAN ID) 20:
![Creation of the VNet for the WAN in the Proxmox SDN](images/proxmox-sdn-new-vnet-wan.png) ![Creation of the VNet for the WAN in the Proxmox SDN](img/proxmox-sdn-new-vnet-wan.png)
I also create the `vlan44` for the *pfSync* VLAN, then I apply this configuration and we are done with the SDN. I also create the `vlan44` for the *pfSync* VLAN, then I apply this configuration and we are done with the SDN.
@@ -74,7 +74,7 @@ The first VM is named `cerbere-head1` (I didn't tell you? My current firewall is
6. `vlan55` *(DMZ)* 6. `vlan55` *(DMZ)*
7. `vlan66` *(Lab)* 7. `vlan66` *(Lab)*
![Hardware settings of the OPNsense VM in Proxmox](images/proxmox-cerbere-vm-settings.png) ![Hardware settings of the OPNsense VM in Proxmox](img/proxmox-cerbere-vm-settings.png)
Now I clone that VM to create `cerbere-head2`, then I proceed with OPNsense installation. I don't want to go into much details about OPNsense installation, I already documented it in the [proof of concept]({{< ref "post/12-opnsense-virtualization-highly-available" >}}). Now I clone that VM to create `cerbere-head2`, then I proceed with OPNsense installation. I don't want to go into much details about OPNsense installation, I already documented it in the [proof of concept]({{< ref "post/12-opnsense-virtualization-highly-available" >}}).
@@ -115,7 +115,7 @@ In Proxmox VE 8, It was possible to create HA groups, depending of their resourc
The Proxmox cluster is able to provide HA for the resources, but you need to define the rules. The Proxmox cluster is able to provide HA for the resources, but you need to define the rules.
In `Datacenter` > `HA`, you can see the status and manage the resources. In the `Resources` panel I click on `Add`. I need to pick the resource to configure as HA in the list, here `cerbere-head1` with ID 122. Then in the tooltip I can define the maximum of restart and relocate, I keep `Failback` enabled and the requested state to `started`: In `Datacenter` > `HA`, you can see the status and manage the resources. In the `Resources` panel I click on `Add`. I need to pick the resource to configure as HA in the list, here `cerbere-head1` with ID 122. Then in the tooltip I can define the maximum of restart and relocate, I keep `Failback` enabled and the requested state to `started`:
![Create HA resource in Proxmox](images/proxmox-add-vm-ha.png) ![Create HA resource in Proxmox](img/proxmox-add-vm-ha.png)
The Proxmox cluster will now make sure this VM is started. I do the same for the other OPNsense VM, `cerbere-head2`. The Proxmox cluster will now make sure this VM is started. I do the same for the other OPNsense VM, `cerbere-head2`.
@@ -124,7 +124,7 @@ The Proxmox cluster will now make sure this VM is started. I do the same for the
Great, but I don't want them on the same node. This is when the new feature HA affinity rules, of Proxmox VE 9, come in. Proxmox allows to create node affinity and resource affinity rules. I don't mind on which node they run, but I don't want them together. I need a resource affinity rule. Great, but I don't want them on the same node. This is when the new feature HA affinity rules, of Proxmox VE 9, come in. Proxmox allows to create node affinity and resource affinity rules. I don't mind on which node they run, but I don't want them together. I need a resource affinity rule.
In `Datacenter` > `HA` > `Affinity Rules`, I add a new HA resource affinity rule. I select both VMs and pick the option `Keep Separate`: In `Datacenter` > `HA` > `Affinity Rules`, I add a new HA resource affinity rule. I select both VMs and pick the option `Keep Separate`:
![Create HA resource affinity in Proxmox](images/proxmox-ha-resource-affinity-rule.png) ![Create HA resource affinity in Proxmox](img/proxmox-ha-resource-affinity-rule.png)
✅ My OPNsense VMs are now fully ready! ✅ My OPNsense VMs are now fully ready!
@@ -390,7 +390,7 @@ When manually entering CARP maintenance mode from the WebGUI interface, no packe
To simulate a failover, I kill the active OPNsense VM. Here I observe only one packet dropped. Awesome. To simulate a failover, I kill the active OPNsense VM. Here I observe only one packet dropped. Awesome.
![Ping test during OPNsense CARP failover](images/opnsense-ping-failover.png) ![Ping test during OPNsense CARP failover](img/opnsense-ping-failover.png)
3. **Disaster Recovery** 3. **Disaster Recovery**

View File

@@ -109,23 +109,23 @@ Avec Semaphore en fonctionnement, faisons rapidement le tour de l'UI et connecto
## Discovery ## Discovery
Après avoir démarré la stack, je peux atteindre la page de connexion à l'URL : Après avoir démarré la stack, je peux atteindre la page de connexion à l'URL :
![Page de connexion de Semaphore UI](images/semaphore-login-page.png) ![Page de connexion de Semaphore UI](img/semaphore-login-page.png)
Pour me connecter, j'utilise les identifiants définis par `SEMAPHORE_ADMIN_NAME`/`SEMAPHORE_ADMIN_PASSWORD`. Pour me connecter, j'utilise les identifiants définis par `SEMAPHORE_ADMIN_NAME`/`SEMAPHORE_ADMIN_PASSWORD`.
Au premier accès, Semaphore me demande de créer un projet. J'ai créé le projet Homelab : Au premier accès, Semaphore me demande de créer un projet. J'ai créé le projet Homelab :
![Page de création de projet de Semaphore UI](images/semaphore-create-project.png) ![Page de création de projet de Semaphore UI](img/semaphore-create-project.png)
La première chose que je veux faire est d'ajouter mon dépôt _homelab_ (vous pouvez trouver son miroir sur Github [ici](https://github.com/Vezpi/homelab)). Dans `Repository`, je clique sur le bouton `New Repository`, et j'ajoute l'URL du repo. Je ne spécifie pas d'identifiants car le dépôt est public : La première chose que je veux faire est d'ajouter mon dépôt _homelab_ (vous pouvez trouver son miroir sur Github [ici](https://github.com/Vezpi/homelab)). Dans `Repository`, je clique sur le bouton `New Repository`, et j'ajoute l'URL du repo. Je ne spécifie pas d'identifiants car le dépôt est public :
![Page d'ajout de dépôt de Semaphore UI](images/semaphore-add-repository.png) ![Page d'ajout de dépôt de Semaphore UI](img/semaphore-add-repository.png)
Avant de continuer, je déploie 3 VM à des fins de test : `sem01`, `sem02` et `sem03`. Je les ai créées avec Terraform via [ce projet](https://github.com/Vezpi/Homelab/tree/main/terraform/projects/semaphore-vms). Avant de continuer, je déploie 3 VM à des fins de test : `sem01`, `sem02` et `sem03`. Je les ai créées avec Terraform via [ce projet](https://github.com/Vezpi/Homelab/tree/main/terraform/projects/semaphore-vms).
Pour interagir avec ces VM, je dois configurer des identifiants. Dans le `Key Store`, j'ajoute la première donnée d'identification, une clé SSH pour mon utilisateur : Pour interagir avec ces VM, je dois configurer des identifiants. Dans le `Key Store`, j'ajoute la première donnée d'identification, une clé SSH pour mon utilisateur :
![Page d'ajout d'une nouvelle clé de Semaphore UI](images/semaphore-create-new-ssh-key.png) ![Page d'ajout d'une nouvelle clé de Semaphore UI](img/semaphore-create-new-ssh-key.png)
Ensuite je crée un nouvel `Inventory`. J'utilise le format d'inventaire Ansible (le seul disponible). Je sélectionne la clé SSH créée précédemment et choisis le type `Static`. Dans les champs je renseigne les 3 hôtes créés avec leur FQDN : Ensuite je crée un nouvel `Inventory`. J'utilise le format d'inventaire Ansible (le seul disponible). Je sélectionne la clé SSH créée précédemment et choisis le type `Static`. Dans les champs je renseigne les 3 hôtes créés avec leur FQDN :
![Page de création d'un inventaire statique de Semaphore UI](images/semaphore-create-new-static-inventory.png) ![Page de création d'un inventaire statique de Semaphore UI](img/semaphore-create-new-static-inventory.png)
✅ Avec un projet, un repo, des identifiants et un inventaire en place, je peux avancer et tester l'exécution d'un playbook Ansible. ✅ Avec un projet, un repo, des identifiants et un inventaire en place, je peux avancer et tester l'exécution d'un playbook Ansible.
@@ -172,20 +172,20 @@ Je veux tester quelque chose de simple : installer un serveur web avec une page
``` ```
Dans Semaphore UI, je peux maintenant créer mon premier `Task Template` pour un playbook Ansible. Je lui donne un nom, le chemin du playbook (depuis le dossier racine du repo), le dépôt et sa branche : Dans Semaphore UI, je peux maintenant créer mon premier `Task Template` pour un playbook Ansible. Je lui donne un nom, le chemin du playbook (depuis le dossier racine du repo), le dépôt et sa branche :
![Nouveau template de tâche Ansible dans Semaphore UI](images/semaphore-create-new-ansible-task-template.png) ![Nouveau template de tâche Ansible dans Semaphore UI](img/semaphore-create-new-ansible-task-template.png)
Il est temps de lancer le playbook ! Dans la liste des task templates, je clique sur le bouton ▶️ : Il est temps de lancer le playbook ! Dans la liste des task templates, je clique sur le bouton ▶️ :
![Lancement du template de tâche Ansible dans Semaphore UI](images/semaphore-run-test-playbook.png) ![Lancement du template de tâche Ansible dans Semaphore UI](img/semaphore-run-test-playbook.png)
Le playbook se lance et je peux suivre la sortie en temps réel : Le playbook se lance et je peux suivre la sortie en temps réel :
![Sortie de la tâche Ansible dans Semaphore UI](images/semaphore-ui-ansible-task-output.png) ![Semaphore UI Ansible task output](img/semaphore-ui-ansible-task-output.png)
Je peux aussi consulter les exécutions précédentes : Je peux aussi consulter les exécutions précédentes :
![Liste des exécutions de tâches dans Semaphore UI](images/semaphore-ui-task-template-run-list.png) ![Liste des exécutions de tâches dans Semaphore UI](img/semaphore-ui-task-template-run-list.png)
✅ Enfin, je peux confirmer que le travail est fini en vérifiant l'URL sur le port 80 (http) : ✅ Enfin, je peux confirmer que le travail est fini en vérifiant l'URL sur le port 80 (http) :
![Test de l'URL après application du playbook sur les hôtes](images/semaphore-ui-test-nginx-page-playbook.png) ![Test de l'URL après application du playbook sur les hôtes](img/semaphore-ui-test-nginx-page-playbook.png)
Gérer des playbooks Ansible dans Semaphore UI est assez simple et vraiment pratique. L'interface est très soignée. Gérer des playbooks Ansible dans Semaphore UI est assez simple et vraiment pratique. L'interface est très soignée.
@@ -233,19 +233,19 @@ Avec cela en place, le playbook a réussi et j'ai pu créer l'utilisateur :
``` ```
Ensuite je crée un variable group `pve_vm`. Un variable group me permet de définir plusieurs variables et secrets ensemble : Ensuite je crée un variable group `pve_vm`. Un variable group me permet de définir plusieurs variables et secrets ensemble :
![Nouveau groupe de variables dans Semaphore UI](images/semaphore-ui-create-variable-group.png) ![Nouveau groupe de variables dans Semaphore UI](img/semaphore-ui-create-variable-group.png)
Puis je crée un nouveau task template, cette fois de type Terraform Code. Je lui donne un nom, le chemin du projet Terraform, un workspace, le dépôt avec sa branche et le variable group : Puis je crée un nouveau task template, cette fois de type Terraform Code. Je lui donne un nom, le chemin du projet Terraform, un workspace, le dépôt avec sa branche et le variable group :
![Nouveau template de tâche Terraform dans Semaphore UI](images/semaphore-task-template-terraform.png) ![Nouveau template de tâche Terraform dans Semaphore UI](img/semaphore-task-template-terraform.png)
Lancer le template me donne quelques options supplémentaires liées à Terraform : Lancer le template me donne quelques options supplémentaires liées à Terraform :
![Options d'exécution Terraform dans Semaphore UI](images/semaphore-running-terraform-code-options.png) ![Options d'exécution Terraform dans Semaphore UI](img/semaphore-running-terraform-code-options.png)
Après le plan Terraform, il me propose d'appliquer, d'annuler ou d'arrêter : Après le plan Terraform, il me propose d'appliquer, d'annuler ou d'arrêter :
![Plan Terraform dans Semaphore UI](images/semaphore-terraform-task-working.png) ![Plan Terraform dans Semaphore UI](img/semaphore-terraform-task-working.png)
Enfin, après avoir cliqué sur ✅ pour appliquer, j'ai pu regarder Terraform construire les VM, comme avec le CLI. À la fin, les VM ont été déployées avec succès sur Proxmox : Enfin, après avoir cliqué sur ✅ pour appliquer, j'ai pu regarder Terraform construire les VM, comme avec le CLI. À la fin, les VM ont été déployées avec succès sur Proxmox :
![Déploiement Terraform terminé dans Semaphore UI](images/semaphore-ui-deploy-with-terraform.png) ![Déploiement Terraform terminé dans Semaphore UI](img/semaphore-ui-deploy-with-terraform.png)
--- ---
## Conclusion ## Conclusion

View File

@@ -109,23 +109,23 @@ With Semaphore running, lets take a quick tour of the UI and wire it up to a
## Discovery ## Discovery
After starting the stack, I can reach the login page at the URL: After starting the stack, I can reach the login page at the URL:
![Semaphore UI login page](images/semaphore-login-page.png) ![Page de connexion de Semaphore UI](img/semaphore-login-page.png)
To log in, I use the credentials defined by `SEMAPHORE_ADMIN_NAME`/`SEMAPHORE_ADMIN_PASSWORD`. To log in, I use the credentials defined by `SEMAPHORE_ADMIN_NAME`/`SEMAPHORE_ADMIN_PASSWORD`.
On first login, Semaphore prompt me to create a project. I created the Homelab project: On first login, Semaphore prompt me to create a project. I created the Homelab project:
![Semaphore UI new project page](images/semaphore-create-project.png) ![Page de création de projet de Semaphore UI](img/semaphore-create-project.png)
The first thing I want to do is to add my *homelab* repository (you can find its mirror on Github [here](https://github.com/Vezpi/homelab)). In `Repository`, I click the `New Repository` button, and add the repo URL. I don't specify credentials because the repo is public: The first thing I want to do is to add my *homelab* repository (you can find its mirror on Github [here](https://github.com/Vezpi/homelab)). In `Repository`, I click the `New Repository` button, and add the repo URL. I don't specify credentials because the repo is public:
![Semaphore UI new repository page](images/semaphore-add-repository.png) ![Page d'ajout de dépôt de Semaphore UI](img/semaphore-add-repository.png)
Before continue, I deploy 3 VMs for testing purpose: `sem01`, `sem02` and `sem03`. I created them using Terraform with [this project](https://github.com/Vezpi/Homelab/tree/main/terraform/projects/semaphore-vms). Before continue, I deploy 3 VMs for testing purpose: `sem01`, `sem02` and `sem03`. I created them using Terraform with [this project](https://github.com/Vezpi/Homelab/tree/main/terraform/projects/semaphore-vms).
To interact with these VMs I need to configure credentials. In the the `Key Store`, I add the first credential, a SSH key for my user: To interact with these VMs I need to configure credentials. In the the `Key Store`, I add the first credential, a SSH key for my user:
![Semaphore UI new key page](images/semaphore-create-new-ssh-key.png) ![Page d'ajout d'une nouvelle clé de Semaphore UI](img/semaphore-create-new-ssh-key.png)
Then I create a new `Inventory`. I'm using the Ansible inventory format (the only one available). I select the SSH key previously created and select the type as `Static`. In the fields I enter the 3 hosts created with their FQDN: Then I create a new `Inventory`. I'm using the Ansible inventory format (the only one available). I select the SSH key previously created and select the type as `Static`. In the fields I enter the 3 hosts created with their FQDN:
![Semaphore UI new inventory page](images/semaphore-create-new-static-inventory.png) ![Page de création d'un inventaire statique de Semaphore UI](img/semaphore-create-new-static-inventory.png)
✅ With a project, repo, credentials, and inventory in place, I can move forward and test to run an Ansible playbook. ✅ With a project, repo, credentials, and inventory in place, I can move forward and test to run an Ansible playbook.
@@ -172,20 +172,20 @@ I want to test something simple, install a web server with a custom page on thes
``` ```
In Semaphore UI, I can now create my first `Task Template` for Ansible playbook. I give it a name, the playbook path (from the root folder of the repo), the repository and its branch: In Semaphore UI, I can now create my first `Task Template` for Ansible playbook. I give it a name, the playbook path (from the root folder of the repo), the repository and its branch:
![Semaphore UI new Ansible task template](images/semaphore-create-new-ansible-task-template.png) ![Nouveau template de tâche Ansible dans Semaphore UI](img/semaphore-create-new-ansible-task-template.png)
Time to launch the playbook! In the task templates list, I click on the ▶️ button: Time to launch the playbook! In the task templates list, I click on the ▶️ button:
![Semaphore UI launch Ansible task template](images/semaphore-run-test-playbook.png) ![Lancement du template de tâche Ansible dans Semaphore UI](img/semaphore-run-test-playbook.png)
The playbook launches and I can follow the output in real time: The playbook launches and I can follow the output in real time:
![Semaphore UI Ansible task output](images/semaphore-ui-ansible-task-output.png) ![Semaphore UI Ansible task output](img/semaphore-ui-ansible-task-output.png)
I can also review previous runs: I can also review previous runs:
![Semaphore UI tasks runs list](images/semaphore-ui-task-template-run-list.png) ![Liste des exécutions de tâches dans Semaphore UI](img/semaphore-ui-task-template-run-list.png)
✅ Finally I can confirm the job is done by checking the URL on port 80 (http): ✅ Finally I can confirm the job is done by checking the URL on port 80 (http):
![Testing URL after applying playbook on hosts ](images/semaphore-ui-test-nginx-page-playbook.png) ![Test de l'URL après application du playbook sur les hôtes](img/semaphore-ui-test-nginx-page-playbook.png)
Managing Ansible playbooks in Semaphore UI is pretty simple and really convenient. The interface is really sleek. Managing Ansible playbooks in Semaphore UI is pretty simple and really convenient. The interface is really sleek.
@@ -233,19 +233,19 @@ With that in place, the playbook succeeded and I could create the user:
``` ```
Next I create a variable group `pve_vm`. A variable group let me define multiple variables and secrets together: Next I create a variable group `pve_vm`. A variable group let me define multiple variables and secrets together:
![Semaphore UI new variable group](images/semaphore-ui-create-variable-group.png) ![Nouveau groupe de variables dans Semaphore UI](img/semaphore-ui-create-variable-group.png)
Then I create a new task template, this time with the kind Terraform Code. I give it a name, the path of the terraform [project](https://github.com/Vezpi/Homelab/tree/main/terraform/projects/semaphore-vms), a workspace, the repository along with its branch and. the variable group: Then I create a new task template, this time with the kind Terraform Code. I give it a name, the path of the terraform [project](https://github.com/Vezpi/Homelab/tree/main/terraform/projects/semaphore-vms), a workspace, the repository along with its branch and. the variable group:
![Semaphore UI new Terraform task template](images/semaphore-task-template-terraform.png) ![Nouveau template de tâche Terraform dans Semaphore UI](img/semaphore-task-template-terraform.png)
Running the template gives me some additional options related to Terraform: Running the template gives me some additional options related to Terraform:
![Semaphore UI run Terraform task](images/semaphore-running-terraform-code-options.png) ![Options d'exécution Terraform dans Semaphore UI](img/semaphore-running-terraform-code-options.png)
After the Terraform plan, I'm proposed to apply, cancel or stop: After the Terraform plan, I'm proposed to apply, cancel or stop:
![Semaphore UI task Terraform plan](images/semaphore-terraform-task-working.png) ![Plan Terraform dans Semaphore UI](img/semaphore-terraform-task-working.png)
Finally after hitting ✅ to apply, I could watch Terraform build the VMs, just like using the CLI. At the end, the VMs were successfully deployed on Proxmox: Finally after hitting ✅ to apply, I could watch Terraform build the VMs, just like using the CLI. At the end, the VMs were successfully deployed on Proxmox:
![Semaphore UI Terraform deploy complete](images/semaphore-ui-deploy-with-terraform.png) ![Déploiement Terraform terminé dans Semaphore UI](img/semaphore-ui-deploy-with-terraform.png)
--- ---
## Conclusion ## Conclusion

View File

@@ -68,7 +68,7 @@ J'ai considéré FreeNAS/TrueNAS, OpenMediaVault et Unraid. J'ai choisi TrueNAS
L'installation ne s'est pas déroulée aussi bien que prévu... L'installation ne s'est pas déroulée aussi bien que prévu...
J'utilise [Ventoy](https://www.ventoy.net/en/index.html) pour garder plusieurs ISOs sur une clé USB. J'étais en version 1.0.99, et l'ISO ne se lançait pas. La mise à jour vers 1.1.10 a résolu le problème : J'utilise [Ventoy](https://www.ventoy.net/en/index.html) pour garder plusieurs ISOs sur une clé USB. J'étais en version 1.0.99, et l'ISO ne se lançait pas. La mise à jour vers 1.1.10 a résolu le problème :
![Écran d'accueil d'installation de TrueNAS](images/truenas-iso-installation-splash.png) ![TrueNAS installation splash screen](img/truenas-iso-installation-splash.png)
Mais là j'ai rencontré un autre problème lors du lancement de l'installation sur mon périphérique de stockage eMMC : Mais là j'ai rencontré un autre problème lors du lancement de l'installation sur mon périphérique de stockage eMMC :
``` ```
@@ -77,16 +77,16 @@ Failed to find partition number 2 on mmcblk0
J'ai trouvé une solution sur ce [post](https://forums.truenas.com/t/installation-failed-on-emmc-odroid-h4/15317/12) : J'ai trouvé une solution sur ce [post](https://forums.truenas.com/t/installation-failed-on-emmc-odroid-h4/15317/12) :
- Entrer dans le shell - Entrer dans le shell
![Entrer dans le shell de l'installateur TrueNAS](images/truenas-iso-enter-shell.png) ![Enter the shell in TrueNAS installer](img/truenas-iso-enter-shell.png)
- Éditer le fichier `/lib/python3/dist-packages/truenas_installer/utils.py` - Éditer le fichier `/lib/python3/dist-packages/truenas_installer/utils.py`
- Déplacer la ligne `await asyncio.sleep(1)` juste sous `for _try in range(tries):` - Déplacer la ligne `await asyncio.sleep(1)` juste sous `for _try in range(tries):`
- Modifier la ligne 46 pour ajouter `+ 'p'` : - Modifier la ligne 46 pour ajouter `+ 'p'` :
`for partdir in filter(lambda x: x.is_dir() and x.name.startswith(device + 'p'), dir_contents):` `for partdir in filter(lambda x: x.is_dir() and x.name.startswith(device + 'p'), dir_contents):`
![Fichier corrigé dans l'installateur TrueNAS](images/truenas-iso-fix-installer.png) ![Fichier corrigé dans l'installateur TrueNAS](img/truenas-iso-fix-installer.png)
- Quitter le shell et lancer l'installation sans redémarrer - Quitter le shell et lancer l'installation sans redémarrer
L'installateur a finalement pu passer : L'installateur a finalement pu passer :
![Progression de l'installation de TrueNAS](images/truenas-iso-installation.png) ![Progression de l'installation de TrueNAS](img/truenas-iso-installation.png)
Une fois l'installation terminée, j'ai éteint la machine. Ensuite je l'ai installée dans mon rack au-dessus des 3 nœuds Proxmox VE. J'ai branché les deux câbles Ethernet depuis mon switch et je l'ai mise sous tension. Une fois l'installation terminée, j'ai éteint la machine. Ensuite je l'ai installée dans mon rack au-dessus des 3 nœuds Proxmox VE. J'ai branché les deux câbles Ethernet depuis mon switch et je l'ai mise sous tension.
@@ -99,18 +99,18 @@ Par défaut, TrueNAS utilise DHCP. J'ai trouvé son adresse MAC dans mon interfa
### Paramètres généraux ### Paramètres généraux
Pendant l'installation, je n'ai pas défini de mot de passe pour truenas_admin. La page de connexion m'a forcé à en choisir un : Pendant l'installation, je n'ai pas défini de mot de passe pour truenas_admin. La page de connexion m'a forcé à en choisir un :
![Page de connexion TrueNAS pour changer le mot de passe de `truenas_admin`](images/truenas-login-page-change-password.png) ![Page de connexion TrueNAS pour changer le mot de passe de `truenas_admin`](img/truenas-login-page-change-password.png)
Une fois le mot de passe mis à jour, j'arrive sur le tableau de bord. L'interface donne une bonne impression au premier abord : Une fois le mot de passe mis à jour, j'arrive sur le tableau de bord. L'interface donne une bonne impression au premier abord :
![Tableau de bord de TrueNAS](images/truenas-fresh-install-dashboard.png) ![Tableau de bord de TrueNAS](img/truenas-fresh-install-dashboard.png)
J'explore rapidement l'interface, la première chose que je fais est de changer le hostname en `granite` et de cocher la case en dessous pour hériter du domaine depuis DHCP : J'explore rapidement l'interface, la première chose que je fais est de changer le hostname en `granite` et de cocher la case en dessous pour hériter du domaine depuis DHCP :
![Configuration du hostname dans TrueNAS](images/truenas-config-change-hostname.png) ![Configuration du hostname dans TrueNAS](img/truenas-config-change-hostname.png)
Dans les `General Settings`, je change les paramètres de `Localization`. Je mets le Console Keyboard Map sur `French (AZERTY)` et le Fuseau horaire sur `Europe/Paris`. Dans les `General Settings`, je change les paramètres de `Localization`. Je mets le Console Keyboard Map sur `French (AZERTY)` et le Fuseau horaire sur `Europe/Paris`.
Je crée un nouvel utilisateur `vez`, avec le rôle `Full Admin` dans TrueNAS. J'autorise SSH uniquement pour l'authentification par clé, pas de mots de passe : Je crée un nouvel utilisateur `vez`, avec le rôle `Full Admin` dans TrueNAS. J'autorise SSH uniquement pour l'authentification par clé, pas de mots de passe :
![Création d'un utilisateur dans TrueNAS](images/truenas-create-new-user.png) ![Création d'un utilisateur dans TrueNAS](img/truenas-create-new-user.png)
Finalement je retire le rôle admin de `truenas_admin` et verrouille le compte. Finalement je retire le rôle admin de `truenas_admin` et verrouille le compte.
@@ -119,16 +119,16 @@ Finalement je retire le rôle admin de `truenas_admin` et verrouille le compte
Dans TrueNAS, un pool est une collection de stockage créée en combinant plusieurs disques en un espace unifié géré par ZFS. Dans TrueNAS, un pool est une collection de stockage créée en combinant plusieurs disques en un espace unifié géré par ZFS.
Dans la page `Storage`, je trouve mes `Disks`, où je peux confirmer que TrueNAS voit mon couple de NVMe : Dans la page `Storage`, je trouve mes `Disks`, où je peux confirmer que TrueNAS voit mon couple de NVMe :
![Liste des disques disponibles dans TrueNAS](images/truenas-storage-disks-unconfigured.png) ![List of available disks in TrueNAS](img/truenas-storage-disks-unconfigured.png)
De retour sur le `Storage Dashboard`, je clique sur le bouton `Create Pool`. Je nomme le pool `storage` parce que je suis vraiment inspiré pour lui donner un nom : De retour sur le `Storage Dashboard`, je clique sur le bouton `Create Pool`. Je nomme le pool `storage` parce que je suis vraiment inspiré pour lui donner un nom :
![Assistant de création de pool dans TrueNAS](images/truenas-pool-creation-general.png) ![Assistant de création de pool dans TrueNAS](img/truenas-pool-creation-general.png)
Puis je sélectionne la disposition `Mirror` : Puis je sélectionne la disposition `Mirror` :
![Sélection de la disposition des disques dans l'assistant de création de pool dans TrueNAS](images/truenas-pool-creation-layout.png) ![Disk layout selection in the pool creation wizard in TrueNAS](img/truenas-pool-creation-layout.png)
J'explore rapidement les configurations optionnelles, mais les valeurs par défaut me conviennent : autotrim, compression, pas de dedup, etc. À la fin, avant de créer le pool, il y a une section `Review` : J'explore rapidement les configurations optionnelles, mais les valeurs par défaut me conviennent : autotrim, compression, pas de dedup, etc. À la fin, avant de créer le pool, il y a une section `Review` :
![Section de révision de l'assistant de création de pool dans TrueNAS](images/truenas-pool-creation-review.png) ![Review section of the pool creation wizard in TrueNAS](img/truenas-pool-creation-review.png)
Après avoir cliqué sur `Create Pool`, on m'avertit que tout sur les disques sera effacé, ce que je confirme. Finalement le pool est créé. Après avoir cliqué sur `Create Pool`, on m'avertit que tout sur les disques sera effacé, ce que je confirme. Finalement le pool est créé.
@@ -139,10 +139,10 @@ Un dataset est un système de fichiers à l'intérieur d'un pool. Il peut conten
#### Partage SMB #### Partage SMB
Créons maintenant mon premier dataset `files` pour partager des fichiers sur le réseau pour mes clients Windows, comme des ISOs, etc : Créons maintenant mon premier dataset `files` pour partager des fichiers sur le réseau pour mes clients Windows, comme des ISOs, etc :
![Créer un dataset dans TrueNAS](images/truenas-create-dataset-files.png) ![Create a dataset in TrueNAS](img/truenas-create-dataset-files.png)
Lors de la création de datasets SMB dans SCALE, définissez le Share Type sur SMB afin que les bons ACL/xattr par défaut s'appliquent. TrueNAS me demande alors de démarrer/activer le service SMB : Lors de la création de datasets SMB dans SCALE, définissez le Share Type sur SMB afin que les bons ACL/xattr par défaut s'appliquent. TrueNAS me demande alors de démarrer/activer le service SMB :
![Invite à démarrer le service SMB dans TrueNAS](images/truenas-start-smb-service.png) ![Invite à démarrer le service SMB dans TrueNAS](img/truenas-start-smb-service.png)
Depuis mon portable Windows, j'essaie d'accéder à mon nouveau partage `\\granite.mgmt.vezpi.com\files`. Comme prévu on me demande des identifiants. Depuis mon portable Windows, j'essaie d'accéder à mon nouveau partage `\\granite.mgmt.vezpi.com\files`. Comme prévu on me demande des identifiants.
@@ -157,7 +157,7 @@ Je crée un autre dataset : `media`, et un enfant `photos`. Je crée un partag
Sur mon serveur NFS actuel, les fichiers photos sont possédés par `root` (gérés par _Immich_). Plus tard je verrai comment migrer vers une version sans root. Sur mon serveur NFS actuel, les fichiers photos sont possédés par `root` (gérés par _Immich_). Plus tard je verrai comment migrer vers une version sans root.
⚠️ Pour l'instant je définis, dans les `Advanced Options`, le `Maproot User` et le `Maproot Group` sur `root`. Cela équivaut à l'attribut NFS `no_squash_root`, le `root` local du client reste `root` sur le serveur, ne faites pas ça : ⚠️ Pour l'instant je définis, dans les `Advanced Options`, le `Maproot User` et le `Maproot Group` sur `root`. Cela équivaut à l'attribut NFS `no_squash_root`, le `root` local du client reste `root` sur le serveur, ne faites pas ça :
![Permissions du partage NFS dans TrueNAS](images/truenas-dataset-photos-nfs-share.png) ![NFS share permission in TrueNAS](img/truenas-dataset-photos-nfs-share.png)
✅ Je monte le partage NFS sur un client, cela fonctionne bien. ✅ Je monte le partage NFS sur un client, cela fonctionne bien.
@@ -178,14 +178,14 @@ J'ai mentionné les capacités VM dans mes exigences. Je ne couvrirais pas cela
### Protection des données ### Protection des données
Il est maintenant temps d'activer quelques fonctionnalités de protection des données : Il est maintenant temps d'activer quelques fonctionnalités de protection des données :
![Fonctionnalités de protection des données dans TrueNAS](images/truenas-data-protection-tab.png) ![Data protection features in TrueNAS](img/truenas-data-protection-tab.png)
Je veux créer des snapshots automatiques pour certains de mes datasets, ceux qui me tiennent le plus à cœur : mes fichiers cloud et les photos. Je veux créer des snapshots automatiques pour certains de mes datasets, ceux qui me tiennent le plus à cœur : mes fichiers cloud et les photos.
Créons des tâches de snapshot. Je clique sur le bouton `Add` à côté de `Periodic Snapshot Tasks` : Créons des tâches de snapshot. Je clique sur le bouton `Add` à côté de `Periodic Snapshot Tasks` :
- cloud : snapshots quotidiens, conserver pendant 2 mois - cloud : snapshots quotidiens, conserver pendant 2 mois
- photos : snapshots quotidiens, conserver pendant 7 jours - photos : snapshots quotidiens, conserver pendant 7 jours
![Créer une tâche de snapshot périodique dans TrueNAS](images/truenas-create-periodic-snapshot.png) ![Create periodic snapshot task in TrueNAS ](img/truenas-create-periodic-snapshot.png)
Je pourrais aussi configurer une `Cloud Sync Task`, mais Duplicati gère déjà les sauvegardes hors site. Je pourrais aussi configurer une `Cloud Sync Task`, mais Duplicati gère déjà les sauvegardes hors site.
@@ -204,12 +204,12 @@ sudo rsync -a --info=progress2 /data/photo/ /new_photos
``` ```
À la fin, je pourrais décommissionner mon ancien serveur NFS sur le LXC. La disposition des datasets après migration ressemble à ceci : À la fin, je pourrais décommissionner mon ancien serveur NFS sur le LXC. La disposition des datasets après migration ressemble à ceci :
![Disposition des datasets dans TrueNAS](images/truenas-datasets-layout.png) ![Dataset layout in TrueNAS](img/truenas-datasets-layout.png)
### Application Android ### Application Android
Par curiosité, j'ai cherché sur le Play Store une application pour gérer une instance TrueNAS. J'ai trouvé [Nasdeck](https://play.google.com/store/apps/details?id=com.strtechllc.nasdeck&hl=fr&pli=1), qui est plutôt sympa. Voici quelques captures d'écran : Par curiosité, j'ai cherché sur le Play Store une application pour gérer une instance TrueNAS. J'ai trouvé [Nasdeck](https://play.google.com/store/apps/details?id=com.strtechllc.nasdeck&hl=fr&pli=1), qui est plutôt sympa. Voici quelques captures d'écran :
![Captures d'écran de l'application Nasdeck](images/nasdeck-android-app.png) ![Captures d'écran de l'application Nasdeck](img/nasdeck-android-app.png)
--- ---
## Conclusion ## Conclusion

View File

@@ -67,7 +67,7 @@ I considered FreeNAS/TrueNAS, OpenMediaVault, and Unraid. I chose TrueNAS SCALE
The install didnt go as smoothly as expected... The install didnt go as smoothly as expected...
I use [Ventoy](https://www.ventoy.net/en/index.html) to keep multiple ISOs on one USB stick. I was in version 1.0.99, and the ISO wouldn't launch. Updating to 1.1.10 fixed it: I use [Ventoy](https://www.ventoy.net/en/index.html) to keep multiple ISOs on one USB stick. I was in version 1.0.99, and the ISO wouldn't launch. Updating to 1.1.10 fixed it:
![TrueNAS installation splash screen](images/truenas-iso-installation-splash.png) ![TrueNAS installation splash screen](img/truenas-iso-installation-splash.png)
But here I encountered another problem when launching the installation on my eMMC storage device: But here I encountered another problem when launching the installation on my eMMC storage device:
``` ```
@@ -76,16 +76,16 @@ Failed to find partition number 2 on mmcblk0
I found a solution on this [post](https://forums.truenas.com/t/installation-failed-on-emmc-odroid-h4/15317/12): I found a solution on this [post](https://forums.truenas.com/t/installation-failed-on-emmc-odroid-h4/15317/12):
- Enter the shell - Enter the shell
![Enter the shell in TrueNAS installer](images/truenas-iso-enter-shell.png) ![Enter the shell in TrueNAS installer](img/truenas-iso-enter-shell.png)
- Edit the file `/lib/python3/dist-packages/truenas_installer/utils.py` - Edit the file `/lib/python3/dist-packages/truenas_installer/utils.py`
- Move the line `await asyncio.sleep(1)` right beneath `for _try in range(tries):` - Move the line `await asyncio.sleep(1)` right beneath `for _try in range(tries):`
- Edit line 46 to add `+ 'p'`: - Edit line 46 to add `+ 'p'`:
`for partdir in filter(lambda x: x.is_dir() and x.name.startswith(device + 'p'), dir_contents):` `for partdir in filter(lambda x: x.is_dir() and x.name.startswith(device + 'p'), dir_contents):`
![Fixed file in the TrueNAS installer](images/truenas-iso-fix-installer.png) ![Fichier corrigé dans l'installateur TrueNAS](img/truenas-iso-fix-installer.png)
- Exit the shell and start the installation without reboot - Exit the shell and start the installation without reboot
The installer was finally able to get through: The installer was finally able to get through:
![TrueNAS installation progress](images/truenas-iso-installation.png) ![Progression de l'installation de TrueNAS](img/truenas-iso-installation.png)
Once the installation was complete, I shut down the machine. Then I installed it into my rack on top of the 3 Proxmox VE nodes. I plugged both Ethernet cables from my switch and powered it up. Once the installation was complete, I shut down the machine. Then I installed it into my rack on top of the 3 Proxmox VE nodes. I plugged both Ethernet cables from my switch and powered it up.
@@ -97,18 +97,18 @@ By default, TrueNAS uses DHCP. I found its MAC address in my UniFi interface and
### General Settings ### General Settings
During install, I didnt set a password for truenas_admin. The login page forced me to pick one: During install, I didnt set a password for truenas_admin. The login page forced me to pick one:
![TrueNAS login page to change `truenas_admin` password](images/truenas-login-page-change-password.png) ![Page de connexion TrueNAS pour changer le mot de passe de `truenas_admin`](img/truenas-login-page-change-password.png)
Once the password is updated, I land on the dashboard. The UI feels great at first glance: Once the password is updated, I land on the dashboard. The UI feels great at first glance:
![TrueNAS dashboard](images/truenas-fresh-install-dashboard.png) ![Tableau de bord de TrueNAS](img/truenas-fresh-install-dashboard.png)
I quickly explore the interface, the first thing I do is changing the hostname to `granite` and check the box below et it inherit domain from DHCP: I quickly explore the interface, the first thing I do is changing the hostname to `granite` and check the box below et it inherit domain from DHCP:
![TrueNAS hostname configuration](images/truenas-config-change-hostname.png) ![Configuration du hostname dans TrueNAS](img/truenas-config-change-hostname.png)
In the `General Settings`, I change the `Localization` settings. I set the Console Keyboard Map to `French (AZERTY)` and the Timezone to `Europe/Paris`. In the `General Settings`, I change the `Localization` settings. I set the Console Keyboard Map to `French (AZERTY)` and the Timezone to `Europe/Paris`.
I create a new user `vez`, with `Full Admin` role within TrueNAS. I allow SSH for keybased auth only, no passwords: I create a new user `vez`, with `Full Admin` role within TrueNAS. I allow SSH for keybased auth only, no passwords:
![TrueNAS user creation](images/truenas-create-new-user.png) ![Création d'un utilisateur dans TrueNAS](img/truenas-create-new-user.png)
Finally I remove the admin role from `truenas_admin` and lock the account. Finally I remove the admin role from `truenas_admin` and lock the account.
@@ -117,16 +117,16 @@ Finally I remove the admin role from `truenas_admin` and lock the account.
In TrueNAS, a pool is a storage collection created by combining multiple disks into a unified ZFSmanaged space. In TrueNAS, a pool is a storage collection created by combining multiple disks into a unified ZFSmanaged space.
In the `Storage` page, I can find my `Disks`, where I can confirm TrueNAS can see my couple of NVMe drives: In the `Storage` page, I can find my `Disks`, where I can confirm TrueNAS can see my couple of NVMe drives:
![List of available disks in TrueNAS](images/truenas-storage-disks-unconfigured.png) ![List of available disks in TrueNAS](img/truenas-storage-disks-unconfigured.png)
Back in the `Storage Dashboard`, I click the `Create Pool` button. I name the pool `storage` because I'm really inspired to give it a name: Back in the `Storage Dashboard`, I click the `Create Pool` button. I name the pool `storage` because I'm really inspired to give it a name:
![Pool creation wizard in TrueNAS](images/truenas-pool-creation-general.png) ![Assistant de création de pool dans TrueNAS](img/truenas-pool-creation-general.png)
Then I select the `Mirror` layout: Then I select the `Mirror` layout:
![Disk layout selection in the pool creation wizard in TrueNAS](images/truenas-pool-creation-layout.png) ![Disk layout selection in the pool creation wizard in TrueNAS](img/truenas-pool-creation-layout.png)
I explore quickly the optional configurations, but the defaults are fine to me: autotrim, compression, no dedup, etc. At the end, before creating the pool, there is a `Review` section: I explore quickly the optional configurations, but the defaults are fine to me: autotrim, compression, no dedup, etc. At the end, before creating the pool, there is a `Review` section:
![Review section of the pool creation wizard in TrueNAS](images/truenas-pool-creation-review.png) ![Review section of the pool creation wizard in TrueNAS](img/truenas-pool-creation-review.png)
After hitting `Create Pool`, I'm warned that everything on the disks will be wiped, which I confirm. Finally the pool is created. After hitting `Create Pool`, I'm warned that everything on the disks will be wiped, which I confirm. Finally the pool is created.
@@ -137,10 +137,10 @@ A dataset is a filesystem inside a pool. It can contains files, directories and
#### SMB share #### SMB share
Let's now create my first dataset `files` to share files over the network for my Windows clients, like ISOs, etc: Let's now create my first dataset `files` to share files over the network for my Windows clients, like ISOs, etc:
![Create a dataset in TrueNAS](images/truenas-create-dataset-files.png) ![Create a dataset in TrueNAS](img/truenas-create-dataset-files.png)
When creating SMB datasets in SCALE, set Share Type to SMB so the right ACL/xattr defaults apply. TrueNAS then prompts me to start/enable the SMB service: When creating SMB datasets in SCALE, set Share Type to SMB so the right ACL/xattr defaults apply. TrueNAS then prompts me to start/enable the SMB service:
![Prompt to start SMB service in TrueNAS](images/truenas-start-smb-service.png) ![Invite à démarrer le service SMB dans TrueNAS](img/truenas-start-smb-service.png)
From my Windows Laptop, I try to access my new share `\\granite.mgmt.vezpi.com\files`. As expected I'm prompt to give credentials. From my Windows Laptop, I try to access my new share `\\granite.mgmt.vezpi.com\files`. As expected I'm prompt to give credentials.
@@ -155,7 +155,7 @@ I create another dataset: `media`, and a child `photos`. I create a NFS share fr
On my current NFS server, the files for the photos are owned by `root` (managed by *Immich*). Later I'll see how I can migrate towards a root-less version. On my current NFS server, the files for the photos are owned by `root` (managed by *Immich*). Later I'll see how I can migrate towards a root-less version.
⚠️ For now I set, in `Advanced Options`, the `Maproot User` and `Maproot Group` to `root`. This is equivalent to the NFS attribute `no_squash_root`, the local `root` of the client stays `root` on the server, don't do that: ⚠️ For now I set, in `Advanced Options`, the `Maproot User` and `Maproot Group` to `root`. This is equivalent to the NFS attribute `no_squash_root`, the local `root` of the client stays `root` on the server, don't do that:
![NFS share permission in TrueNAS](images/truenas-dataset-photos-nfs-share.png) ![NFS share permission in TrueNAS](img/truenas-dataset-photos-nfs-share.png)
✅ I mount the NFS share on a client, this works fine. ✅ I mount the NFS share on a client, this works fine.
@@ -175,14 +175,14 @@ I mentioned VM capabilities in my requirements. I won't cover that is this post,
### Data protection ### Data protection
Now time to enable some data protection features: Now time to enable some data protection features:
![Data protection features in TrueNAS](images/truenas-data-protection-tab.png) ![Data protection features in TrueNAS](img/truenas-data-protection-tab.png)
I want to create automatic snapshots for some of my datasets, those I care the most: my cloud files and photos. I want to create automatic snapshots for some of my datasets, those I care the most: my cloud files and photos.
Let's create snapshot tasks. I click on the `Add` button next to `Periodic Snapshot Tasks`: Let's create snapshot tasks. I click on the `Add` button next to `Periodic Snapshot Tasks`:
- cloud: daily snapshots, keep for 2 months - cloud: daily snapshots, keep for 2 months
- photos: daily snapshots, keep for 7 days - photos: daily snapshots, keep for 7 days
![Create periodic snapshot task in TrueNAS ](images/truenas-create-periodic-snapshot.png) ![Create periodic snapshot task in TrueNAS ](img/truenas-create-periodic-snapshot.png)
I could also set up a `Cloud Sync Task`, but Duplicati already handles offsite backups. I could also set up a `Cloud Sync Task`, but Duplicati already handles offsite backups.
@@ -200,12 +200,12 @@ sudo rsync -a --info=progress2 /data/photo/ /new_photos
``` ```
At the end, I could decommission my old NFS server on the LXC. The dataset layout after migration looks like this: At the end, I could decommission my old NFS server on the LXC. The dataset layout after migration looks like this:
![Dataset layout in TrueNAS](images/truenas-datasets-layout.png) ![Dataset layout in TrueNAS](img/truenas-datasets-layout.png)
### Android application ### Android application
Out of curiosity, I've checked on the Google Play store for an app to manage a TrueNAS instance. I've found [Nasdeck](https://play.google.com/store/apps/details?id=com.strtechllc.nasdeck&hl=fr&pli=1), which is quite nice. Here some screenshots: Out of curiosity, I've checked on the Google Play store for an app to manage a TrueNAS instance. I've found [Nasdeck](https://play.google.com/store/apps/details?id=com.strtechllc.nasdeck&hl=fr&pli=1), which is quite nice. Here some screenshots:
![Screenshots of Nasdeck application](images/nasdeck-android-app.png) ![Captures d'écran de l'application Nasdeck](img/nasdeck-android-app.png)
--- ---
## Conclusion ## Conclusion

View File

@@ -0,0 +1,298 @@
---
slug: migrate-passive-opnsense-node-to-truenas
title: Migrate my Passive OPNsense Node to TrueNAS
description: I migrated my passive OPNsense HA VM from Proxmox to TrueNAS to keep routing and firewalling available even when my Proxmox cluster is down.
date: 2026-03-12
draft: true
tags:
- opnsense
- truenas
- proxmox
categories:
- homelab
---
## Intro
My router is the heart of my homelab. When its down, everything is down: internet, DNS, VLAN firewall, reverse proxy… the whole stack.
Im running an [[OPNsense]] HA cluster made of **two virtual machines** inside my [[Proxmox]] VE cluster. It works great… except for one annoying edge case: when the Proxmox cluster is down (rare, but it happens), I suddenly have **no router left**.
Recently I installed a [[TrueNAS]] server ([[Build my NAS with TrueNAS]]), and TrueNAS can host virtual machines. So I decided to move **only the passive OPNsense node** to TrueNAS, so that if Proxmox goes dark, I still have a node alive that can take over and keep the network running.
The objective of this post is simple: explain what I migrated, why I did it, and what configuration choices made it work reliably.
---
## The Plan: Split the HA Pair Across Two Hypervisors
The goal was:
- Keep the **active** OPNsense node running on Proxmox VE (where it already lives).
- Migrate the **passive** node to TrueNAS.
- Validate that the HA cluster still behaves properly (CARP VIPs, sync, services, failover).
This way, a Proxmox outage no longer means “no routing at all”.
---
## What I Used
Quick overview of the pieces involved:
- **OPNsense**: https://opnsense.org/
- **Proxmox VE** (current home of both OPNsense VMs): https://www.proxmox.com/en/proxmox-virtual-environment/overview
- **TrueNAS** (new home of the passive node, and storage to transfer the VM disk): https://www.truenas.com/
---
## Step 1 — Make OPNsense Lighter (RAM Reduction)
TrueNAS on my side doesnt have “infinite RAM”, so the first step was to reduce memory usage to something more reasonable.
I reduced the memory allocation of both OPNsense nodes in Proxmox:
- Shutdown passive node `cerbere-head2`
- Reduce RAM, restart, verify HA
- Swap services to the passive temporarily and test networking
- Shutdown active node `cerbere-head1`
- Reduce RAM, restart, verify HA again
This kept the cluster healthy while ensuring the VM would fit comfortably on the NAS.
(Details: [[Reduce the memory allocation of OPNsense nodes]])
---
## Step 2 — Prepare Networking on TrueNAS (Trunk + VLAN Strategy)
To host an OPNsense VM properly, TrueNAS must be able to present the right networks to the VM (Mgmt, VLANs, etc.). In my case, I needed a trunk configuration.
In TrueNAS, I went to `System` > `Network` and created VLAN interfaces (example with VLAN 13):
![truenas-create-new-vlan-interface.png](img/truenas-create-new-vlan-interface.png)
TrueNAS is nice here: changes arent applied blindly. You can **test** them and you get a rollback window, which is exactly what you want when youre touching the network config remotely:
![truenas-network-confirm-add-vlans.png](img/truenas-network-confirm-add-vlans.png)
### Management bridge
I created a bridge `br1` for the management interface, shared between:
- TrueNAS itself
- the future OPNsense VM
And moved the IP configuration to the bridge:
![truenas-network-mgmt-bridge.png](img/truenas-network-mgmt-bridge.png)
Final view before apply:
![truenas-network-changes-before-apply.png](img/truenas-network-changes-before-apply.png)
### Static IP vs DHCP (and why I stayed static)
I initially tried switching the management bridge to DHCP by updating the MAC address in OPNsense (Dnsmasq override):
![opnsense-update-dnsmasq-override-truenas-bridge.png](img/opnsense-update-dnsmasq-override-truenas-bridge.png)
Then I attempted to flip TrueNAS from static to DHCP:
![truenas-network-bridge-switch-static-to-dhcp.png](img/truenas-network-bridge-switch-static-to-dhcp.png)
But DHCP didnt behave as I expected: it kept receiving random IPs from the pool. I suspected existing leases played a role. I even tried manually editing leases and restarting the service, but after another change, it still ended up with a random address again.
In the end, I gave up and kept **a static IP** for TrueNAS. Its boring, but its predictable.
### The key decision: bridge VLANs (not just VLAN interfaces)
This became important later: I originally planned to attach VLAN interfaces directly to the OPNsense VM, but it didnt behave well.
So I created **one bridge per VLAN** (ex: `br13` with `vlan13` as the only member), and used those bridges for the VM NICs:
![truenas-network-bridges-for-vlan.png](img/truenas-network-bridges-for-vlan.png)
That ended up being the difference between “split-brain chaos” and “stable HA”.
(Full notes: [[Configure the trunk in TrueNAS]])
---
## Step 3 — Move the VM Disk From Proxmox to TrueNAS
To migrate the VM cleanly, I exported the Proxmox disk to TrueNAS.
### Create a dataset and export it via NFS
I created a dataset (initially called `disk`) and exported it with NFS, restricting access to my three Proxmox nodes (by IP):
- 192.168.88.21
- 192.168.88.22
- 192.168.88.23
(Notes: [[Create a new dataset in TrueNAS to export Proxmox VM disk]])
### Export the passive OPNsense disk
On the Proxmox node hosting the passive VM (`cerbere-head2`), I mounted the NFS share:
```bash
mount granite.mgmt.vezpi.com:/mnt/storage/disk /mnt
```
Then I shut down the VM from Proxmox (HA enabled, so I didnt do it from inside OPNsense), and converted/exported the main disk (not the EFI disk) from Ceph RBD to a qcow2 file:
```bash
qemu-img convert -f raw -O qcow2 -p \
rbd:ceph-workload/vm-123-disk-1 \
/mnt/cerbere-head2.qcow2
```
The conversion took around a minute for a 20GB disk.
(Notes: [[Export the passive OPNsense VM disk from Proxmox]])
### Dataset reorg (cleaner layout)
I reorganized datasets on TrueNAS side to something more VM-oriented:
- created `storage/vm`
- renamed `storage/disk` to `storage/vm/files`
Commands used:
```bash
zfs list
sudo zfs create storage/vm
sudo zfs rename storage/disk storage/vm/files
```
(Notes: [[Reorganize the dataset in TrueNAS]])
---
## Step 4 — Create the OPNsense VM on TrueNAS (Import Disk + Rebuild NICs)
Now the fun part: recreating the VM on TrueNAS with the same “spirit” as the Proxmox VM.
From `Virtual Machines`:
![truenas-vm-menu.png](img/truenas-vm-menu.png)
### VM settings I used
I created a new VM with:
**Operating System**
- Guest: FreeBSD
- Name: `cerberehead2` (TrueNAS doesnt like dashes)
- Boot: UEFI
- Secure Boot: Disabled
- TPM: Disabled
- Start on Boot: Enabled
- VNC: Disabled
**CPU & Memory**
- Virtual CPUs: 1
- Cores: 2
- Threads: 1
- CPU Mode: Custom
- CPU Model: `qemu64`
- Memory: 2 GiB
**Disk**
- Import image enabled
- Source: `/mnt/storage/vm/files/cerbere-head2.qcow2`
- Disk Type: VirtIO
- Location: `storage/vm`
- Size: 20 GiB
**Network**
- Adapter: VirtIO
- Attached to `br1` (Mgmt)
- MAC: kept the generated one here
Summary screen:
![truenas-vm-create-new-summary.png](img/truenas-vm-create-new-summary.png)
After saving, TrueNAS converted the imported image into a Zvol:
![truenas-vm-disk-image-conversion.png](img/truenas-vm-disk-image-conversion.png)
### Adding the additional NICs
After the VM was created, I added the additional NICs in the VM device list:
![truenas-vm-details.png](img/truenas-vm-details.png)
At first, I attached VLAN interfaces directly and started the VM… and instantly broke my network (great success).
The VM itself booted fine though, and seeing OPNsense come up cleanly on TrueNAS was a good sign:
![truenas-vm-opnsense-start-shell.png](img/truenas-vm-opnsense-start-shell.png)
But HA-wise, it was a mess: split-brain symptoms, with the TrueNAS-hosted node thinking it was MASTER on almost everything except Mgmt.
The fix was the VLAN bridging approach mentioned earlier: once I switched the VM NICs to attach to **bridges (`br13`, `br20`, etc.) instead of VLAN interfaces**, the cluster came back to a healthy state.
Second try: stable. ✅
(Notes: [[Create the OPNsense VM in TrueNAS]])
---
## Step 5 — Validate HA: CARP, Sync, Services, Switchover and Failover
Once everything was in place, I validated the new setup with a proper checklist. I wanted to be sure the cluster worked exactly as before.
### Basic checks
- Ping each interface as relevant (Mgmt/User/IoT/pfSync/DMZ/Lab)
- SSH access
- Web UI access
- CARP VIP status must be `BACKUP` on the passive node
- HA status (active must be able to log into passive)
- Services state + “Synchronize and reconfigure all”
- Check updates availability (`System` > `Firmware` > `Check for updates`)
### Switchover test (graceful)
I started:
- a SSH session to DockerVM (to check state keeping)
- a ping to an IoT host from a laptop
Then tested:
- CARP role switch
- inter-VLAN routing
- WAN ping to `8.8.8.8`
- firewall state (SSH session stays alive)
- DNS resolution (external + internal)
- Caddy reverse proxy + layer4 proxy checks
- Wireguard access from outside
- mDNS discovery (printer visibility)
✅ Switchover successful.
### Failover test (hard)
Then I forced power off of the active node and repeated the same functional tests.
✅ Failover successful.
At the end: restarted the active VM, and the HA pair returned to normal operation.
One note: QEMU Guest Agent doesnt bring value here because TrueNAS doesnt implement it as a hypervisor (I still left it installed since its harmless).
(Full checklist and validation steps: [[Validate the new OPNsense VM and cluster state]])
---
## Conclusion
This project solved a real weakness in my homelab: my “highly available” router cluster was still depending on a single platform (Proxmox). By moving only the **passive OPNsense node** to **TrueNAS**, I now have a router that can survive a full Proxmox outage.
The biggest takeaway for me was networking on TrueNAS: attaching VLAN interfaces directly to the VM was not reliable in my setup, but bridging each VLAN (`br13`, `br20`, etc.) made the HA behavior stable and predictable.
Next step is to monitor the cluster for a few days before doing the cleanup of the migration on the Proxmox side.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

View File

@@ -1,283 +0,0 @@
---
slug: migrate-passive-opnsense-node-to-truenas
title: Migrer mon nœud OPNsense HA passif vers TrueNAS
description: Jai migré ma VM OPNsense HA passive de Proxmox vers TrueNAS pour garder le routage et le firewalling disponibles même lorsque mon cluster Proxmox est arrêté.
date: 2026-05-24
draft: false
tags:
- opnsense
- truenas
- proxmox
- high-availability
categories:
- homelab
---
## Intro
Mon réseau homelab est géré par un cluster OPNsense composé de deux nœuds VM. Ces deux VM fonctionnent dans mon cluster Proxmox VE. Vous pouvez trouver les détails dans cet [article]({{< ref "post/15-migration-opnsense-proxmox-highly-available" >}}).
Cette configuration fonctionne bien la plupart du temps. Le problème concerne plutôt les rares cas où le cluster Proxmox lui-même est arrêté. Quand cela arrive, les deux nœuds OPNsense sont indisponibles en même temps, ce qui signifie quil ne me reste aucun routeur, donc aucun réseau du tout.
Récemment, jai installé un serveur TrueNAS dans le lab, que j'ai documenté dans ce [post]({{< ref "post/18-create-nas-server-with-truenas" >}}). Il est principalement là pour agir comme NAS, mais il pourrait aussi héberger des machines virtuelles. Cela me donne une bonne opportunité daméliorer la résilience de mon réseau sans changer toute la conception.
💡 Lidée est simple : garder le nœud OPNsense actif sur Proxmox, mais déplacer le nœud passif vers TrueNAS.
De cette façon, si le cluster Proxmox tombe, le nœud OPNsense passif peut toujours prendre le relais et garder le réseau fonctionnel.
---
## Préparer les nœuds OPNsense
Avant de déplacer quoi que ce soit, je veux massurer que les VM OPNsense peuvent fonctionner avec moins de mémoire.
Le serveur TrueNAS na pas autant de RAM disponible que le cluster Proxmox, donc la première étape est de réduire lallocation mémoire des nœuds OPNsense au minimum.
Je commence avec le nœud passif, `cerbere-head2` :
- Éteindre le nœud passif
- Réduire son allocation mémoire de 4 à 2GB
- Le redémarrer
- Vérifier la santé du cluster
- Basculer le service vers le nœud passif
- Exécuter des vérifications réseau
Ensuite, je répète la même opération sur le nœud actif, `cerbere-head1`.
Le faire un nœud à la fois me permet de garder le cluster HA en bonne santé tout en validant que lallocation mémoire réduite est toujours suffisante pour ma configuration.
---
## Préparer le réseau TrueNAS
La partie la plus importante de cette migration nest pas lexport du disque ni la création de la VM. Cest le réseau.
Une VM OPNsense nest pas un simple serveur avec une seule interface de management. Elle a besoin daccéder à plusieurs réseaux, incluant le management, le WAN, les réseaux utilisateurs, lIoT, pfSync, la DMZ et les réseaux lab.
Du côté TrueNAS, je commence depuis `System` > `Network` et jajoute des interfaces VLAN.
La première est le VLAN utilisateur :
- Type : `VLAN`
- Nom : `vlan13`
- Description : `User`
- Interface parente : `enp1s0`
- Tag VLAN : `13`
![Créer linterface VLAN utilisateur dans TrueNAS](images/truenas-create-new-vlan-interface.png)
Jajoute ensuite les autres VLANs de la même manière.
TrueNAS napplique pas les changements réseau directement. Il donne loption de tester les changements dabord, avec une courte fenêtre de validation. Si la configuration nest pas confirmée à temps, il revient automatiquement en arrière.
Cest vraiment pratique lorsquon change la configuration réseau de la machine à laquelle on est actuellement connecté.
![Confirmer les interfaces VLAN avant dappliquer les changements réseau](images/truenas-network-confirm-add-vlans.png)
Pour le réseau de management, jai créé un bridge appelé `br1`.
Ce bridge porte la configuration IP de management de TrueNAS à la place de linterface physique `enp1s0`, parce quelle doit aussi être partagée avec la VM OPNsense.
![Créer le bridge de management pour TrueNAS et la VM OPNsense](images/truenas-network-mgmt-bridge.png)
Après cela, je retire la configuration IP de linterface physique et je la garde sur le bridge.
![Configuration réseau avant dappliquer les changements du bridge](images/truenas-network-changes-before-apply.png)
Jai initialement essayé dutiliser DHCP pour le bridge de management après avoir mis à jour ladresse MAC dans Dnsmasq, mais jai finalement décidé de garder une adresse IP statique pour TrueNAS. Après certains changements réseau, DHCP a donné une autre adresse du pool, donc ladressage statique était loption la plus sûre et la plus simple pour ce serveur.
Pour la VM OPNsense, je crée un bridge pour chaque VLAN. Par exemple, `br13` utilise `vlan13`, je déplace aussi la description, comme `User`, de linterface VLAN vers le bridge pour plus de clarté.
La configuration réseau finale de TrueNAS :
![Créer un bridge par VLAN pour la VM OPNsense](images/truenas-network-bridges-for-vlan.png)
---
## Créer un dataset dexport temporaire
Pour déplacer le disque de la VM OPNsense passive de Proxmox vers TrueNAS, jai dabord besoin dun endroit pour exporter limage disque.
Dans TrueNAS, je crée un dataset nommé `storage/vm/disk`, puis je crée un partage NFS à partir de celui-ci.
Dans les options avancées du partage NFS, jai configuré :
- Utilisateur Maproot : `root`
- Hôtes autorisés :
- `192.168.88.21`
- `192.168.88.22`
- `192.168.88.23`
Ce sont les nœuds Proxmox VE autorisés à monter le partage.
Je ne crée pas manuellement de zvol à ce moment-là. Le processus de création de VM dans TrueNAS gère limport et la conversion du disque.
---
## Exporter le disque de la VM depuis Proxmox
Depuis linterface web Proxmox VE, je localise le nœud qui héberge la VM OPNsense passive `cerbere-head2`, elle fonctionne sur `Zenith`.
Je me connecte à ce nœud Proxmox en SSH et je monte le partage NFS depuis TrueNAS :
```bash
mount granite.mgmt.vezpi.com:/mnt/storage/vm/disk /mnt
```
Ensuite, jéteins la VM depuis linterface Proxmox VE. Je ne léteins pas depuis lintérieur dOPNsense parce que la VM a la HA activée.
Une fois la VM arrêtée, jexporte le disque principal en qcow2. Je nexporte pas le disque EFI.
```bash
qemu-img convert -f raw -O qcow2 -p \
rbd:ceph-workload/vm-123-disk-1 \
/mnt/cerbere-head2.qcow2
```
La conversion a pris environ une minute pour un disque de 20 GB.
À ce stade, le disque OPNsense passif est disponible sur TrueNAS et prêt à être importé dans une nouvelle VM.
---
## Recréer la VM OPNsense dans TrueNAS
Létape suivante consiste à recréer la VM OPNsense passive dans TrueNAS avec des paramètres correspondant aussi étroitement que possible à la VM dorigine.
Depuis linterface web TrueNAS, je vais dans la section `Virtual Machines`.
![La section Machines virtuelles dans TrueNAS](images/truenas-vm-menu.png)
Je crée une nouvelle VM avec ces paramètres.
Pour le système dexploitation :
- Système dexploitation invité : `FreeBSD`
- Nom : `cerberehead2`
- Horloge système : `Local`
- Méthode de démarrage : `UEFI`
- Activer Secure Boot : désactivé
- Activer Trusted Platform Module : désactivé
- Timeout darrêt : `90`
- Démarrer au boot : activé
- Activer laffichage VNC : désactivé
Le nom de la VM nutilise pas de tirets parce que TrueNAS ne les autorise pas ici.
Pour le CPU et la mémoire :
- CPU virtuels : `1`
- Cœurs : `2`
- Threads : `1`
- Mode CPU : `Custom`
- Modèle CPU : `qemu64`
- Taille mémoire : `2 GiB`
Pour le disque :
- Créer une nouvelle image disque
- Importer une image : activé
- Source de limage : `/mnt/storage/vm/files/cerbere-head2.qcow2`
- Type de disque : `VirtIO`
- Emplacement de stockage : `storage/vm`
- Taille : `20 GiB`
Pour la première interface réseau :
- Type dadaptateur : `VirtIO`
- Adresse MAC : garder celle proposée
- Attacher la NIC : `br1: Mgmt`
Je passe le média dinstallation et la configuration GPU, puis je confirme le résumé.
![Résumé avant de créer la VM OPNsense dans TrueNAS](images/truenas-vm-create-new-summary.png)
Après confirmation, TrueNAS convertit limage qcow2 importée en zvol.
![TrueNAS convertissant limage disque importée en zvol](images/truenas-vm-disk-image-conversion.png)
Une fois la VM créée, jouvre les détails de la VM et jajoute les NICs restantes.
![Les périphériques de la VM dans TrueNAS](images/truenas-vm-details.png)
Pour chaque NIC supplémentaire, jai utilisé VirtIO comme type dadaptateur et je lai attachée au bridge correspondant.
Pour la NIC WAN, je copie lancienne adresse MAC parce que jutilise une astuce avec une seule adresse IP WAN. Jincrémente aussi le chiffre dans lordre des périphériques pour garder le même que dans Proxmox.
![Interface réseau VirtIO supplémentaire pour la VM OPNsense](images/truenas-vm-add-nic.png)
🎉 Enfin, je peux démarrer la VM OPNsense dans TrueNAS.
![OPNsense démarre avec succès comme VM TrueNAS](images/truenas-vm-opnsense-start-shell.png)
---
## Valider le cluster HA
Une fois que le nœud passif fonctionne sur TrueNAS, je dois valider que le cluster HA OPNsense se comporte toujours correctement.
Je commence par des vérifications de base sur le nœud passif :
- Ping de linterface de management depuis le bastion : `192.168.88.3`
- Ping de linterface utilisateur depuis un laptop : `192.168.13.3`
- Ping de linterface IoT : `192.168.37.3`
- Ping pfSync depuis lautre nœud : `192.168.44.2`
- Ping de linterface DMZ : `192.168.55.3`
- Ping de linterface Lab depuis DockerVM : `192.168.66.3`
Je vérifie aussi que le nœud était accessible en SSH depuis mon laptop en utilisant `192.168.13.3`, et que linterface web était joignable à :
```text
https://192.168.13.3:4443
```
Ensuite, je valide létat HA dOPNsense :
- Le statut des VIP CARP doit être `BACKUP` sur toutes les VIP
- La page de statut HA doit montrer que le nœud actif peut se connecter au nœud passif
- Les services doivent fonctionner comme attendu
- La synchronisation des services HA doit fonctionner
- Les vérifications de mise à jour du firmware doivent être accessibles
Depuis le nœud actif, jutilise la page de statut HA et je force une synchronisation complète avec `Synchronize and reconfigure all`.
---
## Tests de bascule contrôlée
Avant de tester le failover, je démarre une session SSH vers `dockerVM` pour confirmer que les états du firewall sont préservés entre les nœuds. Je démarre aussi un ping depuis un laptop vers `192.168.37.120`.
Pour le test de bascule contrôlée, jactive proprement le mode maintenance sur le nœud master.
Le nouveau nœud passif devient `MASTER`, et je valide les services importants :
- Routage VLAN supplémentaire avec un ping vers `192.168.37.120`
- Accès WAN avec un ping vers `8.8.8.8`
- États du firewall en gardant la session SSH active
- Résolution DNS externe avec `host redhat.com`
- Résolution DNS interne avec `host SLZB-06M.mgmt.vezpi.com`
- Accès à une page internet aléatoire
- Reverse proxy Caddy
- Proxy layer4 Caddy
- Accès Wireguard depuis lextérieur
- mDNS en vérifiant si limprimante est apparue
✅ La bascule contrôlée est réussie.
---
## Tests de failover
Après le test de bascule contrôlée propre, je teste un scénario de failover plus direct en forçant un poweroff du nœud actif.
Jai répété la même checklist de validation.
✅ Le failover est réussi.
Enfin, je redémarre la VM OPNsense active.
🎯 À ce stade, le cluster HA OPNsense est de nouveau opérationnel, avec le nœud passif qui fonctionne maintenant sur TrueNAS au lieu de Proxmox.
---
## Conclusion
Cette migration est une petite mais importante amélioration pour mon homelab.
Avant, les deux nœuds OPNsense dépendaient du cluster Proxmox VE. Si le cluster était arrêté, toute ma couche de routage réseau était arrêtée avec lui.
Maintenant, le nœud actif fonctionne toujours sur Proxmox, mais le nœud passif fonctionne sur TrueNAS. Cela me donne une meilleure séparation entre le cluster de virtualisation et la couche de failover réseau.
Petit disclaimer, bien que TrueNAS offre des fonctionnalités de virtualisation, il nest pas comparable à Proxmox VE en termes de clustering et de capacités de gestion dinfrastructure.
Une note à propos de QEMU Guest Agent, la VM OPNsense avait déjà QEMU Guest Agent installé avant lexport. Dans cette configuration, il ne semble pas utile parce que TrueNAS ne la pas implémenté comme fonctionnalité dhyperviseur. Je lai gardé installé quand même, parce quil est inoffensif.

View File

@@ -1,283 +0,0 @@
---
slug: migrate-passive-opnsense-node-to-truenas
title: Migrate my Passive OPNsense HA Node to TrueNAS
description: I migrated my passive OPNsense HA VM from Proxmox to TrueNAS to keep routing and firewalling available even when my Proxmox cluster is down.
date: 2026-05-24
draft: false
tags:
- opnsense
- truenas
- proxmox
- high-availability
categories:
- homelab
---
## Intro
My homelab network is handled by an OPNsense cluster composed of two VM nodes. Both of these VMs are running inside my Proxmox VE cluster. You can find details in this [article]({{< ref "post/15-migration-opnsense-proxmox-highly-available" >}}).
This setup works fine most of the time. The issue is more about the rare cases where the Proxmox cluster itself is down. When that happens, both OPNsense nodes are unavailable at the same time, which means I do not have any router left, so no network at all.
Recently, I installed a TrueNAS server in the labwhich I document in that [post]({{< ref "post/18-create-nas-server-with-truenas" >}}). It is mainly here to act as a NAS, but it could also host virtual machines. That give me a good opportunity to improve the resilience of my network without changing the whole design.
💡 The idea is simple: keep the active OPNsense node on Proxmox, but move the passive node to TrueNAS.
This way, if the Proxmox cluster goes down, the passive OPNsense node can still take over and keep the network alive.
---
## Prepare the OPNsense Nodes
Before moving anything, I want to make sure the OPNsense VMs could run with less memory.
The TrueNAS server does not have as much RAM available as the Proxmox cluster, so the first step is to reduce the memory allocation of the OPNsense nodes to the minimum.
I start with the passive node, `cerbere-head2`:
- Shut down the passive node
- Reduce its memory allocation from 4 to 2GB
- Restart it
- Verify the cluster health
- Swap the service to the passive node
- Run network checks
Then I repeat the same operation on the active node, `cerbere-head1`.
Doing it one node at a time allow me to keep the HA cluster healthy while validating that the reduced memory allocation is still enough for my setup.
---
## Prepare the TrueNAS Network
The most important part of this migration is not the disk export or the VM creation. It is the network.
An OPNsense VM is not a simple server with one management interface. It needs access to several networks, including management, WAN, user networks, IoT, pfSync, DMZ and lab networks.
On the TrueNAS side, I start from `System` > `Network` and add VLAN interfaces.
The first one is the User VLAN:
- Type: `VLAN`
- Name: `vlan13`
- Description: `User`
- Parent interface: `enp1s0`
- VLAN tag: `13`
![Create the User VLAN interface in TrueNAS](images/truenas-create-new-vlan-interface.png)
I then add the other VLANs in the same way.
TrueNAS does not apply network changes directly. It gives the option to test the changes first, with a short validation window. If the configuration is not confirmed in time, it rolls back automatically.
This is really convenient when changing the network configuration of the machine you are currently connected to.
![Confirm the VLAN interfaces before applying the network changes](images/truenas-network-confirm-add-vlans.png)
For the management network, I created a bridge called `br1`.
This bridge holds the TrueNAS management IP configuration instead of the physical interface `enp1s0`, because it also needs to be shared with the OPNsense VM.
![Create the management bridge for TrueNAS and the OPNsense VM](images/truenas-network-mgmt-bridge.png)
After that, I remove the IP configuration from the physical interface and keep it on the bridge.
![Network configuration before applying the bridge changes](images/truenas-network-changes-before-apply.png)
I initially tried to use DHCP for the management bridge after updating the MAC address in Dnsmasq, but I finally decided to keep a static IP address for TrueNAS. After some network changes, DHCP gave another address from the pool, so static addressing was the safer and simpler option for this server.
For the OPNsense VM, I create a bridge for each VLAN. For example, `br13` uses `vlan13`, I also move the description, like `User`, from the VLAN interface to the bridge for clarity.
The final TrueNAS network configuration:
![Create one bridge per VLAN for the OPNsense VM](images/truenas-network-bridges-for-vlan.png)
---
## Create a Temporary Export Dataset
To move the passive OPNsense VM disk from Proxmox to TrueNAS, I first need a place to export the disk image.
In TrueNAS, I create a dataset named `storage/vm/disk`, then create a NFS share from it.
In the advanced options of the NFS share, I configured:
- Maproot user: `root`
- Authorized hosts:
- `192.168.88.21`
- `192.168.88.22`
- `192.168.88.23`
These are the Proxmox VE nodes allowed to mount the share.
I don't manually create a zvol at that point. The VM creation process in TrueNAS handle the disk import and conversion.
---
## Export the VM Disk from Proxmox
From the Proxmox VE web interface, I locate the node hosting the passive OPNsense VM `cerbere-head2`, it is running on `Zenith`.
I log into that Proxmox node over SSH and mount the NFS share from TrueNAS:
```bash
mount granite.mgmt.vezpi.com:/mnt/storage/vm/disk /mnt
```
Then I shut down the VM from the Proxmox VE interface. I don't shut it down from inside OPNsense because the VM has HA enabled.
Once the VM is stopped, I export the main disk to qcow2. I don't export the EFI disk.
```bash
qemu-img convert -f raw -O qcow2 -p \
rbd:ceph-workload/vm-123-disk-1 \
/mnt/cerbere-head2.qcow2
```
The conversion took about one minute for a 20 GB disk.
At this point, the passive OPNsense disk is available on TrueNAS and ready to be imported into a new VM.
---
## Recreate the OPNsense VM in TrueNAS
The next step is to recreate the passive OPNsense VM in TrueNAS with parameters matching the original VM as closely as possible.
From the TrueNAS web interface, I go to the `Virtual Machines` section.
![The Virtual Machines section in TrueNAS](images/truenas-vm-menu.png)
I create a new VM with these settings.
For the operating system:
- Guest Operating System: `FreeBSD`
- Name: `cerberehead2`
- System Clock: `Local`
- Boot Method: `UEFI`
- Enable Secure Boot: disabled
- Enable Trusted Platform Module: disabled
- Shutdown Timeout: `90`
- Start on Boot: enabled
- Enable Display VNC: disabled
The VM name does not use dashes because TrueNAS do not allow them there.
For CPU and memory:
- Virtual CPUs: `1`
- Cores: `2`
- Threads: `1`
- CPU Mode: `Custom`
- CPU Model: `qemu64`
- Memory Size: `2 GiB`
For the disk:
- Create new disk image
- Import Image: enabled
- Image source: `/mnt/storage/vm/files/cerbere-head2.qcow2`
- Disk Type: `VirtIO`
- Storage Location: `storage/vm`
- Size: `20 GiB`
For the first network interface:
- Adapter Type: `VirtIO`
- MAC Address: keep the proposed one
- Attach NIC: `br1: Mgmt`
I skip installation media and GPU configuration, then confirm the summary.
![Summary before creating the OPNsense VM in TrueNAS](images/truenas-vm-create-new-summary.png)
After confirmation, TrueNAS convert the imported qcow2 image into a zvol.
![TrueNAS converting the imported disk image into a zvol](images/truenas-vm-disk-image-conversion.png)
Once the VM is created, I open the VM details and add the remaining NICs.
![The VM devices in TrueNAS](images/truenas-vm-details.png)
For each additional NIC, I used VirtIO as the adapter type and attach it to the corresponding bridge.
For the WAN NIC, I copy the old MAC address because I use a single WAN IP address trick. I also increment the digit in the Device Order to keep the same as in Proxmox.
![Additional VirtIO network interface to the OPNsense VM](images/truenas-vm-add-nic.png)
🎉 Finally I can start the OPNsense VM in TrueNAS.
![OPNsense booting successfully as a TrueNAS VM](images/truenas-vm-opnsense-start-shell.png)
---
## Validate the HA cluster
Once the passive node is running on TrueNAS, I need to validate that the OPNsense HA cluster is still behaving correctly.
I start with basic checks on the passive node:
- Management interface ping from the bastion: `192.168.88.3`
- User interface ping from a laptop: `192.168.13.3`
- IoT interface ping: `192.168.37.3`
- pfSync ping from the other node: `192.168.44.2`
- DMZ interface ping: `192.168.55.3`
- Lab interface ping from DockerVM: `192.168.66.3`
I also check that the node was accessible over SSH from my laptop using `192.168.13.3`, and that the web interface was reachable at:
```text
https://192.168.13.3:4443
```
Then I validate the OPNsense HA state:
- CARP VIP status must be `BACKUP` on all VIPs
- HA status page must show that the active node can log in to the passive node
- Services must be running as expected
- HA service synchronization must work
- Firmware update checks must be accessible
From the active node, I use the HA status page and force a full synchronization with `Synchronize and reconfigure all`.
---
## Switchover Tests
Before testing failover, I start a SSH session to `dockerVM` to confirm that firewall states are preserved across nodes. I also start a ping from a laptop to `192.168.37.120`.
For the switchover test, I gracefully enable maintenance mode on the master node.
The new passive node become `MASTER`, and I validate the important services:
- Extra VLAN routing with ping to `192.168.37.120`
- WAN access with ping to `8.8.8.8`
- Firewall states by keeping the SSH session alive
- External DNS resolution with `host redhat.com`
- Internal DNS resolution with `host SLZB-06M.mgmt.vezpi.com`
- Access to a random internet page
- Caddy reverse proxy
- Caddy layer4 proxy
- Wireguard access from outside
- mDNS by checking if the printer showed up
✅ The switchover is successful.
---
## Failover Tests
After the graceful switchover test, I test a more direct failover scenario by forcing a poweroff of the active node.
I repeated the same validation checklist.
✅ The failover is successful.
Finally, I restart the active OPNsense VM.
🎯 At that point, the OPNsense HA cluster is operational again, with the passive node now running on TrueNAS instead of Proxmox.
---
## Conclusion
This migration is a small but important improvement for my homelab.
Before, both OPNsense nodes depended on the Proxmox VE cluster. If the cluster was down, my whole network routing layer was down with it.
Now, the active node still runs on Proxmox, but the passive node runs on TrueNAS. This gives me a better separation between the virtualization cluster and the network failover layer.
Little disclaimer, while TrueNAS offers virtualization features, it is not comparable to Proxmox VE in terms of clustering and infrastructure management capabilities.
A note about QEMU Guest Agent, the OPNsense VM already had the QEMU Guest Agent installed before expert. In this setup, it does not seem useful because TrueNAS does not have it implemented as a hypervisor feature. I kept it installed anyway, because it is harmless.

View File

@@ -60,7 +60,7 @@ L'idée est simple :
De cette façon, je n'ai plus besoin de copier manuellement de fichiers ni de gérer les déploiements. Tout se déroule, de l'écriture de Markdown dans Obsidian au déploiement complet du site web. De cette façon, je n'ai plus besoin de copier manuellement de fichiers ni de gérer les déploiements. Tout se déroule, de l'écriture de Markdown dans Obsidian au déploiement complet du site web.
![Workflow depuis l'écriture de notes sur Obsidian au Blog publié](images/obsidian-blog-gitea-actions-workflow.png) ![Workflow depuis l'écriture de notes sur Obsidian au Blog publié](img/obsidian-blog-gitea-actions-workflow.png)
--- ---
## ⚙️ Implémentation ## ⚙️ Implémentation
@@ -101,17 +101,17 @@ container:
``` ```
Le runner apparaît dans `Administration Area`, sous `Actions`>`Runners`. Pour obtenir le token d'enrôlement , on clique sur le bouton `Create new Runner` Le runner apparaît dans `Administration Area`, sous `Actions`>`Runners`. Pour obtenir le token d'enrôlement , on clique sur le bouton `Create new Runner`
![Nouveau runner visible dans Gitea](images/gitea-runners-management.png) ![New runner visible in Gitea](img/gitea-runners-management.png)
### Étape 3 : Configurer les Gitea Actions pour le dépôt Obsidian ### Étape 3 : Configurer les Gitea Actions pour le dépôt Obsidian
J'ai d'abord activé les Gitea Actions. Celles-ci sont désactivées par défaut. Cochez la case `Enable Repository Actions` dans les paramètres de ce dépôt. J'ai d'abord activé les Gitea Actions. Celles-ci sont désactivées par défaut. Cochez la case `Enable Repository Actions` dans les paramètres de ce dépôt.
J'ai créé un nouveau PAT (Personal Access Token) avec autorisation RW sur les dépôts. J'ai créé un nouveau PAT (Personal Access Token) avec autorisation RW sur les dépôts.
![Nouvelle création de token d'accès personnel dans Gitea](images/gitea-new-pat.png) ![New personal access token creation in Gitea](img/gitea-new-pat.png)
J'ai ajouté le token comme secret `REPO_TOKEN` dans le dépôt. J'ai ajouté le token comme secret `REPO_TOKEN` dans le dépôt.
![Fenêtre d'ajout de secret dans un dépôt Gitea](images/gitea-add-repo-secret.png) ![Add secret window for repository in Gitea](img/gitea-add-repo-secret.png)
J'ai dû créer le workflow qui lancera un conteneur et effectuera les opérations suivantes : J'ai dû créer le workflow qui lancera un conteneur et effectuera les opérations suivantes :
@@ -171,7 +171,7 @@ jobs:
git push -u origin main git push -u origin main
``` ```
Obsidian utilise des liens de type wiki pour les images, comme `![`, ce qui n'est pas compatible avec Hugo par défaut. Voici comment j'ai automatisé une solution de contournement dans un workflow Gitea Actions :](images/nom_image.png) Obsidian utilise des liens de type wiki pour les images, comme `![[nom_image.png]]`, ce qui n'est pas compatible avec Hugo par défaut. Voici comment j'ai automatisé une solution de contournement dans un workflow Gitea Actions :
- Je trouve toutes les références d'images utilisées dans des fichiers `.md`. - Je trouve toutes les références d'images utilisées dans des fichiers `.md`.
- Pour chaque image référencée, je mets à jour le lien dans les fichiers `.md` correspondants, comme `![nom_image](img/nom_image.png)`. - Pour chaque image référencée, je mets à jour le lien dans les fichiers `.md` correspondants, comme `![nom_image](img/nom_image.png)`.
- Je copie ensuite ces images utilisées dans le répertoire statique du blog en remplaçant les espaces par des underscores. - Je copie ensuite ces images utilisées dans le répertoire statique du blog en remplaçant les espaces par des underscores.

View File

@@ -59,7 +59,7 @@ The idea is simple:
This way, I never need to manually copy files or trigger deployments. Everything flows from writing markdown in Obsidian to having a fully deployed website. This way, I never need to manually copy files or trigger deployments. Everything flows from writing markdown in Obsidian to having a fully deployed website.
![Workflow from writing notes on Obsidian to Blog published](images/obsidian-blog-gitea-actions-workflow.png) ![Workflow depuis l'écriture de notes sur Obsidian au Blog publié](img/obsidian-blog-gitea-actions-workflow.png)
--- ---
## ⚙️ Implementation ## ⚙️ Implementation
@@ -100,17 +100,17 @@ container:
``` ```
The runner appears in the `Administration Area`, under `Actions`>`Runners`. To obtain the registration token, click on the `Create new Runner` button The runner appears in the `Administration Area`, under `Actions`>`Runners`. To obtain the registration token, click on the `Create new Runner` button
![New runner visible in Gitea](images/gitea-runners-management.png) ![New runner visible in Gitea](img/gitea-runners-management.png)
### Step 3: Set up Gitea Actions for Obsidian Repository ### Step 3: Set up Gitea Actions for Obsidian Repository
First I enabled the Gitea Actions, this is disabled by default, tick the box `Enable Repository Actions` in the settings for that repository First I enabled the Gitea Actions, this is disabled by default, tick the box `Enable Repository Actions` in the settings for that repository
I created a new PAT (Personal Access Token) with RW permission on the repositories I created a new PAT (Personal Access Token) with RW permission on the repositories
![New personal access token creation in Gitea](images/gitea-new-pat.png) ![New personal access token creation in Gitea](img/gitea-new-pat.png)
I added this token as secret `REPO_TOKEN` in the repository I added this token as secret `REPO_TOKEN` in the repository
![Add secret window for repository in Gitea](images/gitea-add-repo-secret.png) ![Add secret window for repository in Gitea](img/gitea-add-repo-secret.png)
I needed to create the workflow that will spin-up a container and do the following: I needed to create the workflow that will spin-up a container and do the following:
@@ -170,7 +170,7 @@ jobs:
git push -u origin main git push -u origin main
``` ```
Obsidian uses wiki-style links for images, like `![`, which isn't compatible with Hugo out of the box. Here's how I automated a workaround in a Gitea Actions workflow:](images/image_name.png) Obsidian uses wiki-style links for images, like `![[image name.png]]`, which isn't compatible with Hugo out of the box. Here's how I automated a workaround in a Gitea Actions workflow:
- I find all used image references in `.md` files. - I find all used image references in `.md` files.
- For each referenced image, I update the link in relevant `.md` files like `![image name](img/image_name.png)`. - For each referenced image, I update the link in relevant `.md` files like `![image name](img/image_name.png)`.
- I then copy those used images to the blog's static directory while replacing white-spaces by underscores. - I then copy those used images to the blog's static directory while replacing white-spaces by underscores.

View File

@@ -698,7 +698,7 @@ vm_ip = "192.168.66.156"
✅ Voilà, on vient de créer une VM sur Proxmox en quelques minutes. ✅ Voilà, on vient de créer une VM sur Proxmox en quelques minutes.
![Résumé de la nouvelle VM crée sur Proxmox](images/proxmox-terraform-new-vm.png) ![Résumé de la nouvelle VM crée sur Proxmox](img/proxmox-terraform-new-vm.png)
### Connexion SSH ### Connexion SSH

View File

@@ -697,7 +697,7 @@ vm_ip = "192.168.66.156"
✅ Done! Weve successfully created our first VM on Proxmox using Terraform in just a few minutes. ✅ Done! Weve successfully created our first VM on Proxmox using Terraform in just a few minutes.
![Summary of the newly created VM on Proxmox](images/proxmox-terraform-new-vm.png) ![Résumé de la nouvelle VM crée sur Proxmox](img/proxmox-terraform-new-vm.png)
### SSH Connection ### SSH Connection

View File

@@ -22,13 +22,13 @@ Le blog étant redéployé de façon automatique à chaque modification du conte
Aujourd'hui mon blog se redéploie automatiquement à chaque modification de la branche `main` du [dépôt Git](https://git.vezpi.com/Vezpi/Blog) de mon instance **Gitea** via une **Gitea Actions**. Chaque modification apportée à mon vault **Obsidian** est poussée automatiquement dans cette branche. Aujourd'hui mon blog se redéploie automatiquement à chaque modification de la branche `main` du [dépôt Git](https://git.vezpi.com/Vezpi/Blog) de mon instance **Gitea** via une **Gitea Actions**. Chaque modification apportée à mon vault **Obsidian** est poussée automatiquement dans cette branche.
![Workflow depuis l'écriture de notes sur Obsidian au blog publié](images/obsidian-blog-gitea-actions-workflow.png) ![Workflow depuis l'écriture de notes sur Obsidian au Blog publié](img/obsidian-blog-gitea-actions-workflow.png)
### Créer une Nouvelle Branche ### Créer une Nouvelle Branche
La première partie, la plus simple, a donc été de créer une nouvelle branche qui allait recevoir ces modifications. J'ai donc crée la branche `preview` dans ce dépôt. Ensuite j'ai modifié la branche cible recevant les modifications dans le workflow de mon dépôt Git Obsidian. La première partie, la plus simple, a donc été de créer une nouvelle branche qui allait recevoir ces modifications. J'ai donc crée la branche `preview` dans ce dépôt. Ensuite j'ai modifié la branche cible recevant les modifications dans le workflow de mon dépôt Git Obsidian.
![Créer la branche preview depuis la branche main dans Gitea](images/gitea-create-new-branch.png) ![Create the preview branch from the main branch in Gitea](img/gitea-create-new-branch.png)
### Containeriser le Blog ### Containeriser le Blog
@@ -211,7 +211,7 @@ Maintenant voici ce que le nouveau workflow fait :
Voici un exemple de déploiement après un commit automatique généré par **Obsidian**, on peut voir ici que l'image Docker n'a pas été reconstruire car il n'y avait pas de nouvelle version d'Hugo disponible et que le dossier `docker` n'avait pas été modifié, de ce fait, le dernier job `Clean` n'était pas non plus nécessaire. Voici un exemple de déploiement après un commit automatique généré par **Obsidian**, on peut voir ici que l'image Docker n'a pas été reconstruire car il n'y avait pas de nouvelle version d'Hugo disponible et que le dossier `docker` n'avait pas été modifié, de ce fait, le dernier job `Clean` n'était pas non plus nécessaire.
![Workflow Gitea Actions du déploiement du blog](images/gitea-actions-deploy-blog-workflow.png) ![Gitea Actions workflow for blog deployment](img/gitea-actions-deploy-blog-workflow.png)
#### Code #### Code

View File

@@ -22,13 +22,13 @@ Since the blog is automatically redeployed every time I modify content in Obsidi
Currently, my blog redeploys automatically on every change to the `main` branch of the [Git repository](https://git.vezpi.com/Vezpi/Blog) hosted on my **Gitea** instance, using a **Gitea Actions** workflow. Every change made in my **Obsidian** vault is automatically pushed to this branch. Currently, my blog redeploys automatically on every change to the `main` branch of the [Git repository](https://git.vezpi.com/Vezpi/Blog) hosted on my **Gitea** instance, using a **Gitea Actions** workflow. Every change made in my **Obsidian** vault is automatically pushed to this branch.
![Workflow from writing notes on Obsidian to Blog published](images/obsidian-blog-gitea-actions-workflow.png) ![Workflow depuis l'écriture de notes sur Obsidian au Blog publié](img/obsidian-blog-gitea-actions-workflow.png)
### Create a New Branch ### Create a New Branch
The first and easiest step was to create a new branch to receive these changes. So I created a `preview` branch in this repository and then updated the target branch in the workflow of my Obsidian Git repo. The first and easiest step was to create a new branch to receive these changes. So I created a `preview` branch in this repository and then updated the target branch in the workflow of my Obsidian Git repo.
![Create the preview branch from the main branch in Gitea](images/gitea-create-new-branch.png) ![Create the preview branch from the main branch in Gitea](img/gitea-create-new-branch.png)
### Containerize the Blog ### Containerize the Blog
@@ -211,7 +211,7 @@ Now, heres what the new workflow does:
Heres an example of a deployment triggered by an automatic commit from **Obsidian**. You can see that the Docker image wasnt rebuilt because no new Hugo version was available and the `docker` folder hadnt changed, so the final `Clean` job wasnt necessary either. Heres an example of a deployment triggered by an automatic commit from **Obsidian**. You can see that the Docker image wasnt rebuilt because no new Hugo version was available and the `docker` folder hadnt changed, so the final `Clean` job wasnt necessary either.
![Gitea Actions workflow for blog deployment](images/gitea-actions-deploy-blog-workflow.png) ![Gitea Actions workflow for blog deployment](img/gitea-actions-deploy-blog-workflow.png)
#### Code #### Code

View File

@@ -95,10 +95,10 @@ $ docker compose up -d
``` ```
✅ Atteindre lURL [https://gotify.vezpi.me](https://gotify.vezpi.me) maffiche la page de connexion Gotify : ✅ Atteindre lURL [https://gotify.vezpi.me](https://gotify.vezpi.me) maffiche la page de connexion Gotify :
![Page de connexion Gotify](images/gotify-login-page.png) ![Gotify login page](img/gotify-login-page.png)
Après connexion, jaccède au tableau de bord, sans messages évidemment : Après connexion, jaccède au tableau de bord, sans messages évidemment :
![Tableau de bord Gotify sur une nouvelle installation](images/gotify-dashboard-no-messages.png) ![Gotify dashboard on a fresh installation](img/gotify-dashboard-no-messages.png)
### Créer une Application ### Créer une Application
@@ -107,10 +107,10 @@ Pour permettre lenvoi de messages, je dois dabord créer une application p
- **REST-API** - **REST-API**
Pour le test, jutiliserai la WebUI, je clique sur le bouton `APPS` en haut puis `CREATE APPLICATION`. Je choisis un magnifique nom d'application et une description. Pour le test, jutiliserai la WebUI, je clique sur le bouton `APPS` en haut puis `CREATE APPLICATION`. Je choisis un magnifique nom d'application et une description.
![Créer une application sur Gotify](images/gotify-create-new-application.png) ![Create an application on Gotify](img/gotify-create-new-application.png)
Une fois mon application créée, un token est généré pour celle-ci. Je peux modifier lapplication pour changer quoi que ce soit, je peux aussi uploader une icône. Une fois mon application créée, un token est généré pour celle-ci. Je peux modifier lapplication pour changer quoi que ce soit, je peux aussi uploader une icône.
![Liste des applications Gotify affichant ma nouvelle application Potato](images/gotify-application-list.png) ![Gotify application list showing my new Potato application](img/gotify-application-list.png)
### Tests ### Tests
@@ -122,15 +122,15 @@ curl "https://gotify.vezpi.me/message?token=<apptoken>" -F "title=Cooked!" -F "m
Je reçois instantanément la notification sur mon mobile et dans mon navigateur. Je reçois instantanément la notification sur mon mobile et dans mon navigateur.
Je renvoie un autre message mais avec une priorité plus basse : `-2`. Je ne reçois pas de notification dans mon navigateur, je remarque une légère différence entre les deux messages. Sur mon mobile, seule ma montre la reçoit, je ne la vois pas sur lécran, mais je la retrouve dans le centre de notifications. Je renvoie un autre message mais avec une priorité plus basse : `-2`. Je ne reçois pas de notification dans mon navigateur, je remarque une légère différence entre les deux messages. Sur mon mobile, seule ma montre la reçoit, je ne la vois pas sur lécran, mais je la retrouve dans le centre de notifications.
![Messages reçus sur linterface Web Gotify](images/gotify-messages-received.png) ![Messages received on Gotify WebUI](img/gotify-messages-received.png)
### Application Android ### Application Android
Voici quelques captures décran depuis mon appareil Android : Voici quelques captures décran depuis mon appareil Android :
![Capture décran de lapplication Android Gotify pour la page de connexion](images/gotify-android-first-login.png) ![Capture décran de lapplication Android Gotify pour la page de connexion](img/gotify-android-first-login.png)
Pour une raison inconnue, une notification apparaît aléatoirement pour me dire que je suis connecté à Gotify : Pour une raison inconnue, une notification apparaît aléatoirement pour me dire que je suis connecté à Gotify :
![Capture décran de lapplication Android Gotify avec les messages de test](images/gotify-android-test-messages.png) ![Capture décran de lapplication Android Gotify avec les messages de test](img/gotify-android-test-messages.png)
### Conclusion ### Conclusion
@@ -205,7 +205,7 @@ $ docker compose up -d
``` ```
✅ LURL [https://ntfy.vezpi.me](https://ntfy.vezpi.me) me donne accès au tableau de bord Ntfy : ✅ LURL [https://ntfy.vezpi.me](https://ntfy.vezpi.me) me donne accès au tableau de bord Ntfy :
![Tableau de bord Ntfy](images/ntfy-login-dashboard.png) ![Ntfy dashboard](img/ntfy-login-dashboard.png)
Au départ je nai aucun utilisateur et aucun nest créé par défaut. Comme jai interdit tout accès anonyme dans la config, je dois en créer un. Au départ je nai aucun utilisateur et aucun nest créé par défaut. Comme jai interdit tout accès anonyme dans la config, je dois en créer un.
@@ -228,7 +228,7 @@ Je peux maintenant me connecter à linterface Web, et passer en mode sombre,
### Topics ### Topics
Dans Ntfy, il ny a pas dapplications à créer, mais les messages sont regroupés dans des topics, plus lisibles quun token lors de lenvoi. Une fois le topic créé, je peux changer le nom daffichage ou envoyer des messages de test. Sur linterface Web, cependant, je ne trouve aucune option pour changer licône, alors que cest possible depuis lapplication Android, ce qui nest pas très pratique. Dans Ntfy, il ny a pas dapplications à créer, mais les messages sont regroupés dans des topics, plus lisibles quun token lors de lenvoi. Une fois le topic créé, je peux changer le nom daffichage ou envoyer des messages de test. Sur linterface Web, cependant, je ne trouve aucune option pour changer licône, alors que cest possible depuis lapplication Android, ce qui nest pas très pratique.
![Exemple de messages dans Ntfy](images/ntfy-topic-messages.png) ![Example messages in Ntfy](img/ntfy-topic-messages.png)
### Tests ### Tests
Envoyer un message est en fait plus difficile que prévu. Comme jai activé lauthentification, je dois aussi mauthentifier pour envoyer des messages : Envoyer un message est en fait plus difficile que prévu. Comme jai activé lauthentification, je dois aussi mauthentifier pour envoyer des messages :
@@ -244,7 +244,7 @@ curl \
### Application Android ### Application Android
Voici quelques captures de lapplication Android Ntfy : Voici quelques captures de lapplication Android Ntfy :
![Captures de lapplication Android Ntfy](images/ntfy-android-app.png) ![Captures de lapplication Android Ntfy](img/ntfy-android-app.png)
### Conclusion ### Conclusion
@@ -287,7 +287,7 @@ $ curl -u gitea_blog:<password> -d "Message test from gitea_blog!" https://ntfy.
{"id":"xIgwz9dr1w9Z","time":1749587681,"expires":1749630881,"event":"message","topic":"blog","message":"Message test from gitea_blog!"} {"id":"xIgwz9dr1w9Z","time":1749587681,"expires":1749630881,"event":"message","topic":"blog","message":"Message test from gitea_blog!"}
``` ```
![Test denvoi de messages sur le topic blog avec Ntfy ](images/ntfy-testing-gitea-blog-user.png) ![Test denvoi de messages sur le topic blog avec Ntfy ](img/ntfy-testing-gitea-blog-user.png)
✅ Message reçu ! ✅ Message reçu !
Je tente aussi un envoi sur mon topic de test : Je tente aussi un envoi sur mon topic de test :
@@ -319,7 +319,7 @@ Maintenant que mes utilisateurs sont prêts, je veux ajouter un job `Notify` dan
#### Créer un Secret #### Créer un Secret
Pour permettre à mon Gitea Runner dutiliser lutilisateur `gitea_blog` dans ses jobs, je veux créer un secret. Jexplore le dépôt Gitea `Blog` dans `Settings`, puis `Actions` > `Secrets` > `Add Secret`. Jy mets la valeur du secret au format `<utilisateur>:<password>` : Pour permettre à mon Gitea Runner dutiliser lutilisateur `gitea_blog` dans ses jobs, je veux créer un secret. Jexplore le dépôt Gitea `Blog` dans `Settings`, puis `Actions` > `Secrets` > `Add Secret`. Jy mets la valeur du secret au format `<utilisateur>:<password>` :
![Ajout dun secret dans le dépôt Gitea du blog](images/gitea-blog-ntfy-credentials.png) ![Add a secret in the blog Gitea repository](img/gitea-blog-ntfy-credentials.png)
### Écrire le Code `Notify` ### Écrire le Code `Notify`
@@ -369,7 +369,7 @@ Si quelque chose échoue, je veux être notifié sur mon mobile avec une priorit
``` ```
✅ Test des deux cas, fonctionne comme prévu : ✅ Test des deux cas, fonctionne comme prévu :
![Vérification des deux scénarios de test dans Ntfy WebUI](images/ntfy-testing-blog-notifications.png) ![Checking both test scenario in Ntfy WebUI](img/ntfy-testing-blog-notifications.png)
## Conclusion ## Conclusion

View File

@@ -95,10 +95,10 @@ $ docker compose up -d
``` ```
✅ Reaching the URL https://gotify.vezpi.me gives me the Gotify login page: ✅ Reaching the URL https://gotify.vezpi.me gives me the Gotify login page:
![Gotify login page](images/gotify-login-page.png) ![Gotify login page](img/gotify-login-page.png)
After login, I can access the dashboard, with no messages obviously: After login, I can access the dashboard, with no messages obviously:
![Gotify dashboard on a fresh installation](images/gotify-dashboard-no-messages.png) ![Gotify dashboard on a fresh installation](img/gotify-dashboard-no-messages.png)
### Creating an Application ### Creating an Application
@@ -107,10 +107,10 @@ To allow messages to be pushed, I before need to create an application for which
- **REST-API** - **REST-API**
For the test, I will use the WebUI, I click on the `APPS` button at the top and `CREATE APPLICATION`. I choose a wonderful application name and description. For the test, I will use the WebUI, I click on the `APPS` button at the top and `CREATE APPLICATION`. I choose a wonderful application name and description.
![Create an application on Gotify](images/gotify-create-new-application.png) ![Create an application on Gotify](img/gotify-create-new-application.png)
Once my application in created, a token is generated for it. I can edit the application to change anything, I can also upload an icon. Once my application in created, a token is generated for it. I can edit the application to change anything, I can also upload an icon.
![Gotify application list showing my new Potato application](images/gotify-application-list.png) ![Gotify application list showing my new Potato application](img/gotify-application-list.png)
### Testing ### Testing
@@ -122,15 +122,15 @@ curl "https://gotify.vezpi.me/message?token=<apptoken>" -F "title=Cooked!" -F "m
I instantly received the notification on my mobile and on my browser. I instantly received the notification on my mobile and on my browser.
I retried to send another message but with a lower priority: `-2`. I didn't get any notification in my browser, I see a slight differences between the two messages. On my mobile, only my watch received it, I don't see it on my screen, but I can find it on the notification center. I retried to send another message but with a lower priority: `-2`. I didn't get any notification in my browser, I see a slight differences between the two messages. On my mobile, only my watch received it, I don't see it on my screen, but I can find it on the notification center.
![Messages received on Gotify WebUI](images/gotify-messages-received.png) ![Messages received on Gotify WebUI](img/gotify-messages-received.png)
### Android App ### Android App
Here some screenshots from my Android device: Here some screenshots from my Android device:
![Gotify's Android App screenshot for the login page](images/gotify-android-first-login.png) ![Capture décran de lapplication Android Gotify pour la page de connexion](img/gotify-android-first-login.png)
For some reason, a notification randomly pops up to tell me that I'm connected to Gotify: For some reason, a notification randomly pops up to tell me that I'm connected to Gotify:
![Gotify's Android App screenshot for test messages](images/gotify-android-test-messages.png) ![Capture décran de lapplication Android Gotify avec les messages de test](img/gotify-android-test-messages.png)
### Conclusion ### Conclusion
On the [documentation](https://gotify.net/docs/msgextras), I found some extras features, like adding images or click actions. In summary, it does the job, that's it. Easy installation process, the utilization is not hard, but I need to create an application for a token, then add this token anytime I want to push messages there. On the [documentation](https://gotify.net/docs/msgextras), I found some extras features, like adding images or click actions. In summary, it does the job, that's it. Easy installation process, the utilization is not hard, but I need to create an application for a token, then add this token anytime I want to push messages there.
@@ -204,7 +204,7 @@ $ docker compose up -d
``` ```
✅ The URL https://ntfy.vezpi.me gives me to the Ntfy dashboard: ✅ The URL https://ntfy.vezpi.me gives me to the Ntfy dashboard:
![Ntfy dashboard](images/ntfy-login-dashboard.png) ![Ntfy dashboard](img/ntfy-login-dashboard.png)
At start I don't have any user and none is created by default, as I denied all access to anonymous in the config, I need to create one. At start I don't have any user and none is created by default, as I denied all access to anonymous in the config, I need to create one.
@@ -227,7 +227,7 @@ I can now login into the WebUI, and I can now switch to dark mode, my eyes are g
### Topics ### Topics
In Ntfy there are no applications to create, but messages are grouped into topics, more readable than a token when sending messages. When the topic is created I can change the display name or send test messages. On the WebUI though I don't find any option to change the icon, where I can find this option in the Android App which is not really convenient. In Ntfy there are no applications to create, but messages are grouped into topics, more readable than a token when sending messages. When the topic is created I can change the display name or send test messages. On the WebUI though I don't find any option to change the icon, where I can find this option in the Android App which is not really convenient.
![Example messages in Ntfy](images/ntfy-topic-messages.png) ![Example messages in Ntfy](img/ntfy-topic-messages.png)
### Testing ### Testing
@@ -244,7 +244,7 @@ curl \
### Android App ### Android App
Here are some screenshots of Ntfy Android App: Here are some screenshots of Ntfy Android App:
![Ntfy's Android App screenshots](images/ntfy-android-app.png) ![Captures de lapplication Android Ntfy](img/ntfy-android-app.png)
### Conclusion ### Conclusion
Ntfy is a beautiful application with a really strong [documentation](https://docs.ntfy.sh/). The possibilities are endless and the list of integration is impressive. The installation was not hard but required a bit of more setup. The needs for CLI to configure users and permissions is not really convenient. Ntfy is a beautiful application with a really strong [documentation](https://docs.ntfy.sh/). The possibilities are endless and the list of integration is impressive. The installation was not hard but required a bit of more setup. The needs for CLI to configure users and permissions is not really convenient.
@@ -286,7 +286,7 @@ $ curl -u gitea_blog:<password> -d "Message test from gitea_blog!" https://ntfy.
{"id":"xIgwz9dr1w9Z","time":1749587681,"expires":1749630881,"event":"message","topic":"blog","message":"Message test from gitea_blog!"} {"id":"xIgwz9dr1w9Z","time":1749587681,"expires":1749630881,"event":"message","topic":"blog","message":"Message test from gitea_blog!"}
``` ```
![Testing Ntfy messages in the blog topic](images/ntfy-testing-gitea-blog-user.png) ![Test denvoi de messages sur le topic blog avec Ntfy ](img/ntfy-testing-gitea-blog-user.png)
✅ Message received! ✅ Message received!
I also try to send a message on my test topic: I also try to send a message on my test topic:
@@ -318,7 +318,7 @@ Now my users are setup, I want to add a `Notify` job in my CI/CD pipeline for th
#### Create a Secret #### Create a Secret
To allow my Gitea Runner to use my `gitea_blog` user in its job, I want to create a secret. I explore the `Blog` Gitea repository `Settings`, then `Actions` > `Secrets` > `Add Secret`. Here I set the secret value with the `<user>:<password>` format: To allow my Gitea Runner to use my `gitea_blog` user in its job, I want to create a secret. I explore the `Blog` Gitea repository `Settings`, then `Actions` > `Secrets` > `Add Secret`. Here I set the secret value with the `<user>:<password>` format:
![Add a secret in the blog Gitea repository](images/gitea-blog-ntfy-credentials.png) ![Add a secret in the blog Gitea repository](img/gitea-blog-ntfy-credentials.png)
### Write the `Notify` Code ### Write the `Notify` Code
@@ -368,7 +368,7 @@ If anything fails, I want to be notified on my mobile with higher priority. Ntfy
``` ```
✅ Testing both cases, work as expected: ✅ Testing both cases, work as expected:
![Checking both test scenario in Ntfy WebUI](images/ntfy-testing-blog-notifications.png) ![Checking both test scenario in Ntfy WebUI](img/ntfy-testing-blog-notifications.png)
## Conclusion ## Conclusion

View File

@@ -37,7 +37,7 @@ Node-RED ne remplace pas Home Assistant, il le renforce. Je ne détaillerai pas
## Ancien Workflow ## Ancien Workflow
Javais déjà une solution plutôt efficace pour contrôler ma climatisation via Home Assistant et Node-RED, mais je voulais laméliorer pour quelle prenne aussi en compte le taux dhumidité dans lappartement. Mon workflow actuel, bien quil fonctionne, nétait pas vraiment évolutif et assez difficile à maintenir : Javais déjà une solution plutôt efficace pour contrôler ma climatisation via Home Assistant et Node-RED, mais je voulais laméliorer pour quelle prenne aussi en compte le taux dhumidité dans lappartement. Mon workflow actuel, bien quil fonctionne, nétait pas vraiment évolutif et assez difficile à maintenir :
![Ancien workflow Node-RED pour contrôler la climatisation](images/node-red-ha-ac-automation-before.png) ![Ancien workflow Node-RED pour contrôler la climatisation](img/node-red-ha-ac-automation-before.png)
## Nouveau Workflow ## Nouveau Workflow
@@ -54,12 +54,12 @@ Pour maider à faire tout ça, jutilise 4 [capteurs de température et d
### Workflow ### Workflow
Laissez-moi vous présenter mon nouveau workflow de climatisation dans Node-RED, et vous expliquer en détail comment il fonctionne : Laissez-moi vous présenter mon nouveau workflow de climatisation dans Node-RED, et vous expliquer en détail comment il fonctionne :
![Nouveau workflow Node-RED pour la climatisation](images/node-red-new-ac-workflow-with-legend.png) ![New Node-RED air conditioning workflow](img/node-red-new-ac-workflow-with-legend.png)
#### #### 1. Capteurs de Température #### #### 1. Capteurs de Température
Dans le premier nœud, jai regroupé tous les capteurs thermiques dans un seul `trigger state node`, en ajoutant non seulement la température mais aussi le taux dhumidité géré par chaque capteur. Ce nœud contient donc une liste de 8 entités (2 pour chaque capteur). À chaque fois quune de ces 8 valeurs change, le nœud est déclenché : Dans le premier nœud, jai regroupé tous les capteurs thermiques dans un seul `trigger state node`, en ajoutant non seulement la température mais aussi le taux dhumidité géré par chaque capteur. Ce nœud contient donc une liste de 8 entités (2 pour chaque capteur). À chaque fois quune de ces 8 valeurs change, le nœud est déclenché :
![Nœud trigger state dans Node-RED avec les 8 entités](images/node-red-temperature-sensors-trigger-node.png) ![Nœud trigger state dans Node-RED avec les 8 entités](img/node-red-temperature-sensors-trigger-node.png)
Chacun de mes capteurs thermiques porte un nom de couleur en français, car ils ont tous un autocollant coloré pour les distinguer : Chacun de mes capteurs thermiques porte un nom de couleur en français, car ils ont tous un autocollant coloré pour les distinguer :
- **Jaune** : Salon - **Jaune** : Salon
@@ -93,7 +93,7 @@ return msg;
``` ```
Pour le dernier nœud, dans la majorité des cas, les capteurs envoient deux messages simultanés : lun pour la température, lautre pour lhumidité. Jai donc ajouté un `join node` pour fusionner ces deux messages sils sont envoyés dans la même seconde : Pour le dernier nœud, dans la majorité des cas, les capteurs envoient deux messages simultanés : lun pour la température, lautre pour lhumidité. Jai donc ajouté un `join node` pour fusionner ces deux messages sils sont envoyés dans la même seconde :
![Nœud join dans Node-RED pour fusionner température et humidité](images/node-red-temperature-sensor-join-node.png) ![Join node in Node-RED to merge temperature and humidity](img/node-red-temperature-sensor-join-node.png)
#### 2. Notification #### 2. Notification
@@ -132,17 +132,17 @@ return null; // Don't send anything now
``` ```
Le second nœud est un `call service node` qui envoie une notification sur mon téléphone Android avec les informations fournies : Le second nœud est un `call service node` qui envoie une notification sur mon téléphone Android avec les informations fournies :
![Nœud call service dans Node-RED pour envoyer une notification](images/node-red-call-service-node-notification.png) ![Node-RED call service node for notification](img/node-red-call-service-node-notification.png)
#### 3. Curseurs de Température #### 3. Curseurs de Température
Pour pouvoir ajuster la température sans avoir à modifier tout le workflow, jai créé deux entrées (ou helper) Home Assistant, de type number, pour chaque unité de climatisation, ce qui me fait un total de 6 entrées : Pour pouvoir ajuster la température sans avoir à modifier tout le workflow, jai créé deux entrées (ou helper) Home Assistant, de type number, pour chaque unité de climatisation, ce qui me fait un total de 6 entrées :
![Curseur de température dans Home Assistant pour chaque unité](images/home-assistant-temperature-room-sliders.png) ![Curseur de température dans Home Assistant pour chaque unité](img/home-assistant-temperature-room-sliders.png)
Ces valeurs représentent la température de base utilisée pour le calcul des seuils, en fonction des offsets que je détaillerai plus loin. Ces valeurs représentent la température de base utilisée pour le calcul des seuils, en fonction des offsets que je détaillerai plus loin.
Le premier nœud est un `trigger state node` qui regroupe les 6 entités. Si je modifie lune de ces valeurs, le nœud est déclenché : Le premier nœud est un `trigger state node` qui regroupe les 6 entités. Si je modifie lune de ces valeurs, le nœud est déclenché :
![Nœud trigger state dans Node-RED pour les curseurs](images/node-red-trigger-state-mode-for-sliders.png) ![Node-RED trigger state node for sliders](img/node-red-trigger-state-mode-for-sliders.png)
Le deuxième nœud est un `function node`, qui permet de déterminer la pièce concernée : Le deuxième nœud est un `function node`, qui permet de déterminer la pièce concernée :
```js ```js
@@ -164,17 +164,17 @@ return msg;
Dans Home Assistant, jutilise dautres entrées, mais cette fois sous forme de booléens. Le plus important est celui dédié à la climatisation, qui me permet de désactiver manuellement tout le workflow. Jen ai dautres qui sont automatisés, par exemple pour le moment de la journée ou la détection de présence à la maison. Dans Home Assistant, jutilise dautres entrées, mais cette fois sous forme de booléens. Le plus important est celui dédié à la climatisation, qui me permet de désactiver manuellement tout le workflow. Jen ai dautres qui sont automatisés, par exemple pour le moment de la journée ou la détection de présence à la maison.
Jutilise un autre `trigger state node` qui regroupe tous mes interrupteurs sous forme de booléens, y compris un bouton de test utilisé pour le débogage : Jutilise un autre `trigger state node` qui regroupe tous mes interrupteurs sous forme de booléens, y compris un bouton de test utilisé pour le débogage :
![Nœud trigger state dans Node-RED pour les interrupteurs](images/node-red-trigger-state-node-toggles.png) ![Node-RED trigger state node for toggles](img/node-red-trigger-state-node-toggles.png)
Comme ces interrupteurs impactent tout lappartement (et non une seule unité), le nœud suivant est un `change node` qui définit la valeur de la pièce à `partout` : Comme ces interrupteurs impactent tout lappartement (et non une seule unité), le nœud suivant est un `change node` qui définit la valeur de la pièce à `partout` :
![Nœud change dans Node-RED pour définir la pièce sur partout](images/node-red-change-node-room-partout.png) ![Node-RED change node to set room to partout](img/node-red-change-node-room-partout.png)
#### 5. Fenêtres #### 5. Fenêtres
Les derniers déclencheurs sont les fenêtres. Si jouvre ou ferme une fenêtre située près dune unité, cela active le workflow. Jai des capteurs douverture sur certaines fenêtres, mais pour lunité du couloir, jutilise létat des fenêtres Velux. Certaines pièces ayant plusieurs fenêtres, jai créé une entrée de type groupe pour les regrouper. Les derniers déclencheurs sont les fenêtres. Si jouvre ou ferme une fenêtre située près dune unité, cela active le workflow. Jai des capteurs douverture sur certaines fenêtres, mais pour lunité du couloir, jutilise létat des fenêtres Velux. Certaines pièces ayant plusieurs fenêtres, jai créé une entrée de type groupe pour les regrouper.
Le premier nœud est le dernier `trigger state node`. La valeur retournée est une string quil faudra ensuite convertir en booléen : Le premier nœud est le dernier `trigger state node`. La valeur retournée est une string quil faudra ensuite convertir en booléen :
![Nœud trigger state dans Node-RED pour les fenêtres](images/node-red-trigger-state-node-windows.png) ![Node-RED trigger state node for windows](img/node-red-trigger-state-node-windows.png)
Juste après, un autre `function node` permet didentifier la pièce concernée : Juste après, un autre `function node` permet didentifier la pièce concernée :
```js ```js
@@ -195,20 +195,20 @@ return msg;
Quand jouvre une fenêtre, ce nest pas forcément pour la laisser ouverte longtemps. Je peux simplement faire sortir le chat ou jeter un œil au portail. Je ne veux pas que la climatisation se coupe dès que jouvre une fenêtre. Pour contourner cela, jai mis en place un watchdog pour chaque unité, afin de retarder lenvoi du message pendant un certain temps. Quand jouvre une fenêtre, ce nest pas forcément pour la laisser ouverte longtemps. Je peux simplement faire sortir le chat ou jeter un œil au portail. Je ne veux pas que la climatisation se coupe dès que jouvre une fenêtre. Pour contourner cela, jai mis en place un watchdog pour chaque unité, afin de retarder lenvoi du message pendant un certain temps.
Le premier nœud est un `switch node`. En fonction de la pièce transmise par le nœud précédent, il envoie le message au _watchdog_ correspondant : Le premier nœud est un `switch node`. En fonction de la pièce transmise par le nœud précédent, il envoie le message au _watchdog_ correspondant :
![Nœud “switch” dans Node-RED basé sur la pièce pour diriger vers le bon _watchdog_](images/node-red-switch-node-room-selector-watchdog.png) ![Node-RED switch node based on the room for the watchdog](img/node-red-switch-node-room-selector-watchdog.png)
Viennent ensuite les _watchdogs_, des `trigger nodes`, qui retardent le message pendant un certain temps, et prolongent ce délai si un autre message est reçu entre-temps : Viennent ensuite les _watchdogs_, des `trigger nodes`, qui retardent le message pendant un certain temps, et prolongent ce délai si un autre message est reçu entre-temps :
![Nœud “trigger” dans Node-RED pour la surveillance des fenêtres](images/node-red-trigger-node-window-watchdog.png) ![Node-RED trigger node for window watchdog](img/node-red-trigger-node-window-watchdog.png)
#### 7. Climatisation Activée ? #### 7. Climatisation Activée ?
Tous ces déclencheurs arrivent maintenant dans la chaîne de traitement, qui va déterminer ce que le système doit faire. Mais avant cela, on vérifie si lautomatisation est activée. Jai ajouté ce kill switch au cas où, même si je lutilise rarement. Tous ces déclencheurs arrivent maintenant dans la chaîne de traitement, qui va déterminer ce que le système doit faire. Mais avant cela, on vérifie si lautomatisation est activée. Jai ajouté ce kill switch au cas où, même si je lutilise rarement.
Le premier nœud est un `delay node` qui régule le débit des messages entrants à 1 message par seconde : Le premier nœud est un `delay node` qui régule le débit des messages entrants à 1 message par seconde :
![Nœud delay dans Node-RED pour limiter le débit à 1 message par seconde](images/node-red-delay-node-1-msg-per-second.png) ![Node-RED delay node to limit the rate to 1 message per second](img/node-red-delay-node-1-msg-per-second.png)
Le deuxième nœud est un `current state node` qui vérifie si le booléen `climatisation` est activé : Le deuxième nœud est un `current state node` qui vérifie si le booléen `climatisation` est activé :
![Nœud current state dans Node-RED pour létat de la climatisation](images/node-red-current-state-node-climatisation-enabled.png) ![Node-RED current state node for climatisation](img/node-red-current-state-node-climatisation-enabled.png)
#### 8. Configuration des pièces #### 8. Configuration des pièces
@@ -223,7 +223,7 @@ Les unités de climatisation disposent de 4 modes :
Pour déterminer quel mode utiliser, jutilise des seuils pour chaque mode et la vitesse de ventilation, avec différents offsets selon la situation. Je peux ainsi définir un offset spécifique la nuit ou en cas dabsence. Je peux aussi définir un offset sur `disabled`, ce qui forcera larrêt de lunité. Pour déterminer quel mode utiliser, jutilise des seuils pour chaque mode et la vitesse de ventilation, avec différents offsets selon la situation. Je peux ainsi définir un offset spécifique la nuit ou en cas dabsence. Je peux aussi définir un offset sur `disabled`, ce qui forcera larrêt de lunité.
Le premier nœud est un `switch node`, basé sur la valeur `room`, qui oriente le message vers la configuration associée. Si la pièce est `partout`, le message est dupliqué vers les 3 configurations de pièce : Le premier nœud est un `switch node`, basé sur la valeur `room`, qui oriente le message vers la configuration associée. Si la pièce est `partout`, le message est dupliqué vers les 3 configurations de pièce :
![Nœud “switch” dans Node-RED pour sélectionner la configuration en fonction de la pièce](images/node-red-switch-node-room-config.png) ![Node-RED switch node for room configuration](img/node-red-switch-node-room-config.png)
Il est ensuite connecté à un `change node`, qui ajoute la configuration dans `room_config`. Voici un exemple avec la configuration du salon : Il est ensuite connecté à un `change node`, qui ajoute la configuration dans `room_config`. Voici un exemple avec la configuration du salon :
```json ```json
@@ -445,13 +445,13 @@ return msg;
``` ```
Le troisième nœud est un `filter node`, qui ignore les messages suivants ayant un contenu similaire : Le troisième nœud est un `filter node`, qui ignore les messages suivants ayant un contenu similaire :
![Nœud de filtrage dans Node-RED pour bloquer les messages identiques](images/node-red-filter-node-blocker.png) ![Node-RED filter node to block similar message](img/node-red-filter-node-blocker.png)
Le quatrième nœud vérifie si un verrou est actif à laide dun `current state node`. On regarde si le minuteur associé à lunité est inactif. Si ce nest pas le cas, le message est ignoré : Le quatrième nœud vérifie si un verrou est actif à laide dun `current state node`. On regarde si le minuteur associé à lunité est inactif. Si ce nest pas le cas, le message est ignoré :
![Nœud current state dans Node-RED pour vérifier le verrou temporaire](images/node-red-current-state-node-lock-timer.png) ![Node-RED current state node for timer lock](img/node-red-current-state-node-lock-timer.png)
Le dernier nœud est un autre `current state node` qui permet de récupérer létat actuel de lunité et ses propriétés : Le dernier nœud est un autre `current state node` qui permet de récupérer létat actuel de lunité et ses propriétés :
![Nœud current state dans Node-RED pour obtenir létat actuel de lunité](images/node-red-current-state-node-get-unit-state.png) ![Node-RED current state node to get current unit state](img/node-red-current-state-node-get-unit-state.png)
#### 10. État Cible #### 10. État Cible
@@ -608,17 +608,17 @@ return msg;
#### 11. Choix de l'Action #### 11. Choix de l'Action
En fonction de laction à effectuer, le `switch node` va router le message vers le bon chemin : En fonction de laction à effectuer, le `switch node` va router le message vers le bon chemin :
![Node-RED `switch node` pour sélectionner laction](images/node-red-switch-node-select-action.png) ![Node-RED `switch node` pour sélectionner laction](img/node-red-switch-node-select-action.png)
#### 12. Démarrage #### 12. Démarrage
Lorsque laction est `start`, il faut dabord allumer lunité. Cela prend entre 20 et 40 secondes selon le modèle, et une fois démarrée, lunité est verrouillée pendant un court laps de temps pour éviter les messages suivants. Lorsque laction est `start`, il faut dabord allumer lunité. Cela prend entre 20 et 40 secondes selon le modèle, et une fois démarrée, lunité est verrouillée pendant un court laps de temps pour éviter les messages suivants.
Le premier nœud est un `call service node` utilisant le service `turn_on` sur lunité de climatisation : Le premier nœud est un `call service node` utilisant le service `turn_on` sur lunité de climatisation :
![Nœud `call service` dans Node-RED avec le service `turn_on`](images/node-red-call-service-node-turn-on.png) ![Node-RED call service node with turn_on service](img/node-red-call-service-node-turn-on.png)
Le second nœud est un autre `call service node` qui va démarrer un minuteur de verrouillage (lock timer) pour cette unité pendant 45 secondes : Le second nœud est un autre `call service node` qui va démarrer un minuteur de verrouillage (lock timer) pour cette unité pendant 45 secondes :
![Nœud `call service` dans Node-RED pour démarrer le minuteur de lunité](images/node-red-call-service-node-start-timer.png) ![Node-RED call service node to start the unit timer](img/node-red-call-service-node-start-timer.png)
Le dernier est un `delay node` de 5 secondes, pour laisser le temps à lintégration Daikin de Home Assistant de refléter le nouvel état. Le dernier est un `delay node` de 5 secondes, pour laisser le temps à lintégration Daikin de Home Assistant de refléter le nouvel état.
@@ -629,12 +629,12 @@ Le dernier est un `delay node` de 5 secondes, pour laisser le temps à linté
Laction `change` est utilisée pour passer dun mode à un autre, mais aussi juste après lallumage. Laction `change` est utilisée pour passer dun mode à un autre, mais aussi juste après lallumage.
Le premier nœud est un `call service node` utilisant le service `set_hvac_mode` sur lunité de climatisation : Le premier nœud est un `call service node` utilisant le service `set_hvac_mode` sur lunité de climatisation :
![Nœud `call service` dans Node-RED avec le service `set_hvac_mode`](images/node-red-call-service-node-set-hvac-mode.png) ![Node-RED call service node with set_hvac_mode service](img/node-red-call-service-node-set-hvac-mode.png)
Le nœud suivant est un `delay node` de 5 secondes. Le nœud suivant est un `delay node` de 5 secondes.
Le dernier vérifie, avec un `switch node`, si la température cible doit être définie. Cela nest nécessaire que pour les modes `cool` et `heat` : Le dernier vérifie, avec un `switch node`, si la température cible doit être définie. Cela nest nécessaire que pour les modes `cool` et `heat` :
![Nœud `switch` dans Node-RED pour définir la température cible](images/node-red-switch-node-set-temp.png) ![Node-RED switch node for set_temp](img/node-red-switch-node-set-temp.png)
--- ---
@@ -643,7 +643,7 @@ Le dernier vérifie, avec un `switch node`, si la température cible doit être
La température cible est uniquement pertinente pour les modes `cool` et `heat`. Avec une climatisation classique, vous définissez une température à atteindre — cest exactement ce quon fait ici. Mais comme chaque unité utilise son propre capteur interne pour vérifier cette température, je ne leur fais pas vraiment confiance. Si la température cible est déjà atteinte selon lunité, elle ne soufflera plus du tout. La température cible est uniquement pertinente pour les modes `cool` et `heat`. Avec une climatisation classique, vous définissez une température à atteindre — cest exactement ce quon fait ici. Mais comme chaque unité utilise son propre capteur interne pour vérifier cette température, je ne leur fais pas vraiment confiance. Si la température cible est déjà atteinte selon lunité, elle ne soufflera plus du tout.
Le premier nœud est un autre `call service node` utilisant le service `set_temperature` : Le premier nœud est un autre `call service node` utilisant le service `set_temperature` :
![Nœud `call service` dans Node-RED avec le service `set_temperature`](images/node-red-call-service-node-set-temperature-service.png) ![Node-RED call service node with set_temperature service](img/node-red-call-service-node-set-temperature-service.png)
Encore une fois, ce nœud est suivi dun `delay node` de 5 secondes. Encore une fois, ce nœud est suivi dun `delay node` de 5 secondes.
@@ -652,20 +652,20 @@ Encore une fois, ce nœud est suivi dun `delay node` de 5 secondes.
Laction `check` est utilisée presque tout le temps. Elle consiste uniquement à vérifier et comparer la vitesse de ventilation souhaitée, et à la modifier si nécessaire. Laction `check` est utilisée presque tout le temps. Elle consiste uniquement à vérifier et comparer la vitesse de ventilation souhaitée, et à la modifier si nécessaire.
Le premier nœud est un `switch node` qui vérifie si la valeur `speed` est définie : Le premier nœud est un `switch node` qui vérifie si la valeur `speed` est définie :
![Nœud switch dans Node-RED pour tester si la vitesse est définie](images/node-red-switch-node-fan-speed.png) ![Node-RED switch node to test if speed is defined](img/node-red-switch-node-fan-speed.png)
Le deuxième est un autre `switch node` qui compare la valeur `speed` avec la vitesse actuelle : Le deuxième est un autre `switch node` qui compare la valeur `speed` avec la vitesse actuelle :
![Nœud switch dans Node-RED pour comparer la vitesse](images/node-red-switch-node-compare-speed.png) ![Node-Red switch node to compare speed](img/node-red-switch-node-compare-speed.png)
Enfin, le dernier nœud est un `call service node` utilisant le service `set_fan_mode` pour définir la vitesse du ventilateur : Enfin, le dernier nœud est un `call service node` utilisant le service `set_fan_mode` pour définir la vitesse du ventilateur :
![Nœud `call service` dans Node-RED avec le service `set_fan_mode`](images/node-red-call-service-node-set-fan-mode.png) ![Node-RED call service node with set_fan_mode](img/node-red-call-service-node-set-fan-mode.png)
#### 16. Arrêt #### 16. Arrêt
Lorsque laction est `stop`, lunité de climatisation est simplement arrêtée. Lorsque laction est `stop`, lunité de climatisation est simplement arrêtée.
Le premier nœud est un `call service node` utilisant le service `turn_off` : Le premier nœud est un `call service node` utilisant le service `turn_off` :
![Nœud `call service` dans Node-RED avec le service `turn_off`](images/node-red-call-service-node-turn-off.png) ![Node-RED call service node with turn_off service](img/node-red-call-service-node-turn-off.png)
Le deuxième nœud est un autre `call service node` qui va démarrer le minuteur de verrouillage de cette unité pour 45 secondes. Le deuxième nœud est un autre `call service node` qui va démarrer le minuteur de verrouillage de cette unité pour 45 secondes.
@@ -675,7 +675,7 @@ Parfois, pour une raison ou une autre, on souhaite utiliser la climatisation man
Node-RED utilise son propre utilisateur dans Home Assistant, donc si une unité change détat sans cet utilisateur, cest quune intervention manuelle a eu lieu. Node-RED utilise son propre utilisateur dans Home Assistant, donc si une unité change détat sans cet utilisateur, cest quune intervention manuelle a eu lieu.
Le premier nœud est un `trigger state node`, qui envoie un message dès quune unité AC change détat : Le premier nœud est un `trigger state node`, qui envoie un message dès quune unité AC change détat :
![node-red-trigger-state-unit-change.png](images/node-red-trigger-state-unit-change.png) ![node-red-trigger-state-unit-change.png](img/node-red-trigger-state-unit-change.png)
Le deuxième est un `function node` qui associe lunité avec son minuteur : Le deuxième est un `function node` qui associe lunité avec son minuteur :
```js ```js
@@ -690,13 +690,13 @@ return msg;
``` ```
Le troisième est un `switch node` qui laisse passer le message uniquement si le `user_id` **nest pas** celui de Node-RED : Le troisième est un `switch node` qui laisse passer le message uniquement si le `user_id` **nest pas** celui de Node-RED :
![Nœud `switch` dans Node-RED pour vérifier que ce nest pas lID utilisateur Node-RED](images/node-red-switch-node-user-id.png) ![Node-RED switch node not specific user_id](img/node-red-switch-node-user-id.png)
Le quatrième est un autre `switch node` qui vérifie que le champ `user_id` **est bien défini** : Le quatrième est un autre `switch node` qui vérifie que le champ `user_id` **est bien défini** :
![Nœud `switch` dans Node-RED pour vérifier que `user_id` nest pas nul](images/node-red-switch-node-check-user-id.png) ![Node-RED switch node check user_id not null](img/node-red-switch-node-check-user-id.png)
Enfin, le dernier nœud est un `call service node` utilisant le service `start` sur le minuteur de lunité, avec sa durée par défaut (60 minutes) : Enfin, le dernier nœud est un `call service node` utilisant le service `start` sur le minuteur de lunité, avec sa durée par défaut (60 minutes) :
![Nœud `call service` dans Node-RED pour démarrer le minuteur avec la durée par défaut](images/node-red-call-service-node-start-unit-timer.png) ![Node-RED call service node start timer with default duration](img/node-red-call-service-node-start-unit-timer.png)
## TL;DR ## TL;DR

View File

@@ -37,7 +37,7 @@ Node-RED does not replace Home Assistant, it empowers it. I won't cover the inst
## Previous Workflow ## Previous Workflow
I was already having a good solution to control my AC from Home Assistant with Node-RED, but I wanted to enhance it to also handle the humidity level at home. My current workflow, despite being functional, was not really scalable and quite hard to maintain: I was already having a good solution to control my AC from Home Assistant with Node-RED, but I wanted to enhance it to also handle the humidity level at home. My current workflow, despite being functional, was not really scalable and quite hard to maintain:
![Old Node-RED workflow to control air conditioning](images/node-red-ha-ac-automation-before.png) ![Ancien workflow Node-RED pour contrôler la climatisation](img/node-red-ha-ac-automation-before.png)
## New Workflow ## New Workflow
@@ -54,12 +54,12 @@ To help me achieve that, I'm using 4 [Aqara temperature and humidity sensors](ht
### Workflow ### Workflow
Let me introduce my new AC workflow within Node-RED and explain what it does in detail: Let me introduce my new AC workflow within Node-RED and explain what it does in detail:
![New Node-RED air conditioning workflow](images/node-red-new-ac-workflow-with-legend.png) ![New Node-RED air conditioning workflow](img/node-red-new-ac-workflow-with-legend.png)
#### 1. Temperature Sensors #### 1. Temperature Sensors
In the first node, I combined all the temperature sensors together in one `trigger state node`, but I also added humidity levels in addition to the temperature, managed by the sensor. The node then contains 8 entities in a list (2 for each of my sensor). Each time one value change out of these 8 entities, the node is triggered: In the first node, I combined all the temperature sensors together in one `trigger state node`, but I also added humidity levels in addition to the temperature, managed by the sensor. The node then contains 8 entities in a list (2 for each of my sensor). Each time one value change out of these 8 entities, the node is triggered:
![Trigger state node in Node-RED with all 8 entities](images/node-red-temperature-sensors-trigger-node.png) ![Nœud trigger state dans Node-RED avec les 8 entités](img/node-red-temperature-sensors-trigger-node.png)
Each of my temperature sensors are named with a color in French, because each has its own color sticker to distinguish them: Each of my temperature sensors are named with a color in French, because each has its own color sticker to distinguish them:
- **Jaune**: Living room - **Jaune**: Living room
@@ -93,7 +93,7 @@ return msg;
``` ```
For the last node, most of the time, the sensors will send two messages at the same time, one containing the temperature value and the other, the humidity level. I added a `join node` to combined the two messages if they are sent within the same second: For the last node, most of the time, the sensors will send two messages at the same time, one containing the temperature value and the other, the humidity level. I added a `join node` to combined the two messages if they are sent within the same second:
![Join node in Node-RED to merge temperature and humidity](images/node-red-temperature-sensor-join-node.png) ![Join node in Node-RED to merge temperature and humidity](img/node-red-temperature-sensor-join-node.png)
#### 2. Notification #### 2. Notification
@@ -132,17 +132,17 @@ return null; // Don't send anything now
``` ```
The second node is a `call service node` which send a notification on my Android device with the value given: The second node is a `call service node` which send a notification on my Android device with the value given:
![Node-RED call service node for notification](images/node-red-call-service-node-notification.png) ![Node-RED call service node for notification](img/node-red-call-service-node-notification.png)
#### 3. Temperature Sliders #### 3. Temperature Sliders
To have a control over the temperature without having to change the workflow, I created two Home Assistant helper, as number, which I can adjust for each unit, giving me 6 helpers in total: To have a control over the temperature without having to change the workflow, I created two Home Assistant helper, as number, which I can adjust for each unit, giving me 6 helpers in total:
![Home Assistant temperature slider for each unit](images/home-assistant-temperature-room-sliders.png) ![Curseur de température dans Home Assistant pour chaque unité](img/home-assistant-temperature-room-sliders.png)
These values are the base temperature used for the calculation of the threshold, depending off the offset which I will detail further. These values are the base temperature used for the calculation of the threshold, depending off the offset which I will detail further.
The first node is a `trigger state node`, with all 6 entities combined. If I change one value, the node is triggered: The first node is a `trigger state node`, with all 6 entities combined. If I change one value, the node is triggered:
![Node-RED trigger state node for sliders](images/node-red-trigger-state-mode-for-sliders.png) ![Node-RED trigger state node for sliders](img/node-red-trigger-state-mode-for-sliders.png)
The second node is a `function node`, to determine the room affected: The second node is a `function node`, to determine the room affected:
```js ```js
@@ -164,17 +164,17 @@ return msg;
In Home Assistant, I'm using other helper but as boolean, the most important is the AC one, where I can manually disable the whole workflow. I have other which are automated, for the time of the day or for detect presence at home. In Home Assistant, I'm using other helper but as boolean, the most important is the AC one, where I can manually disable the whole workflow. I have other which are automated, for the time of the day or for detect presence at home.
I have another `trigger state node` with all my toggles as boolean, including a test button, for debug purpose: I have another `trigger state node` with all my toggles as boolean, including a test button, for debug purpose:
![Node-RED trigger state node for toggles](images/node-red-trigger-state-node-toggles.png) ![Node-RED trigger state node for toggles](img/node-red-trigger-state-node-toggles.png)
As toggles affect the whole apartment and not a single unit, the next node is a `change node`, which set the room value to `partout` (everywhere): As toggles affect the whole apartment and not a single unit, the next node is a `change node`, which set the room value to `partout` (everywhere):
![Node-RED change node to set room to partout](images/node-red-change-node-room-partout.png) ![Node-RED change node to set room to partout](img/node-red-change-node-room-partout.png)
#### 5. Windows #### 5. Windows
The last triggers are my windows, if I open or close a window next to my unit, it triggers the workflow. I have door sensor for some of my doors, but for the hallway unit, I'm using the Velux windows state. Some rooms have more than one, I created a group helper for them. The last triggers are my windows, if I open or close a window next to my unit, it triggers the workflow. I have door sensor for some of my doors, but for the hallway unit, I'm using the Velux windows state. Some rooms have more than one, I created a group helper for them.
The first node is the last `trigger state node`, the returned value is a string which I will have to convert later into boolean: The first node is the last `trigger state node`, the returned value is a string which I will have to convert later into boolean:
![Node-RED trigger state node for windows](images/node-red-trigger-state-node-windows.png) ![Node-RED trigger state node for windows](img/node-red-trigger-state-node-windows.png)
Connected to it, again a `function node` to select the affect room: Connected to it, again a `function node` to select the affect room:
```js ```js
@@ -195,20 +195,20 @@ return msg;
When I open a window, it is not necessarily to let it open for a long time. I could just let the cat out or having a look at my portal. I don't want my AC tuned off as soon as open it. To workaround that I created a watchdog for each unit, to delay the message for some time. When I open a window, it is not necessarily to let it open for a long time. I could just let the cat out or having a look at my portal. I don't want my AC tuned off as soon as open it. To workaround that I created a watchdog for each unit, to delay the message for some time.
The first node is a `switch node`, based on the room given by the previous node, it will send the message to the associated watchdog: The first node is a `switch node`, based on the room given by the previous node, it will send the message to the associated watchdog:
![Node-RED switch node based on the room for the watchdog](images/node-red-switch-node-room-selector-watchdog.png) ![Node-RED switch node based on the room for the watchdog](img/node-red-switch-node-room-selector-watchdog.png)
After are the watchdogs, `trigger nodes`, which will delay the message by some time and extend the delay if another message if received: After are the watchdogs, `trigger nodes`, which will delay the message by some time and extend the delay if another message if received:
![Node-RED trigger node for window watchdog](images/node-red-trigger-node-window-watchdog.png) ![Node-RED trigger node for window watchdog](img/node-red-trigger-node-window-watchdog.png)
#### 7. AC Enabled ? #### 7. AC Enabled ?
All these triggers are now entering the computing pipeline, to determine what the system must do with the action. But before, it is checking if the automation is even enabled. I add this kill switch, just in case, but I rarely use it anyway. All these triggers are now entering the computing pipeline, to determine what the system must do with the action. But before, it is checking if the automation is even enabled. I add this kill switch, just in case, but I rarely use it anyway.
The first node is a `delay node` which regulate the rate of every incoming messages to 1 per second: The first node is a `delay node` which regulate the rate of every incoming messages to 1 per second:
![Node-RED delay node to limit the rate to 1 message per second](images/node-red-delay-node-1-msg-per-second.png) ![Node-RED delay node to limit the rate to 1 message per second](img/node-red-delay-node-1-msg-per-second.png)
The second node is a `current state node` which checks if the `climatisation` boolean is enabled: The second node is a `current state node` which checks if the `climatisation` boolean is enabled:
![Node-RED current state node for climatisation](images/node-red-current-state-node-climatisation-enabled.png) ![Node-RED current state node for climatisation](img/node-red-current-state-node-climatisation-enabled.png)
#### 8. Room Configuration #### 8. Room Configuration
The idea here is to attach the configuration of the room to the message. Each room have their own configuration, which unit is used, which sensors and more importantly, when should they be turned on and off. The idea here is to attach the configuration of the room to the message. Each room have their own configuration, which unit is used, which sensors and more importantly, when should they be turned on and off.
@@ -222,7 +222,7 @@ AC units have 4 mode which can be used:
To determine which mode should be used, I'm using threshold for each mode and unit fan's speed, with different offset depending the situation. I can then define a offset during the night or when I'm away. I can also set the offset to `disabled`, which will force the unit to shut down. To determine which mode should be used, I'm using threshold for each mode and unit fan's speed, with different offset depending the situation. I can then define a offset during the night or when I'm away. I can also set the offset to `disabled`, which will force the unit to shut down.
The first node is a `switch node`, based on the `room` value, which will route the message to the associated room configuration. When the room is `partout` (everywhere), the message is split to all 3 room configuration: The first node is a `switch node`, based on the `room` value, which will route the message to the associated room configuration. When the room is `partout` (everywhere), the message is split to all 3 room configuration:
![Node-RED switch node for room configuration](images/node-red-switch-node-room-config.png) ![Node-RED switch node for room configuration](img/node-red-switch-node-room-config.png)
It is connected to a `change node` which will attach the configuration to the `room_config`, here an example with the living-room configuration: It is connected to a `change node` which will attach the configuration to the `room_config`, here an example with the living-room configuration:
```json ```json
@@ -443,13 +443,13 @@ return msg;
``` ```
The third node is a `filter node`, which drops subsequent messages with similar payload: The third node is a `filter node`, which drops subsequent messages with similar payload:
![Node-RED filter node to block similar message](images/node-red-filter-node-blocker.png) ![Node-RED filter node to block similar message](img/node-red-filter-node-blocker.png)
The fourth node checks if any lock is set, with a `current state node`, we verify if the timer associated to the unit is idle. If not, the message is discarded: The fourth node checks if any lock is set, with a `current state node`, we verify if the timer associated to the unit is idle. If not, the message is discarded:
![Node-RED current state node for timer lock](images/node-red-current-state-node-lock-timer.png) ![Node-RED current state node for timer lock](img/node-red-current-state-node-lock-timer.png)
The last node is another `current state node` which will fetch the unit state and properties: The last node is another `current state node` which will fetch the unit state and properties:
![Node-RED current state node to get current unit state](images/node-red-current-state-node-get-unit-state.png) ![Node-RED current state node to get current unit state](img/node-red-current-state-node-get-unit-state.png)
#### 10. Target State #### 10. Target State
@@ -606,17 +606,17 @@ return msg;
#### 11. Action Switch #### 11. Action Switch
Based on the action to take, the `switch node` will route the message accordingly: Based on the action to take, the `switch node` will route the message accordingly:
![Node-RED switch node to select the action](images/node-red-switch-node-select-action.png) ![Node-RED `switch node` pour sélectionner laction](img/node-red-switch-node-select-action.png)
#### 12. Start #### 12. Start
When the action is `start`, we first need to turn the unit online, while this takes between 20 to 40 seconds depending on the unit model, it is also locking the unit for a short period for future messages. When the action is `start`, we first need to turn the unit online, while this takes between 20 to 40 seconds depending on the unit model, it is also locking the unit for a short period for future messages.
The first node is a `call service node` using the `turn_on` service on the AC unit: The first node is a `call service node` using the `turn_on` service on the AC unit:
![Node-RED call service node with turn_on service](images/node-red-call-service-node-turn-on.png) ![Node-RED call service node with turn_on service](img/node-red-call-service-node-turn-on.png)
The second node is another `call service node` which will start the lock timer of this unit for 45 seconds: The second node is another `call service node` which will start the lock timer of this unit for 45 seconds:
![Node-RED call service node to start the unit timer](images/node-red-call-service-node-start-timer.png) ![Node-RED call service node to start the unit timer](img/node-red-call-service-node-start-timer.png)
The last one is a `delay node` of 5 seconds, to give the time to the Home Assistant Daikin integration to resolve the new state. The last one is a `delay node` of 5 seconds, to give the time to the Home Assistant Daikin integration to resolve the new state.
@@ -625,19 +625,19 @@ The last one is a `delay node` of 5 seconds, to give the time to the Home Assist
The `change` action is used to change from one mode to another, but also used right after the start action. The `change` action is used to change from one mode to another, but also used right after the start action.
The first node is a `call service node` using `the set_hvac_mode` service on the AC unit: The first node is a `call service node` using `the set_hvac_mode` service on the AC unit:
![Node-RED call service node with set_hvac_mode service](images/node-red-call-service-node-set-hvac-mode.png) ![Node-RED call service node with set_hvac_mode service](img/node-red-call-service-node-set-hvac-mode.png)
The following node is another delay of 5 seconds. The following node is another delay of 5 seconds.
The last one verify with a `switch node` if the target temperature needs to be set, this is only required for the modes `cool` and `heat`: The last one verify with a `switch node` if the target temperature needs to be set, this is only required for the modes `cool` and `heat`:
![Node-RED switch node for set_temp](images/node-red-switch-node-set-temp.png) ![Node-RED switch node for set_temp](img/node-red-switch-node-set-temp.png)
#### 14. Set Target Temperature #### 14. Set Target Temperature
The target temperature is only relevant for `cool` and `heat` mode, when you use a normal AC unit, you define a temperature to reach. This is exactly what is defined here. But because each unit is using its own internal sensor to verify, I don't trust it. If the value is already reached, the unit won't blow anything. The target temperature is only relevant for `cool` and `heat` mode, when you use a normal AC unit, you define a temperature to reach. This is exactly what is defined here. But because each unit is using its own internal sensor to verify, I don't trust it. If the value is already reached, the unit won't blow anything.
The first node is another `call service node` using the `set_temperature` service: The first node is another `call service node` using the `set_temperature` service:
![Node-RED call service node with set_temperature service](images/node-red-call-service-node-set-temperature-service.png) ![Node-RED call service node with set_temperature service](img/node-red-call-service-node-set-temperature-service.png)
Again, this node is followed by a `delay node` of 5 seconds Again, this node is followed by a `delay node` of 5 seconds
@@ -646,20 +646,20 @@ Again, this node is followed by a `delay node` of 5 seconds
The `check` action is almost used everytime, it is actually only checks and compare the desired fan speed, it changes the fan speed if needed. The `check` action is almost used everytime, it is actually only checks and compare the desired fan speed, it changes the fan speed if needed.
The first node is a `switch node` which verify if the `speed` is defined: The first node is a `switch node` which verify if the `speed` is defined:
![Node-RED switch node to test if speed is defined](images/node-red-switch-node-fan-speed.png) ![Node-RED switch node to test if speed is defined](img/node-red-switch-node-fan-speed.png)
The second is another `switch node` to compare the `speed` value with the current speed: The second is another `switch node` to compare the `speed` value with the current speed:
![Node-Red switch node to compare speed](images/node-red-switch-node-compare-speed.png) ![Node-Red switch node to compare speed](img/node-red-switch-node-compare-speed.png)
Finally the last node is a `call service node` using the `set_fan_mode` to set the fan speed: Finally the last node is a `call service node` using the `set_fan_mode` to set the fan speed:
![Node-RED call service node with set_fan_mode](images/node-red-call-service-node-set-fan-mode.png) ![Node-RED call service node with set_fan_mode](img/node-red-call-service-node-set-fan-mode.png)
#### 16. Stop #### 16. Stop
When the `action` is stop, the AC unit is simply turned off When the `action` is stop, the AC unit is simply turned off
The first node is a `call service noded` using the service `turn_off`: The first node is a `call service noded` using the service `turn_off`:
![Node-RED call service node with turn_off service](images/node-red-call-service-node-turn-off.png) ![Node-RED call service node with turn_off service](img/node-red-call-service-node-turn-off.png)
The second node is another `call service node` which will start the lock timer of this unit for 45 seconds The second node is another `call service node` which will start the lock timer of this unit for 45 seconds
@@ -668,7 +668,7 @@ The second node is another `call service node` which will start the lock timer o
Sometime, for some reason, we want to use the AC manually. When we do, we don't want the workflow to change our manual setting, at least for some time. Node-RED is using its own user in Home Assistant, so when an AC unit change state without this user, this was manually done. Sometime, for some reason, we want to use the AC manually. When we do, we don't want the workflow to change our manual setting, at least for some time. Node-RED is using its own user in Home Assistant, so when an AC unit change state without this user, this was manually done.
The first node is a `trigger state node`, which will send a message when any AC unit is changing state: The first node is a `trigger state node`, which will send a message when any AC unit is changing state:
![node-red-trigger-state-unit-change.png](images/node-red-trigger-state-unit-change.png) ![node-red-trigger-state-unit-change.png](img/node-red-trigger-state-unit-change.png)
The second is a `function node` which willassociate the unit with its timer: The second is a `function node` which willassociate the unit with its timer:
```js ```js
@@ -683,13 +683,13 @@ return msg;
``` ```
The third is a `switch node` that will let through the message when the user_id is not the Node-RED user's one: The third is a `switch node` that will let through the message when the user_id is not the Node-RED user's one:
![Node-RED switch node not specific user_id](images/node-red-switch-node-user-id.png) ![Node-RED switch node not specific user_id](img/node-red-switch-node-user-id.png)
The fourth is another `switch node` which checks if there are any `user_id`: The fourth is another `switch node` which checks if there are any `user_id`:
![Node-RED switch node check user_id not null](images/node-red-switch-node-check-user-id.png) ![Node-RED switch node check user_id not null](img/node-red-switch-node-check-user-id.png)
Lastly, the final node is a `call service node` using `start` service on the unit's timer with its default duration (60 minutes): Lastly, the final node is a `call service node` using `start` service on the unit's timer with its default duration (60 minutes):
![Node-RED call service node start timer with default duration](images/node-red-call-service-node-start-unit-timer.png) ![Node-RED call service node start timer with default duration](img/node-red-call-service-node-start-unit-timer.png)
## TL;DR ## TL;DR

View File

@@ -594,7 +594,7 @@ vm_ip = "192.168.66.159"
✅ La VM est maintenant prête ! ✅ La VM est maintenant prête !
![VM sur l'interface Proxmox déployé avec le module Terraform](images/proxmox-vm-deployed-using-terraform-module.png) ![VM on Proxmox WebUI deployed using a Terraform module](img/proxmox-vm-deployed-using-terraform-module.png)
🕗 _Ne faites pas attention à luptime, jai pris la capture décran le lendemain._ 🕗 _Ne faites pas attention à luptime, jai pris la capture décran le lendemain._

View File

@@ -589,7 +589,7 @@ vm_ip = "192.168.66.159"
✅ The VM is now ready! ✅ The VM is now ready!
![VM on Proxmox WebUI deployed using a Terraform module](images/proxmox-vm-deployed-using-terraform-module.png) ![VM on Proxmox WebUI deployed using a Terraform module](img/proxmox-vm-deployed-using-terraform-module.png)
🕗 *Don't pay attention to the uptime, I took the screenshot the next day* 🕗 *Don't pay attention to the uptime, I took the screenshot the next day*

View File

@@ -97,31 +97,31 @@ BGP est désactivé par défaut, aussi bien sur OPNsense que sur Cilium. Activon
Daprès la [documentation officielle OPNsense](https://docs.opnsense.org/manual/dynamic_routing.html#bgp-section), lactivation de BGP nécessite dinstaller un plugin. Daprès la [documentation officielle OPNsense](https://docs.opnsense.org/manual/dynamic_routing.html#bgp-section), lactivation de BGP nécessite dinstaller un plugin.
Va dans `System` > `Firmware` > `Plugins` et installe le plugin **os-frr** : Va dans `System` > `Firmware` > `Plugins` et installe le plugin **os-frr** :
![ ](images/opnsense-add-os-frr-plugin.png) ![ ](img/opnsense-add-os-frr-plugin.png)
Installer le plugin `os-frr` dans OPNsense Installer le plugin `os-frr` dans OPNsense
Une fois installé, active le plugin dans `Routing` > `General` : Une fois installé, active le plugin dans `Routing` > `General` :
![ ](images/opnsense-enable-routing-frr-plugin.png) ![ ](img/opnsense-enable-routing-frr-plugin.png)
Activer le routage dans OPNsense Activer le routage dans OPNsense
Ensuite, rends-toi dans la section **BGP**. Dans longlet **General** : Ensuite, rends-toi dans la section **BGP**. Dans longlet **General** :
- Coche la case pour activer BGP. - Coche la case pour activer BGP.
- Défini ton **ASN BGP**. Jai choisi `64512`, le premier ASN privé de la plage réservée (voir [ASN table](https://en.wikipedia.org/wiki/Autonomous_system_\(Internet\)#ASN_Table)) : - Défini ton **ASN BGP**. Jai choisi `64512`, le premier ASN privé de la plage réservée (voir [ASN table](https://en.wikipedia.org/wiki/Autonomous_system_\(Internet\)#ASN_Table)) :
![Configuration générale de BGP dans OPNsense](images/opnsense-enable-bgp.png) ![ ](img/opnsense-enable-bgp.png)
Ajoute ensuite tes voisins BGP. Je ne fais le peering quavec mes **nœuds workers** (puisque seuls eux hébergent des workloads). Pour chaque voisin : Ajoute ensuite tes voisins BGP. Je ne fais le peering quavec mes **nœuds workers** (puisque seuls eux hébergent des workloads). Pour chaque voisin :
- Mets lIP du nœud dans `Peer-IP`. - Mets lIP du nœud dans `Peer-IP`.
- Utilise `64513` comme **Remote AS** (celui de Cilium). - Utilise `64513` comme **Remote AS** (celui de Cilium).
- Configure `Update-Source Interface` sur `Lab`. - Configure `Update-Source Interface` sur `Lab`.
- Coche `Next-Hop-Self`. - Coche `Next-Hop-Self`.
![Configuration dun voisin BGP dans OPNsense](images/opnsense-bgp-create-neighbor.png) ![ ](img/opnsense-bgp-create-neighbor.png)
Voici la liste de mes voisins une fois configurés : Voici la liste de mes voisins une fois configurés :
![ ](images/opnsense-bgp-neighbor-list.png) ![ ](img/opnsense-bgp-neighbor-list.png)
Liste des voisins BGP Liste des voisins BGP
Noublie pas la règle firewall pour autoriser BGP (port `179/TCP`) depuis le VLAN **Lab** vers le firewall : Noublie pas la règle firewall pour autoriser BGP (port `179/TCP`) depuis le VLAN **Lab** vers le firewall :
![ ](images/opnsense-create-firewall-rule-bgp-peering.png) ![ ](img/opnsense-create-firewall-rule-bgp-peering.png)
Autoriser TCP/179 de Lab vers OPNsense Autoriser TCP/179 de Lab vers OPNsense
#### Dans Cilium #### Dans Cilium
@@ -294,7 +294,7 @@ test-lb LoadBalancer 10.100.167.198 192.168.55.20 80:31350/TCP 169m
Le service a récupéré la première IP du pool défini : `192.168.55.20`. Le service a récupéré la première IP du pool défini : `192.168.55.20`.
Depuis nimporte quel appareil du LAN, on peut tester laccès sur le port 80 : Depuis nimporte quel appareil du LAN, on peut tester laccès sur le port 80 :
![Test du service LoadBalancer avec BGP](images/k8s-test-loadbalancer-service-with-bgp.png) ![Test LoadBalancer service with BGP](img/k8s-test-loadbalancer-service-with-bgp.png)
✅ Notre pod est joignable via une IP `LoadBalancer` routée en BGP. Première étape réussie ! ✅ Notre pod est joignable via une IP `LoadBalancer` routée en BGP. Première étape réussie !
@@ -451,10 +451,10 @@ Ensuite, japplique le manifeste `Ingress` pour exposer le service en HTTP.
Comme jutilise le plugin **Caddy** dans OPNsense, jai encore besoin dun routage local de type Layer 4 pour rediriger le trafic de `test.vezpi.me` vers ladresse IP de lIngress Controller (`192.168.55.55`). Je crée donc une nouvelle règle dans le plugin Caddy. Comme jutilise le plugin **Caddy** dans OPNsense, jai encore besoin dun routage local de type Layer 4 pour rediriger le trafic de `test.vezpi.me` vers ladresse IP de lIngress Controller (`192.168.55.55`). Je crée donc une nouvelle règle dans le plugin Caddy.
![Créer un routeur Layer4 dans le plugin Caddy dOPNsense](images/opnsense-caddy-create-layer4-route-http.png) ![Create Layer4 router in Caddy plugin for OPNsense](img/opnsense-caddy-create-layer4-route-http.png)
Puis je teste laccès dans le navigateur : Puis je teste laccès dans le navigateur :
![ ](images/ingress-controller-nginx-test-simple-webserver.png) ![ ](img/ingress-controller-nginx-test-simple-webserver.png)
Test dun Ingress en HTTP Test dun Ingress en HTTP
✅ Mon pod est désormais accessible via son URL HTTP en utilisant un Ingress. Deuxième étape complétée ! ✅ Mon pod est désormais accessible via son URL HTTP en utilisant un Ingress. Deuxième étape complétée !
@@ -558,7 +558,7 @@ En arrière-plan, Cert-Manager suit ce flux pour émettre le certificat :
- LIngress utilise automatiquement ce Secret pour servir en HTTPS. - LIngress utilise automatiquement ce Secret pour servir en HTTPS.
✅ Une fois ce processus terminé, votre Ingress est sécurisé avec un certificat TLS. ✅ Une fois ce processus terminé, votre Ingress est sécurisé avec un certificat TLS.
![Certificat TLS validé avec le serveur de staging de Lets Encrypt](images/k8s-test-deploy-service-tls-certificate-staging-lets-encrypt.png) ![Certificat TLS validé avec le serveur de staging de Lets Encrypt](img/k8s-test-deploy-service-tls-certificate-staging-lets-encrypt.png)
### Passer aux certificats de production ### Passer aux certificats de production
@@ -614,7 +614,7 @@ kubectl delete secret test-vezpi-me-tls
``` ```
🎉 Mon `Ingress` est désormais sécurisé avec un certificat TLS valide délivré par Lets Encrypt. Les requêtes vers `https://test.vezpi.me` sont chiffrées de bout en bout et routées par le NGINX Ingress Controller jusquà mon pod `nginx` : 🎉 Mon `Ingress` est désormais sécurisé avec un certificat TLS valide délivré par Lets Encrypt. Les requêtes vers `https://test.vezpi.me` sont chiffrées de bout en bout et routées par le NGINX Ingress Controller jusquà mon pod `nginx` :
![Ingress HTTPS avec certificat validé par Lets Encrypt](images/k8s-deploy-test-service-tls-certificate-lets-encrypt.png) ![Ingress HTTPS avec certificat validé par Lets Encrypt](img/k8s-deploy-test-service-tls-certificate-lets-encrypt.png)
--- ---

View File

@@ -93,17 +93,17 @@ BGP is disabled by default on both OPNsense and Cilium. Lets enable it on bot
According to the [official OPNsense documentation](https://docs.opnsense.org/manual/dynamic_routing.html#bgp-section), enabling BGP requires installing a plugin. According to the [official OPNsense documentation](https://docs.opnsense.org/manual/dynamic_routing.html#bgp-section), enabling BGP requires installing a plugin.
Head to `System` > `Firmware` > `Plugins` and install the `os-frr` plugin: Head to `System` > `Firmware` > `Plugins` and install the `os-frr` plugin:
![ ](images/opnsense-add-os-frr-plugin.png) ![ ](img/opnsense-add-os-frr-plugin.png)
Install `os-frr` plugin in OPNsense Install `os-frr` plugin in OPNsense
Once installed, enable the plugin under `Routing` > `General`: Once installed, enable the plugin under `Routing` > `General`:
![ ](images/opnsense-enable-routing-frr-plugin.png) ![ ](img/opnsense-enable-routing-frr-plugin.png)
Enable routing in OPNsense Enable routing in OPNsense
Then navigate to the `BGP` section. In the **General** tab: Then navigate to the `BGP` section. In the **General** tab:
- Tick the box to enable BGP. - Tick the box to enable BGP.
- Set your **BGP ASN**. I used `64512`, the first private ASN from the reserved range (see [ASN table](https://en.wikipedia.org/wiki/Autonomous_system_\(Internet\)#ASN_Table)): - Set your **BGP ASN**. I used `64512`, the first private ASN from the reserved range (see [ASN table](https://en.wikipedia.org/wiki/Autonomous_system_\(Internet\)#ASN_Table)):
![ ](images/opnsense-enable-bgp.png) ![ ](img/opnsense-enable-bgp.png)
General BGP configuration in OPNsense General BGP configuration in OPNsense
Now create your BGP neighbors. Im only peering with my **worker nodes** (since only they run workloads). For each neighbor: Now create your BGP neighbors. Im only peering with my **worker nodes** (since only they run workloads). For each neighbor:
@@ -111,15 +111,15 @@ Now create your BGP neighbors. Im only peering with my **worker nodes** (sinc
- Use `64513` as the **Remote AS** (Ciliums ASN) - Use `64513` as the **Remote AS** (Ciliums ASN)
- Set `Update-Source Interface` to `Lab` - Set `Update-Source Interface` to `Lab`
- Tick `Next-Hop-Self`: - Tick `Next-Hop-Self`:
![ ](images/opnsense-bgp-create-neighbor.png) ![ ](img/opnsense-bgp-create-neighbor.png)
BGP neighbor configuration in OPNsense BGP neighbor configuration in OPNsense
Heres how my neighbors list looks once complete: Heres how my neighbors list looks once complete:
![ ](images/opnsense-bgp-neighbor-list.png) ![ ](img/opnsense-bgp-neighbor-list.png)
BGP neighbor list BGP neighbor list
Dont forget to create a firewall rule allowing BGP (port `179/TCP`) from the **Lab** VLAN to the firewall: Dont forget to create a firewall rule allowing BGP (port `179/TCP`) from the **Lab** VLAN to the firewall:
![ ](images/opnsense-create-firewall-rule-bgp-peering.png) ![ ](img/opnsense-create-firewall-rule-bgp-peering.png)
Allow TCP/179 from Lab to OPNsense Allow TCP/179 from Lab to OPNsense
#### In Cilium #### In Cilium
@@ -292,7 +292,7 @@ test-lb LoadBalancer 10.100.167.198 192.168.55.20 80:31350/TCP 169m
The service got the first IP from our defined pool: `192.168.55.20`. The service got the first IP from our defined pool: `192.168.55.20`.
Now from any device on the LAN, try to reach that IP on port 80: Now from any device on the LAN, try to reach that IP on port 80:
![Test LoadBalancer service with BGP](images/k8s-test-loadbalancer-service-with-bgp.png) ![Test LoadBalancer service with BGP](img/k8s-test-loadbalancer-service-with-bgp.png)
✅ Our pod is reachable through BGP-routed `LoadBalancer` IP, first step successful! ✅ Our pod is reachable through BGP-routed `LoadBalancer` IP, first step successful!
@@ -449,10 +449,10 @@ Then I apply the `Ingress` manifest as shown earlier to expose the service over
Since I'm using the Caddy plugin on OPNsense, I still need a local Layer 4 route to forward traffic for `test.vezpi.me` to the NGINX Ingress Controller IP (`192.168.55.55`). I simply create a new rule in the Caddy plugin. Since I'm using the Caddy plugin on OPNsense, I still need a local Layer 4 route to forward traffic for `test.vezpi.me` to the NGINX Ingress Controller IP (`192.168.55.55`). I simply create a new rule in the Caddy plugin.
![Create Layer4 router in Caddy plugin for OPNsense](images/opnsense-caddy-create-layer4-route-http.png) ![Create Layer4 router in Caddy plugin for OPNsense](img/opnsense-caddy-create-layer4-route-http.png)
Now lets test it in the browser: Now lets test it in the browser:
![ ](images/ingress-controller-nginx-test-simple-webserver.png) ![ ](img/ingress-controller-nginx-test-simple-webserver.png)
Test Ingress on HTTP Test Ingress on HTTP
✅ Our pod is now reachable on its HTTP URL using an Ingress. Second step complete! ✅ Our pod is now reachable on its HTTP URL using an Ingress. Second step complete!
@@ -556,7 +556,7 @@ Behind the scenes, Cert-Manager goes through this workflow to issue the certific
- The Ingress automatically uses the Secret to serve HTTPS. - The Ingress automatically uses the Secret to serve HTTPS.
✅ Once this process completes, your Ingress is secured with a TLS certificate. ✅ Once this process completes, your Ingress is secured with a TLS certificate.
![TLS certificate verified with the staging Let's Encrypt server](images/k8s-test-deploy-service-tls-certificate-staging-lets-encrypt.png) ![Certificat TLS validé avec le serveur de staging de Lets Encrypt](img/k8s-test-deploy-service-tls-certificate-staging-lets-encrypt.png)
### Switch to Production Certificates ### Switch to Production Certificates
@@ -612,7 +612,7 @@ kubectl delete secret test-vezpi-me-tls
``` ```
🎉 My `Ingress` is now secured with a valid TLS certificate from Lets Encrypt. Requests to `https://test.vezpi.me` are encrypted end-to-end and routed by the NGINX Ingress Controller to my `nginx` pod: 🎉 My `Ingress` is now secured with a valid TLS certificate from Lets Encrypt. Requests to `https://test.vezpi.me` are encrypted end-to-end and routed by the NGINX Ingress Controller to my `nginx` pod:
![Ingress HTTPS with certificate verified by Let's Encrypt](images/k8s-deploy-test-service-tls-certificate-lets-encrypt.png) ![Ingress HTTPS avec certificat validé par Lets Encrypt](img/k8s-deploy-test-service-tls-certificate-lets-encrypt.png)
--- ---

View File

@@ -28,5 +28,3 @@ Checklist:
- [x] Checked - [x] Checked
Look this is ~~strike~~ ! Look this is ~~strike~~ !
What else? A fix!

View File

@@ -21,6 +21,9 @@ rm -rf "$CLONE_DIR"
echo "- Cloning $REPO_URL (branch: $BRANCH)..." echo "- Cloning $REPO_URL (branch: $BRANCH)..."
git clone --recurse-submodules --branch "$BRANCH" "$REPO_URL" "$CLONE_DIR" git clone --recurse-submodules --branch "$BRANCH" "$REPO_URL" "$CLONE_DIR"
# Patch .Site.Data references not yet fixed in Stack theme
sed -i 's/\.Site\.Data/hugo.Data/g' "$CLONE_DIR/themes/stack/layouts/_partials/article/components/photoswipe.html"
# Generate static files with hugo # Generate static files with hugo
echo "- Building site with Hugo v$HUGO_VERSION in $HUGO_DEST..." echo "- Building site with Hugo v$HUGO_VERSION in $HUGO_DEST..."
hugo --source "$CLONE_DIR" --destination "$HUGO_DEST" --baseURL="https://${URL}" ${DRAFTS} --logLevel info --cleanDestinationDir --gc --panicOnWarning --printI18nWarnings hugo --source "$CLONE_DIR" --destination "$HUGO_DEST" --baseURL="https://${URL}" ${DRAFTS} --logLevel info --cleanDestinationDir --gc --panicOnWarning --printI18nWarnings

View File

@@ -1,14 +1,14 @@
baseURL: "https://blog.vezpi.com/" baseURL: "https://blog.vezpi.com/"
title: "Vezpi Lab" title: "Vezpi Lab"
theme: "stack" theme: "stack"
locale: "en-us" languageCode: "en-us"
enableGitInfo: true enableGitInfo: true
DefaultContentLanguage: "en" DefaultContentLanguage: "en"
defaultContentLanguageInSubdir: true defaultContentLanguageInSubdir: true
languages: languages:
en: en:
label: English languageName: English
weight: 1 weight: 1
menu: menu:
main: main:
@@ -54,7 +54,7 @@ languages:
lastUpdated: "Jan 2, 2006" lastUpdated: "Jan 2, 2006"
fr: fr:
label: Français languageName: Français
weight: 2 weight: 2
menu: menu:
main: main:

View File

@@ -76,8 +76,3 @@ footer:
designedBy: designedBy:
other: " " other: " "
pagination:
jumpToPage: "Jump to page"
jump: "Go"
pressEnter: "Press Enter to jump"

View File

@@ -75,8 +75,3 @@ footer:
designedBy: designedBy:
other: " " other: " "
pagination:
jumpToPage: "Aller à la page"
jump: "Aller"
pressEnter: "Presser Entrée pour aller"

View File

@@ -1,13 +1,8 @@
{{- $IsList := .IsList -}}
{{- $Page := .Page -}}
<div class="article-details"> <div class="article-details">
{{ if $Page.Params.categories }} {{ if .Params.categories }}
<header class="article-category"> <header class="article-category">
{{ range ($Page.GetTerms "tags") }} {{ range (.GetTerms "tags") }}
{{ $color := partial "helper/color-from-str" .LinkTitle }} <a href="{{ .RelPermalink }}" {{ with .Params.style }}style="background-color: {{ .background }}; color: {{ .color }};"{{ end }}>
{{ $BackgroundColor := default $color.BackgroundColor .Params.style.background }}
{{ $TextColor := default $color.TextColor .Params.style.color }}
<a href="{{ $Page.RelPermalink }}" style="background-color: {{ $BackgroundColor | safeCSS }}; color: {{ $TextColor | safeCSS }};">
{{ .LinkTitle }} {{ .LinkTitle }}
</a> </a>
{{ end }} {{ end }}
@@ -16,51 +11,50 @@
<div class="article-title-wrapper"> <div class="article-title-wrapper">
<h2 class="article-title"> <h2 class="article-title">
<a href="{{ $Page.RelPermalink }}"> <a href="{{ .RelPermalink }}">
{{- $Page.Title -}} {{- .Title -}}
</a> </a>
</h2> </h2>
{{ with $Page.Params.description }} {{ with .Params.description }}
<h3 class="article-subtitle"> <h3 class="article-subtitle">
{{ . }} {{ . }}
</h3> </h3>
{{ end }} {{ end }}
</div> </div>
{{ $showReadingTime := $Page.Params.readingTime | default ($Page.Site.Params.article.readingTime) }} {{ $showReadingTime := .Params.readingTime | default (.Site.Params.article.readingTime) }}
{{ $showDate := not $Page.Date.IsZero }} {{ $showDate := not .Date.IsZero }}
{{ $showTime := or $showDate $showReadingTime }} {{ $showFooter := or $showDate $showReadingTime }}
{{ if $showFooter }}
{{ if $showTime }} <footer class="article-time">
<footer class="article-meta"> {{ if $showDate }}
<div class="inline-meta"> <div>
{{ if $showDate }}
{{ partial "helper/icon" "date" }} {{ partial "helper/icon" "date" }}
<time class="article-time--published" datetime='{{ $Page.Date.Format "2006-01-02T15:04:05Z07:00" }}'> <time class="article-time--published">
{{- $Page.Date | time.Format $Page.Site.Params.dateFormat.published -}} {{- .Date | time.Format (or .Site.Params.dateFormat.published "Jan 02, 2006") -}}
</time> </time>
{{ end }} </div>
{{ end }}
{{ if $showReadingTime }} {{ if $showReadingTime }}
{{ partial "helper/icon" "clock" }} <div>
{{ partial "helper/icon" "stopwatch" }}
<time class="article-time--reading"> <time class="article-time--reading">
{{ T "article.readingTime" $Page.ReadingTime }} {{ T "article.readingTime" .ReadingTime }}
</time> </time>
{{ end }} </div>
{{ end }}
{{- $date := $Page.Date.Format "20060102" | int -}} {{- $date := .Date.Format "20060102" | int -}}
{{- $lastmod := $Page.Lastmod.Format "20060102" | int -}} {{- $lastmod := .Lastmod.Format "20060102" | int -}}
{{- if gt $lastmod $date -}} {{- if gt $lastmod $date -}}
<div class="article-lastmod"> <div class="article-lastmod">
{{ partial "helper/icon" "refresh" }} {{ partial "helper/icon" "refresh" }}
<time> <time>
{{ T "article.lastUpdatedOn" }} {{ $Page.Lastmod | time.Format ( or $Page.Site.Params.dateFormat.lastUpdated "Jan 02, 2006 15:04 MST" ) }} {{ T "article.lastUpdatedOn" }} {{ .Lastmod | time.Format ( or .Site.Params.dateFormat.lastUpdated "Jan 02, 2006 15:04 MST" ) }}
</time> </time>
</div> </div>
{{- end -}} {{- end -}}
</div>
</footer> </footer>
{{ end }} {{ end }}
</div> </div>

View File

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View File

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 554 KiB

After

Width:  |  Height:  |  Size: 554 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 MiB

After

Width:  |  Height:  |  Size: 2.0 MiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View File

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

View File

Before

Width:  |  Height:  |  Size: 386 KiB

After

Width:  |  Height:  |  Size: 386 KiB

View File

Before

Width:  |  Height:  |  Size: 295 KiB

After

Width:  |  Height:  |  Size: 295 KiB

View File

Before

Width:  |  Height:  |  Size: 568 KiB

After

Width:  |  Height:  |  Size: 568 KiB

View File

Before

Width:  |  Height:  |  Size: 507 KiB

After

Width:  |  Height:  |  Size: 507 KiB

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Some files were not shown because too many files have changed in this diff Show More