Portail

Le portail parent du module crèche permet aux parents de communiquer avec les structures. Il est divisé en deux modules indépendant activables par configuration. Les deux modules sont :

  • Préinscription qui concerne la partie permettant au parent de déclarer un nouvel enfant et ses besoins
  • Communication qui permet au parent de gérer son profil et l’activité de ses enfants

Toutes ces actions sont accessibles par un unique portail lié à une ou plusieurs sociétés SAINet. L’objectif de cette documentation, dédiée aux utilisateurs de type Super-administrateurs est d’exprimer les étapes pour déployer ce portail et configurer les éléments initiaux.

La partie configuration des fonctionnalités SAINet se trouve dans la documentation pré-inscription et la documentation portail de communication.

Pré-requis de déploiement

Dans SYS01, créer une nouvelle société. Par convention, les sociétés avec un seul portail global sur le serveur utilisent l’identifiant POR.

Déclaration du domaine

Dans les modules de la société, sélectionner la configuration de modules qui contient le module creche_portal. Cette configuration est généralement appellée également creche_portal par convention.

Une autre étape nécessaire est la déclaration de la société qui contiendra la configuration de toutes les fonctionnalités du portail. Il n’est pas nécessaire à ce point de configurer les fonctionnalités mais il est obligatoire d’en définir la base.

Déclaration du domaine

Le paramétrage des fonctionnalités disponibles dans le portail parents s’effectue dans la chaîne UNI de SAINet. Il est donc nécessaire de créer une nouvelle entrée POR (si la société du portail est POR) dans UNI91. Afin de pouvoir faire cette saisie, l’utilisateur courant doit avoir accès à la société POR dans SYS02.

Champs obligatoires de SAL01

Note:

Il est important que le domaine du portail (POR) soit déclaré au niveau des différentes tâches 01 (SAL01, CRM01, GTP01 et DEB01).

Création d’un compte utilisateur

Pour se connecter à SAINet, il est nécessaire de créer un compte de service sous la forme d’un utilisateur dans SYS02 dont les informations seront fournies à la configuration du portail parents. Ce compte, de type USER et appartenant au groupe PORTAIL, doit pouvoir accéder à toutes les sociétés qui concernent les enfants et qui seront disponibles à travers le portail.

Ce compte doit impérativement avoir la coche Compte système activée dans SYS02. Sans cette coche, SAINet refuse les requêtes du portail dès que celui-ci agit au nom d’un parent ou d’un collaborateur.

Attention:

Un compte système ne peut pas avoir le rôle SUPERADMIN. Veillez également à ce que son mot de passe soit à jour (non marqué comme devant être changé), sans quoi la connexion du portail échoue.

Afin d’éviter tout problème lié à l’expiration du mot de passe, il est conseillé de mettre une date de fin de validité suffisamment grande. Cette opération nécessite un accès direct à la base de données.

Création d’un groupe

Il est conseillé de créer un groupe PORTAIL en SYS03 afin d’y attacher l’utilisateur et de créer ensuite les droits nécessaires pour ce groupe via les tâches SYS04 et SYS07. L’utilisateur doit avoir les accès suivants:

Tâche Accès Remarques
SYS00 Accès en lecture et exécution. Accès obligatoire pour la connexion des utilisateurs portail.
UNI91 Accès en lecture. Accès obligatoire.
SAL01 Accès en lecture. Accès obligatoire.
CRM06 Accès en lecture/écriture. Accès obligatoire.
CRM07 Accès en lecture/écriture. Accès obligatoire.
CRML6 Accès en lecture. Uniquement si le portail permet aux parents d’accéder à la fonctionnalité “Résiliations”.
CRMLK Accès en lecture. Uniquement si le portail permet aux parents d’accéder à la fonctionnalité “Bon à savoir”.
CRM40 Accès en lecture/écriture. Accès obligatoire.
GTP01 Accès en lecture. Accès obligatoire pour lire les configurations.
GTP11 Accès en lecture/écriture. Accès obligatoire.
GTP12 Accès en lecture/écriture. Accès obligatoire.
GTP18 Accès en lecture. Uniquement si le portail permet aux parents d’accéder à la fonctionnalité “Absences”.
GTP40 Accès en lecture. Uniquement si le portail permet aux parents d’accéder à la fonctionnalité “Agenda”.
GTPPI Accès en lecture. Uniquement si le portail permet aux parents d’accéder à la fonctionnalité “Saisie des irréguliers”.
UNI42 Accès en lecture/écriture. Accès obligatoire.
DEB02 Accès en lecture. Uniquement si le portail permet aux parents d’accéder à leur factures.
DEBED Accès en lecture. Nécessaire si accès au DEB02.
DEBOF Accès en lecture. Nécessaire si accès au DEB02.
DEB54 Accès en lecture. Nécessaire si accès au DEB02.
DEB65 Accès en lecture. Nécessaire si accès au DEB02.

Docker

Afin de simplifier le déploiement, une image docker est disponible permettant en quelques lignes d’avoir un portail opérationnel facilement maintenable. Cette image s’appelle saierp/portal. La version de l’image dépend du serveur pointé. Par exemple, pour un serveur en version 4.10, l’image sera saierp/portal:4.10.

Afin de pouvoir déployer l’image, il est nécessaire d’avoir docker installé sur la machine.

Configuration et déploiement

Afin de déployer l’image, créer un fichier docker-compose.yml avec le contenu suivant :

version: "3.0"

services:
  sainet_portal:
    restart: always
    image: saierp/portal:4.12
    environment:
      - <variable>=<valeur>
      - ...
    volumes:
      - "./portal/app:/usr/src/app"
      - "./portal/update:/usr/src/update"
    ports:
      - "<port>:3001"

Le <port> correspond au port externe d’écoute de l’image docker. En interne, le port 3001 est utilisé. Il peut être changé dans les variables d’environnement ce qui n’est pas utile dans le cadre d’un déploiement docker.

Il est également nécessaire de configurer les deux volumes persistants sur /usr/src/app et /usr/src/update afin que les mises à jour automatiques puissent être appliquées.

La liste des variables principales se trouve ci-dessous:

Variable Requis Valeur Définition
SAINET_URL oui https://<host>/SNV4SRV-mobile-war/ Le lien vers l’interface mobile d’un serveur SAINet.
SAINET_USER oui string L’utilisateur du compte de service pour le lien avec SAINet.
SAINET_PASS oui string Le mot de passe du compte de service.
SAINET_DOMAIN oui string L’identifiant du domaine du portail qui sera utilisé pour la gestion.
SAINET_MODULE string Type de portail (MRH,REG,CRE). Par défaut CRE.
SAINET_APP_NAME string Intitulé du portail. Par défaut ‘SAINet’.
SAINET_NPM_AUTH_TOKEN oui string Token d’authentification pour que le portail puisse être compilé (également lors des mises à jour). Cette valeur est fournie par SAI ERP.
SAINET_SUB_SITE_KEY * string La clé pour le captcha de google dans le cas où l’inscription est disponible.
SAINET_SUB_SECRET_KEY * string La clé secrète pour vérifier le catpcha dans le cas où l’inscription est disponible.

Les variables SAINET_SUB_SITE_KEY et SAINET_SUB_SECRET_KEY peuvent être récupérées sur Google reCAPTCHA v2 (voir ici). Ces variables sont obligatoires lorsqu’un utilisateur peut s’enregistrer par lui-même.

Note:

Le portail s’attend à des requêtes HTTP. En cas de redirection HTTPS vers HTTP en amont (via Apache ou Traefik), il est nécessaire d’insérer le header X-Forwarded-Proto avec la valeur https sinon le mode NODE_ENV=production ne fonctionnera pas.

Les variables complémentaires suivantes sont également disponibles:

Variable Type Définition
SAINET_ROOT_DIR string Chemin d’installation du portail SAINet. Par défaut /usr/src en Docker. Sous Windows (service.bat), cette variable est obligatoire et n’a pas de valeur par défaut.
SAINET_AUTOUPDATE_COOLDOWN integer Temps d’attente (en ms) avant de vérifier l’existence d’une mise à jour au (re)démarrage du service. Par défaut, 1h.
SAINET_RELATIVE_PATH string URL relative du portail lorsque ce dernier ne se trouve pas à la racine du site.
SAINET_LOGOUT_URL string URL de logout par un service externe. Utilisé en lien avec SAINET_SESSION_TYPE=F5.
SAINET_SESSION_TYPE string Type de session. Peut prendre la valeur F5 dans le cas d’une authentification gérée par un service spécial. En mode F5, l’option « Utiliser un identifiant externe » de la configuration de connexion du portail doit être activée, sinon les connexions par identifiant externe sont refusées.
SAINET_DOWNLOAD_FOLDER string Dossier de téléchargement des mises à jour.
SAINET_DISPLAY_ENDPOINT boolean Affichage de l’endpoint dans les logs lors du démarrage et quitte le processus.
SAINET_PORT integer Le port interne d’écoute du portail. Par défaut 3001.
SAINET_AUTO_UPDATE 0 ou 1 Permet d’activer ou désactiver la mise à jour automatique. Par défaut 1.
SAINET_CSRF_CHECK 0 ou 1 Permet d’activer le contrôle d’origine de la protection CSRF (1). Par défaut 0 (désactivé). Voir Protection CSRF.
SAINET_LOG_LEVEL 1 à 6 Logging pour node. Par défaut à 6 (DEBUG).
SAINET_SERVER_LOG_LEVEL 1 à 6 Logging pour les appels serveurs. Par défaut à 2 (WARNING).
SAINET_CONFIG_LOG_LEVEL 1 à 6 Logging pour la gestion de la configuration. Par défaut à 2 (WARNING).
SAINET_SESSION_KEY string Secret servant à signer les cookies de session et à chiffrer le mot de passe en attente lors d’une double authentification (MFA). Recommandé pour tout déploiement, et indispensable dès que plusieurs instances du portail tournent sans Redis. Doit être une chaîne aléatoire d’au moins 32 caractères. Si la variable est absente, un secret aléatoire est généré au démarrage : partagé entre les instances via Redis si SAINET_REDIS_HOST est défini, propre au processus sinon (les sessions sont alors perdues à chaque redémarrage). Un avertissement est écrit dans les logs. Avec Redis, ce secret généré est stocké dans le même Redis que les sessions (et que les données MFA en attente, chiffrées) : toute personne pouvant lire Redis peut alors forger des cookies de session, d’où la recommandation de définir SAINET_SESSION_KEY. Le premier déploiement de cette version, ainsi que tout changement de cette valeur, déconnecte une fois tous les utilisateurs.
SAINET_REDIS_HOST string Host du service Redis. Si pas spécifié, un cache local sera utilisé.
SAINET_REDIS_PORT integer Port du service Redis. Par défaut 6379.
SAINET_REDIS_PASSWORD string Mot de passe pour le service redis.
SAINET_NODE_OPTIONS options Liste d’options à passer à la commande node. Par défaut, seule l’option –max-old-space-size est mise à 512.
NODE_ENV development,production Définit le mode d’exécution du portail en terme d’optimisation. Par défaut production (nécessite un accès en HTTPS).
NODE_TLS_REJECT_UNAUTHORIZED 0 ou 1 Permet d’ignorer les erreurs de certificat lors de tests https en serveur local. Par défaut à 0 (<=3.0.2) et 1 (>=3.0.3).

Le portail doit être placé derrière exactement un reverse proxy de confiance (trust proxy à 1) : c’est à cette condition que la limitation des tentatives de connexion par adresse IP est fiable. Les compteurs de cette limitation sont conservés en mémoire, par instance du portail.

Exemple

Voici un exemple de docker-compose.yml minimal dans le cadre d’un déploiement avec une partie pré-inscription. L’image Docker du portail est déployée sur Docker Hub.

Cette exemple montre une configuration du serveur SAINet avec son compte de service et la mise en place du captcha google pour la pré-inscription.

version: "3.0"

services:
  sainet_portal:
    restart: always
    image: saierp/portal:4.12
    environment:
      - SAINET_APP_NAME=MyPortal
      - SAINET_URL=https://my-sainet-server.ch/SNV4SRV-mobile-war
      - SAINET_MODULE=CRE
      - SAINET_USER=portal
      - SAINET_PASS=example
      - SAINET_DOMAIN=POR
      - SAINET_NPM_AUTH_TOKEN=123456789=
      - SAINET_REDIS_HOST=sainet_redis
      - SAINET_SUB_SITE_KEY=123456789abcd
      - SAINET_SUB_SECRET_KEY=987654321dcba
    volumes:
      - "./portal/app:/usr/src/app"
      - "./portal/update:/usr/src/update"
    ports:
      - "3001:3001"
      
  sainet_redis:
    restart: always
    image: redis:7-alpine
    command: redis-server --maxmemory 512m

HTTP/HTTPS et cookies sécurisés

Il est important de pouvoir accéder en https à l’URL de login (car des cookies sécurisés sont utilisées). Si cela n’est pas le cas, alors il est nécessaire de définir NODE_ENV=development afin de pouvoir se connecter. Dans le cas contraire, le header HTTP X-Forwarded-Proto doit être inséré dans la redirection avec la valeur “https” afin que l’utilisateur puisse être authentifié.

En utilisant Traefik pour réceptionner les requêtes https, il n’y a pas de configuration spéciale à effectuer car les headers nécessaires sont déjà inclus dans la requête transférée (x-forwarded-for, x-forwarded-host, x-forwarded-port, x-forwarded-proto, x-forwarded-server et x-real-ip).

Dans le cas d’une configuration Apache2 (dans une configuration VirtualHost), il est nécessaire d’activer le mode headers:

a2enmod headers

Il suffit ensuite d’ajouter la ligne ci-dessous dans le VirtualHost pour que le header soit ajouté lors de la redirection.

RequestHeader set "X-Forwarded-Proto" "https"

Protection CSRF

Lorsque la protection est activée (SAINET_CSRF_CHECK=1), le portail refuse en HTTP 403 toute requête dont la méthode n’est pas GET, HEAD ou OPTIONS provenant d’un autre site que lui-même. Le contrôle s’effectue dans l’ordre suivant :

  1. Si l’en-tête Sec-Fetch-Site est présent, seules les valeurs same-origin et none sont acceptées. Toute autre valeur, y compris same-site, est refusée : un formulaire hébergé sur un sous-domaine voisin est donc rejeté.
  2. Sinon, si l’en-tête Origin est présent, son hôte (port compris) doit être identique à l’hôte de la requête. Le schéma n’est pas comparé.
  3. Si aucun de ces deux en-têtes n’est présent (clients non navigateurs), la requête est acceptée.

Les requêtes GET, HEAD et OPTIONS ne sont pas contrôlées.

Le reverse proxy doit respecter les points suivants :

  • ne pas retirer ni réécrire les en-têtes Origin et Sec-Fetch-* ;
  • transmettre l’hôte d’origine de la requête : soit en le conservant dans l’en-tête Host (sous Apache, en activant la directive ProxyPreserveHost), soit en renseignant l’en-tête X-Forwarded-Host. Le portail utilise X-Forwarded-Host s’il est présent, sinon Host. L’en-tête X-Forwarded-Host n’est pris en compte que si la requête provient du proxy de confiance (réglage trust proxy du portail, limité à un saut).

Si le proxy réécrit l’hôte (par exemple en le remplaçant par le nom interne du portail) sans transmettre l’hôte d’origine, et que le navigateur n’envoie pas Sec-Fetch-Site, l’hôte de l’en-tête Origin ne correspond plus et toutes les actions sont refusées en 403.

Le contrôle est désactivé par défaut, par compatibilité avec les installations existantes. La variable d’environnement SAINET_CSRF_CHECK=1, lue une seule fois au démarrage du portail, l’active. L’activation est recommandée, une fois vérifié que le reverse proxy conserve l’en-tête Host ou transmet X-Forwarded-Host, et qu’il ne retire pas les en-têtes Sec-Fetch-* et Origin. Seule la valeur exacte 1 active le contrôle : toute autre valeur, ou l’absence de la variable, le laisse désactivé et les requêtes sont acceptées quelle que soit leur origine.

La variable se pose à l’un des emplacements suivants :

  • sous Docker, dans la section environment: du fichier compose (ou avec l’option -e de docker run), qui prend le pas sur la valeur par défaut de l’image ;
  • sous Windows, dans le fichier env.bat, appelé par service.bat après les valeurs par défaut.

Sous Windows, aucun espace ne doit suivre la valeur dans SET SAINET_CSRF_CHECK=1 : l’espace ferait partie de la valeur et le contrôle resterait désactivé.

Un avertissement est écrit dans les logs au démarrage, que le contrôle soit activé ou non.

Indépendamment de cette variable, les actions à effet de bord (déconnexion, acceptation des conditions d’utilisation, fonctions d’administration /features/**) n’acceptent que la méthode POST : une requête GET sur ces adresses n’exécute rien (au plus, une page de confirmation ou de présentation est affichée).

Pour la configuration Apache, voir la installation Apache.

Certificat

Il est possible de mettre le portail derrière un service traefik afin de gérer le certificat SSL. La configuration ci-dessous est un exemple (basé sur cette documentation) qui sert un certificat statique.

services:
  sainet_portal:
    ...
    
    labels:
      - "traefik.http.routers.sainet_portal.rule=PathPrefix(`/`)"
      - "traefik.http.routers.sainet_portal.entrypoints=websecure"
      - "traefik.http.services.sainet_portal.loadbalancer.server.port=3001"
      - "traefik.http.routers.sainet_portal.tls=true"

  sainet_traefik:
    restart: always
    image: traefik:v3.6
    depends_on:
      - sainet_portal
    command:
      - --providers.docker
      - --providers.file.directory=/certificates/config
      - --entrypoints.web.address=:80
      - --entrypoints.web.http.redirections.entrypoint.to=websecure
      - --entrypoints.web.http.redirections.entrypoint.scheme=https
      - --entrypoints.web.http.redirections.entrypoint.permanent=true
      - --entrypoints.websecure.address=:443
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"
      - "./certificates:/certificates"
    ports:
      - "443:443"

Service Redis

Redis permet de garder certaines données en cache, notamment au niveau des sessions utilisateurs. Par conséquent, il est utile que ces données soient persistentes afin que les utilisateurs ne se retrouvent pas déconnectés si le service de portail venait à redémarrer (typiquement lorsqu’un administrateur applique une mise à jour).

Niveau de logging

En cas de problème, la valeur des variables SAINET_LOG_LEVEL, SAINET_SERVER_LOG_LEVEL et SAINET_CONFIG_LOG_LEVEL peut prendre une valeur de 1 à 6 avec les définitions suivantes:

  1. SEVERE
  2. WARNING
  3. FINE
  4. FINER
  5. FINEST
  6. DEBUG

Windows / service

La procédure pour installer le portail en tant que service sur Windows:

  • Installer le serveur Apache en changeant le port pour 3001 dans la configuration.
  • Installer la dernière version LTS de NodeJS (npm et node doivent être disponibles dans la variable d’environnement PATH).
  • Installer sevenzip (7z doit être disponible dans la variable d’environnement PATH).
  • Télécharger l’artifact du portail.
  • Extraire les fichiers dans un sous-dossier app (par exemple D:/sainet/portal/app).
  • Copier les fichiers app/windows/env.bat et app/windows/service.bat au même niveau que le répertoire app (ils doivent être dehors du répertoire).
  • Mettre à jour les variables dans env.bat.
  • Créer un service en redémarrage automatique avec nssm (voir ici) en pointant le fichier <portal>/service.bat.
  • Le dossier d’exécution (« Startup directory ») du service doit être <portal> (mettre le chemin complet), c’est-à-dire le dossier qui contient env.bat et service.bat.
Attention:

service.bat appelle env.bat par un chemin relatif, avant tout changement de répertoire : sans le réglage du dossier d’exécution indiqué ci-dessus, le service démarre par défaut dans System32, où env.bat n’est pas trouvé, ce qui fait échouer le démarrage. L’exemple de dossier fourni pour le service SAINet principal ne s’applique pas au portail.

Il est possible de tester le démarrage du portail en lancant la commande suivante dans une console:

cd <portal>
service.bat

Mises à jour automatiques

Lorsque la mise à jour automatique est active (SAINET_AUTO_UPDATE=1), le portail va contrôler chaque nuit si une nouvelle version du portail est disponible à travers SAINet. Au besoin, la nouvelle version est téléchargée et le container se relance pour appliquer la mise à jour.

A noter également que lorsque le container est (re)démarré, le portail va directement vérifier la disponibilité d’une mise à jour et l’installer le cas échéant. Un timestamp local évite que cela se fasse à chaque (re)démarrage si celui-ci intervient moins d’une heure avant la dernière mise à jour. Cela peut-être forcé en supprimant le fichier <update>/update.time et en redémarrant le container.

Administration

Une page d’administration est également disponible sur https://<url-portal>/admin-login. Il est nécessaire de fournir un utilisateur/mot de passe SAINet ayant le rôle SUPERADMIN afin de s’y connecter.

Impersonation d’un utilisateur du portail

Un utilisateur SUPERADMIN connecté avec la double authentification peut ouvrir le portail tel que le voit un utilisateur du portail (pour le support, avec un motif obligatoire, une durée limitée à 30 minutes et une trace d’audit). Cette fonctionnalité est décrite dans le guide d’aide « Impersonation d’un utilisateur du portail » (chaîne SYS). Elle est indisponible avec SAINET_SESSION_TYPE=F5, et la définition de SAINET_SESSION_KEY ainsi que le reverse proxy unique (trust proxy à 1, afin que l’adresse IP enregistrée dans l’audit soit celle du navigateur) sont recommandés (voir les variables d’environnement).

Commandes utiles

Afin de vérifier que la connexion du portail à SAINet se fait correctement, il est possible d’exécuter la commande suivante:

curl -H 'X-Auth-User:<portal>' -H 'X-Auth-Pass:<password>' 'https://portal-bes.reg.sai-erp.net/clearcache?type=configuration'

Suivi de l’utilisation

Il est possible d’effectuer du suivi de l’utilisation du portail de manière anonyme via Google Analytics. Ce service permet d’obtenir des informations sur les pages du portail qui ont été visitées par les utilisateur.

Pour mettre en place ce suivi, il suffit de générer un nouveau Stream pour le portail dans Google Analystics et de récupérer la clé (Mesurement ID).

Dans le domaine du portail, créer un page Paramètres divers dans UNI91 et renseigner la clé.

Certains navigateurs ou extensions (bloqueur de publicité, protections intégrées comme Brave Shields, proxy d’entreprise, cookies refusés) empêchent le chargement d’une partie du portail. Pour éviter qu’un utilisateur reste devant une page inutilisable sans explication, un contrôle s’exécute dans le navigateur sur les pages d’entrée du portail : connexion, connexion administrateur, activation et réinitialisation du mot de passe, politique de confidentialité et préinscription (crèches).

Lorsqu’un blocage est détecté (cookies refusés, bibliothèque externe non chargée ou script de page interrompu), un panneau explique la marche à suivre et affiche un code de diagnostic au format <navigateur>-<causes>, par exemple BR-JQJS (BR Brave, ED Edge, FF Firefox, CH Chrome, SF Safari, OT autre ; CK cookies, ST stockage du site, JQ/LD/MO bibliothèques manquantes, JS script interrompu). L’utilisateur peut fermer le panneau et continuer.

Le rapport du navigateur est envoyé au portail (POST /diag/client) et journalisé en niveau WARN avec le préfixe [client-preflight] (code, utilisateur, page, état des cookies et du stockage, ressources bloquées, erreurs, User-Agent). Il suffit de rechercher le code communiqué par l’utilisateur dans les logs du portail.

Les coordonnées de l’administration affichées à côté du code se renseignent dans UNI91, page Paramètres divers, champ Contact de l'administration (texte libre sur une ligne, affiché tel quel). Si le champ est vide, seul le message générique est affiché. La valeur est mise en cache avec la configuration du portail : elle n’est prise en compte qu’après un rechargement de la configuration (voir Commandes utiles, commande clearcache?type=configuration).

Troubleshooting

Dépendance sharp

Sur un serveur virtualisé, cette exception peut survenir au démarrage (voir cette issue):

sainet_portal_1  | Error: Could not load the "sharp" module using the linux-x64 runtime
sainet_portal_1  | Unsupported CPU: Prebuilt binaries for linux-x64 require v2 microarchitecture
sainet_portal_1  | Possible solutions:
sainet_portal_1  | - Ensure optional dependencies can be installed:
sainet_portal_1  |     npm install --include=optional sharp
sainet_portal_1  | - Ensure your package manager supports multi-platform installation:
sainet_portal_1  |     See https://sharp.pixelplumbing.com/install#cross-platform
sainet_portal_1  | - Add platform-specific dependencies:
sainet_portal_1  |     npm install --os=linux --cpu=x64 sharp
sainet_portal_1  | - Consult the installation documentation:
sainet_portal_1  |     See https://sharp.pixelplumbing.com/install
sainet_portal_1  |     at Object.<anonymous> (/usr/src/app/node_modules/sharp/lib/sharp.js:121:9)
sainet_portal_1  |     at Module._compile (node:internal/modules/cjs/loader:1730:14)
sainet_portal_1  |     at Object..js (node:internal/modules/cjs/loader:1895:10)
sainet_portal_1  |     at Module.load (node:internal/modules/cjs/loader:1465:32)
sainet_portal_1  |     at Function._load (node:internal/modules/cjs/loader:1282:12)
sainet_portal_1  |     at TracingChannel.traceSync (node:diagnostics_channel:322:14)
sainet_portal_1  |     at wrapModuleLoad (node:internal/modules/cjs/loader:235:24)
sainet_portal_1  |     at Module.require (node:internal/modules/cjs/loader:1487:12)
sainet_portal_1  |     at require (node:internal/modules/helpers:135:16)
sainet_portal_1  |     at Object.<anonymous> (/usr/src/app/node_modules/sharp/lib/constructor.js:10:1)

Pour corriger cela, une solution est de changer le type de CPU au niveau de PVE pour utiliser x86-64-v2 au lieu de Common KVM processor.

Si le service est directement installé sur la machine, dans ce cas simplement lancer la commande npm install --os=win32 --cpu=x64 sharp et relancer le service.