Aller au contenu

ONAS — Dossier de livraison

Ce document s’adresse au service informatique de l’ONAS. Il répond à quatre questions, dans cet ordre :

  1. Comment fonctionne le site (ce n’est pas un CMS PHP/MySQL — la différence change tout).
  2. De quoi l’ONAS a besoin pour l’héberger, et ce qu’il doit configurer.
  3. Ce que Levell livre exactement, artefact par artefact.
  4. Comment mettre en service le site avec son contenu déjà en place.

Le site est une application Node.js (Next.js 16 + Payload CMS 3) : un processus qui tourne en permanence sur le serveur et répond sur un port TCP. Ce n’est pas un ensemble de fichiers .php déposés par FTP dans un www/.

Chez vous, d’habitudeIci
PHP exécuté par Apache/Nginx à chaque requêteProcessus Node.js 24 permanent, écoute sur le port 3000
Base MySQL/MariaDB + phpMyAdminSQLite : la base est un seul fichier (payload.db, 760 Ko) — aucun serveur de base de données à installer
wp-content/uploads/Dossier public/media sur un disque persistant
Back-office WordPressBack-office Payload, à l’URL /admin
Déploiement par FTPDéploiement par image Docker (ou git pull + npm run build + redémarrage du service)
Apache sert le HTTPSReverse proxy (Nginx/Traefik/Caddy) devant le port 3000, qui porte le certificat TLS

Trois conséquences pratiques :

  • Il faut un service qui redémarre automatiquement le processus (Docker restart: unless-stopped, ou une unité systemd).
  • Il faut un reverse proxy HTTPS devant : l’application ne gère pas les certificats.
  • Il faut un disque persistant : c’est le point le plus important, expliqué juste en dessous.

2. Comment sont stockées les images — le point clé

Section intitulée « 2. Comment sont stockées les images — le point clé »

Le site contient deux familles d’images qui ne vivent pas au même endroit et ne se sauvegardent pas de la même façon. Confondre les deux est la principale source d’incident au déploiement.

🎨 Images permanentes (habillage du site)🗂️ Médias du back-office (contenu éditorial)
Exemplesphotos des bannières, décors, icônes, logos ONAS et partenairesphotos des projets, illustrations des actualités, PDF des appels d’offres et publications
Emplacementpublic/images/dans le code sourcepublic/media/sur un volume/disque persistant, hors du code
Volumétrie actuelle86 fichiers, 278 Mo (dont photos/ 260 Mo, decor/ 13 Mo, audit/ 4,7 Mo, logos/ 708 Ko, partners/ 112 Ko, icons/ 52 Ko)279 fichiers, 793 Mo (63 médias : 38 JPEG, 9 PNG, 16 PDF)
URL publiquehttps://<domaine>/images/…https://<domaine>/media/…
Qui peut les modifierLevell uniquement (nouveau build + redéploiement)L’ONAS lui-même, depuis /admin
Voyage avec…le code source / l’image Docker (elles sont embarquées dans le build)la sauvegarde du volume — jamais avec le code
Sauvegardeassurée par le dépôt Git (rien à faire)à la charge de l’ONAS : payload.db + dossier media/

À chaque image envoyée depuis le back-office, l’application génère automatiquement trois déclinaisons en plus de l’original :

DéclinaisonDimensionsUsage
thumbnail400 × 300vignettes du back-office et des listes
card768 × 1024cartes projets / actualités
tablet1024 × (proportionnel)affichage tablette

C’est ce qui explique l’écart entre 63 médias déclarés et 279 fichiers sur le disque (543,8 Mo d’originaux → 793 Mo au total). Ce redimensionnement est effectué par la bibliothèque sharp (module natif) au moment de l’upload — d’où le besoin d’un peu de CPU et de RAM sur le serveur.

Détail important : aucune image n’est servie telle quelle

Section intitulée « Détail important : aucune image n’est servie telle quelle »

Les deux familles d’images ci-dessus ne sont jamais envoyées directement au navigateur. Elles transitent par l’optimiseur intégré à Next.js, qui les recompresse à la volée en AVIF ou WebP à la taille exacte demandée, puis met le résultat en cache.

Concrètement, une page ne contient pas <img src="/images/photos/hero.png"> mais :

/_next/image?url=%2Fimages%2Fphotos%2Fhero.png&w=1920&q=100
↑ ↑
largeur qualité

Il y a donc deux URL différentes derrière chaque image, et elles peuvent tomber en panne indépendamment — ce qui est la clé du diagnostic :

URLRôleSi elle échoue
/images/… ou /media/…le fichier source sur le disquefichier absent, volume non monté, droits
/_next/image?url=…l’optimiseur qui le transformele fichier est bien là, mais refusé ou non transformable

RessourceMinimumRecommandéPourquoi
CPU2 vCPU2–4 vCPURedimensionnement d’images (sharp) au moment des uploads
RAM2 Go4 GoLe processus consomme ~300 Mo au repos ; la construction de l’application (npm run build) demande davantage
Disque10 Go20 GoImage Docker ~2,25 Go + médias 793 Mo + croissance éditoriale + sauvegardes
OSLinux 64 bits (Ubuntu/Debian/RHEL…)Ubuntu 22.04+ / Debian 12+
ExécutionDocker + Docker Compose (recommandé) ou Node.js 24 LTSDockerDocker évite d’installer Node, de gérer les versions et le service
StockageDisque persistant obligatoire pour data/ et media/Voir Règle n°2 ci-dessus
FluxSensDétail
80/tcp, 443/tcpentrantAccès public au site (le proxy redirige vers le port 3000 en interne)
443/tcp vers api.brevo.comsortantEnvoi des e-mails transactionnels (accusés de réception des formulaires)
443/tcp vers Let’s EncryptsortantUniquement si le certificat TLS est émis automatiquement
3000/tcpinterneNe doit pas être exposé publiquement
BesoinDétailQui le fournit
Nom de domaineLe domaine officiel ONAS (ex. www.onas.sn) + accès à la zone DNS pour créer l’enregistrement A vers l’IP du serveurONAS
Certificat TLSLet’s Encrypt (gratuit, automatique) ou certificat de l’ONAS installé sur le reverse proxyONAS
Compte d’envoi d’e-mailsUne clé API Brevo (ou Resend) + une adresse d’expédition vérifiée sur ce compteONAS
Reverse proxyNginx, Traefik ou Caddy devant le port 3000ONAS
  • ❌ Serveur MySQL/MariaDB/PostgreSQL — la base est un fichier SQLite.
  • ❌ PHP, Apache avec mod_php, cPanel/Plesk.
  • ❌ Serveur SMTP.
  • ❌ Stockage objet / CDN (les médias sont servis par l’application).
  • ❌ Licence logicielle — l’ensemble de la pile est open source.

Toute la configuration tient dans un fichier .env à la racine du projet, plus deux actions hors application (DNS et compte administrateur).

Fenêtre de terminal
# ── Sécurité ─────────────────────────────────────────────────────────
# Clé de signature des sessions du back-office.
# À REGÉNÉRER par l'ONAS : openssl rand -hex 32
PAYLOAD_SECRET=<chaîne aléatoire de 32 octets>
# ── Base de données ──────────────────────────────────────────────────
# Chemin du fichier SQLite. Doit pointer vers le disque persistant.
DATABASE_URI=file:/app/data/payload.db
# ── E-mails ──────────────────────────────────────────────────────────
# Rien à renseigner ici : depuis le 12 août 2026, l'envoi se configure
# entièrement dans le back-office (voir l'encadré ci-dessous).

Configuration en vigueur (relevée le 13 août 2026) :

RéglageValeur
Méthode d’envoiServeur SMTP
Serveurmail.infomaniak.com, port 587, STARTTLS
Identifiantno-reply@onas.sn
ExpéditeurService Commercial ONAS <no-reply@onas.sn>
Destinatairesclients@onas.sn pour six formulaires, onas_branchement@onas.sn pour les branchements
ActionDétail
DNSCréer un enregistrement A : www.onas.sn → adresse IP publique du serveur. Prévoir la redirection de l’apex (onas.sn) vers www, ou un second enregistrement A.
TLSInstaller le certificat sur le reverse proxy (Let’s Encrypt automatique, ou certificat fourni par l’ONAS).
Compte administrateurLe back-office est livré avec un seul compte (technique, Levell). L’ONAS crée ses propres comptes depuis /adminUtilisateurs, puis supprime le compte Levell après la recette.
SauvegardesPlanifier la copie quotidienne de payload.db et du dossier media/ (voir §8).

#ArtefactFormatTailleContenu
1Code source completDépôt Git privé (accès accordé à l’ONAS) ou archive .tar.gz~400 MoApplication, et les 278 Mo d’images d’habillage (public/images/)
2Fichiers de déploiementDockerfile, docker-compose.yml, entrypoint.sh, next.config.tsInclus dans le code — la construction est reproductible en une commande. next.config.ts doit rester présent à la racine de l’application en production : il est relu à chaque démarrage et conditionne l’affichage des images (§2) et les redirections d’URL
3Modèle de configuration.env.example commentéToutes les variables du §4, documentées
4Base de données peupléepayload.db760 KoTout le contenu éditorial actuel (voir §6)
5Médias du back-officeonas-media.tar.gz~790 MoLes 279 fichiers de public/media/ (photos + PDF, originaux et déclinaisons)
6Runbook de mise en serviceCe document, §7Commandes exactes, dans l’ordre
7Guide du back-officeDocument / session de prise en mainComment l’ONAS publie une actualité, un projet, un appel d’offres
  • L’hébergement, la supervision et l’exploitation du serveur.
  • Le nom de domaine, le certificat TLS, le compte d’envoi d’e-mails — ce sont des actifs de l’ONAS.
  • Les identifiants ou accès à l’infrastructure Levell.

Le site n’arrivera pas vide. La base livrée contient l’intégralité du contenu saisi en phase de production, réparti sur 19 collections :

CollectionEntréesCollectionEntrées
Projets11Documents & publications9
Actualités10FAQ12
Appels d’offres7Produits2
Partenaires9Albums photo1
Médias (images + PDF)63Vidéos1
Utilisateurs (admin)1Journal d’e-mails8

Les collections de demandes citoyennes (contacts, branchements, dépotage, analyses, appui aux promoteurs, produits dérivés, cahiers des charges) sont vides — c’est normal : elles se remplissent avec les soumissions réelles du public une fois le site en ligne.


Procédure pour l’option Docker, recommandée. (Une variante Node.js + systemd + Nginx est fournie sur demande.)

Fenêtre de terminal
# 1. Déposer le code sur le serveur
git clone <dépôt-privé> /opt/onas-web # ou : tar -xzf onas-web.tar.gz -C /opt
cd /opt/onas-web
# 2. Configurer
cp .env.example .env
openssl rand -hex 32 # générer PAYLOAD_SECRET
nano .env # renseigner les variables du §4.1
# 3. Créer les volumes et y RESTAURER le backend peuplé (AVANT tout démarrage)
docker volume create onas-web_onas_data
docker volume create onas-web_onas_media
docker run --rm -v onas-web_onas_data:/dest -v "$PWD":/src alpine \
cp /src/payload.db /dest/payload.db
docker run --rm -v onas-web_onas_media:/dest -v "$PWD":/src alpine \
tar -xzf /src/onas-media.tar.gz -C /dest
# 4. Construire et démarrer
docker compose build # ~5 à 10 min au premier build
docker compose up -d
# 5. Vérifier
docker compose logs -f # attendre "Ready" — pas de message d'initialisation
curl -I http://localhost:3000 # doit répondre 200

Puis configurer le reverse proxy pour transmettre www.onas.sn127.0.0.1:3000, avec les en-têtes habituels (Host, X-Forwarded-Proto, X-Forwarded-For) et un corps de requête autorisé d’au moins 50 Mo (client_max_body_size côté Nginx) pour permettre l’upload des PDF et photos depuis le back-office.

VérificationAttendu
https://www.onas.snPage d’accueil complète, images de bannière visibles (= public/images OK)
Page « Projets »11 projets avec leurs photos (= volume media + base OK)
Page « Documents »Les PDF se téléchargent
https://www.onas.sn/adminConnexion au back-office
Formulaire de contactSoumission → visible dans /admin, et e-mail reçu par le service concerné
Cadenas TLSCertificat valide, redirection HTTP → HTTPS active

/adminContenu du siteCarrousel d’accueil. Chaque slide expose :

ChampRôle
ÉtiquetteRepère dans la liste, et description de l’image pour les lecteurs d’écran
TitreUne ligne par entrée — chaque ligne est animée séparément
Texte de présentationLe paragraphe sous le titre. Deux à trois lignes : au-delà, il concurrence le titre
Texte / Lien du boutonUne page du site commence par / (ex. /projets), un lien externe par https://
Image — ordinateurPaysage, 2400 × 1350 px minimum
Image — mobilePortrait, 1200 × 1600 px minimum. Facultatif — sinon l’image ordinateur est recadrée, souvent mal
Ordre d’affichageDu plus petit au plus grand
VisibleDécocher retire le slide du site sans le supprimer

Tant qu’aucune image n’est téléversée, le visuel d’origine livré avec le site reste affiché. Dès qu’une image est déposée, elle prend le dessus.

Le même délai d’une minute s’applique aux actualités, projets et appels d’offres publiés depuis le back-office.

#!/bin/sh
# /opt/onas-web/backup.sh — à mettre en cron quotidien
DEST=/backup/onas/$(date +%F)
mkdir -p "$DEST"
# Base ET médias, ensemble (voir Règle n°1)
docker run --rm -v onas-web_onas_data:/src -v "$DEST":/out alpine \
cp /src/payload.db /out/payload.db
docker run --rm -v onas-web_onas_media:/src -v "$DEST":/out alpine \
tar -czf /out/media.tar.gz -C /src .

Volume à prévoir : ~800 Mo par sauvegarde complète (les médias dominent). Une rétention de 7 jours + une archive mensuelle est un bon compromis.

Arrêter le service (docker compose down), remplacer payload.db et le contenu de media/ par la même sauvegarde datée, redémarrer (docker compose up -d).

Mise à jour du site (évolution livrée par Levell)

Section intitulée « Mise à jour du site (évolution livrée par Levell) »

Côté Levell, une mise à jour tient en deux commandes :

Fenêtre de terminal
cd /home/sidy/deploy-kit
./deploy.sh onas --no-health # compile ici, transfère l'artefact (~3 s)
# → Manager Infomaniak → Node.js → Redémarrer
./deploy.sh onas --health-only # contrôle de santé

Si la mise à jour touche au schéma de la base, exécuter ./migrate-db.sh onas avant le déploiement, et prévenir l’ONAS : l’opération ouvre une fenêtre de moins d’une minute pendant laquelle une saisie au back-office serait perdue.

La base (data/payload.db), les médias du back-office (public/media/) et le fichier .env ne sont jamais touchés. Ce n’est pas une précaution d’usage : l’archive est relue avant tout envoi et le déploiement s’interrompt si l’un de ces chemins s’y trouve. Interruption de service : le temps du redémarrage.

SymptômePiste
Le site ne répond pasdocker compose ps puis docker compose logs --tail=100
Images du back-office casséesLe volume media n’est pas monté (Règle n°2) ou a été restauré sans la base
Certaines images ne s’affichent pas, d’autres oui (bannières, slider, hero)Qualité d’image non déclarée — voir la procédure ci-dessous. Ce n’est pas un fichier manquant.
Back-office vide alors que le site était peupléLa base a été réinitialisée — restaurer la dernière sauvegarde
Les formulaires n’envoient pas d’e-mailOuvrir Journaux emails dans /admin : le statut donne la cause. skipped = méthode d’envoi ou destinataire non configuré ; failed + 535 = identifiants SMTP refusés ; failed + 550 = expéditeur non autorisé (chez Infomaniak, l’adresse d’expédition doit être identique à l’identifiant SMTP). Les demandes restent enregistrées dans /admin.
Upload d’un gros PDF en erreurclient_max_body_size du reverse proxy trop bas

Diagnostic : « une partie des images a disparu »

Section intitulée « Diagnostic : « une partie des images a disparu » »

Quand des images manquent alors que le reste de la page s’affiche normalement, le réflexe est de chercher un fichier absent. C’est presque toujours une fausse piste : le fichier est là, c’est l’optimiseur qui refuse de le servir (voir §2).

Le diagnostic tient en deux commandes. Prendre l’URL d’une image manquante dans l’inspecteur du navigateur (onglet Réseau), puis :

Fenêtre de terminal
# 1. Le fichier source est-il servi ?
curl -sI "https://<domaine>/images/photos/30-ans-ONAS.png" | head -3
# 2. L'optimiseur le sert-il ?
curl -s "https://<domaine>/_next/image?url=%2Fimages%2Fphotos%2F30-ans-ONAS.png&w=1920&q=100" | head -c 200
RésultatInterprétationCorrection
1 → 404Fichier réellement absentVolume media non monté, ou build incomplet
1 → 200, 2 → 400 avec "q" parameter (quality) of … is not allowedQualité non déclaréeRétablir qualities: [75, 80, 85, 90, 92, 100] dans next.config.ts, puis redémarrer
1 → 200, 2 → 200L’image est servie : problème d’affichage (cache navigateur, CSS)Recharger en vidant le cache (Ctrl+Shift+R)

Après correction, l’application doit être redémarrée — pas reconstruite (§2).


SujetLevellONAS
Développement du site✅ Fait, figé
Livraison du code, de la base et des médias
Runbook et transfert de compétences
Serveur, système d’exploitation, Docker
Reverse proxy, certificat TLS
Nom de domaine et DNS
Compte d’envoi d’e-mails (Brevo)
Destinataires métier des formulairesConseil✅ Décision
Sauvegardes et restaurationProcédure fournie✅ Exécution
Publication du contenu au quotidien

10. Cas Infomaniak — le serveur de l’ONAS convient-il ?

Section intitulée « 10. Cas Infomaniak — le serveur de l’ONAS convient-il ? »

L’ONAS dispose d’un hébergement Infomaniak avec accès SSH.

10.1 Les offres Infomaniak et ce qu’elles permettent

Section intitulée « 10.1 Les offres Infomaniak et ce qu’elles permettent »
Offre InfomaniakRoot / DockerVerdict pour le site ONAS
VPS Cloud / VPS Lite✅ Root complet, Docker installable🟢 Idéal — c’est le scénario nominal du §7
Public Cloud (OpenStack) / Jelastic Cloud✅ Root possible🟢 Convient
Serveur Cloud Managé❌ Pas de root, pas de Docker🟡 À étudier — Node.js peut être installé manuellement, mais Infomaniak précise alors que « le site ne fonctionnera pas avec les ressources garanties du Cloud Managé »
Hébergement Web + « site Node.js »❌ Pas de root🟡 Probablement jouable — voir 10.3
Hébergement Web PHP seul (Starter/Essentiel classique)🔴 Incompatible — il faut basculer sur une autre offre

Première action, la plus rapide : demander à l’ONAS le nom exact de l’offre telle qu’elle apparaît dans son Manager Infomaniak (ou une capture d’écran de la page « Hébergements »). Cela suffit souvent à trancher en une minute.

Pour une réponse certaine, faire exécuter ce script par l’ONAS sur le serveur cible, puis nous renvoyer toute la sortie. Il est 100 % en lecture seule : il n’installe rien, ne modifie aucune configuration, et peut tourner sans risque sur un serveur en production.

Fenêtre de terminal
curl -O https://docs.levell.cloud/onas-diagnostic.sh
sh onas-diagnostic.sh

(Le script est également fournissable par e-mail en pièce jointe si le serveur ne peut pas télécharger depuis l’extérieur.)

Il vérifie, en 11 points : identité de la machine · privilèges root/sudo · présence et accessibilité de Docker · version de Node.js · possibilité de faire tourner un processus permanent (systemd) · CPU / RAM / disque · droit d’écriture persistante · accès réseau sortant (npm et Brevo) · possibilité d’écouter sur le port 3000 · reverse proxy déjà en place · outils de compilation.

Ce que montre le diagnosticVerdictCe que Levell livre alors
docker présent et démon accessible🟢 Compatible, cas nominalLe paquet Docker complet du §5, runbook du §7 tel quel
Pas de Docker, mais root/sudo + systemd🟢 CompatibleSoit on installe Docker, soit livraison Node.js + service systemd + Nginx
Pas de root, mais Node.js ≥ 24, écriture persistante OK, port d’écoute autorisé🟡 Compatible sous conditionsLivraison pré-construite (voir encart ci-dessous)
RAM < 2 Go ou disque libre < 5 Go🟡 InsuffisantDemander un redimensionnement de l’offre
Pas de systemd, écriture refusée, port 3000 impossible🔴 IncompatibleBasculer sur un VPS Cloud Infomaniak (même fournisseur, migration simple)
  1. Le domaine onas.sn est-il géré chez Infomaniak ? Si oui, le raccordement DNS + certificat TLS se fait en quelques clics dans le Manager, sans intervention externe.
  2. Y a-t-il déjà un site en production sur cet hébergement ? Le site ONAS doit cohabiter sans écraser l’existant.
  3. Quel est le quota disque réellement disponible, et non la taille du disque de la machine (sur les offres mutualisées, le quota est par compte).
  4. Les sauvegardes automatiques Infomaniak couvrent-elles le dossier des médias, ou faut-il mettre en place le script du §8 ?

Au-delà du diagnostic automatisé du §10, ces réponses permettent de figer le format de livraison et le runbook définitif :

  1. Le serveur cible dispose-t-il de Docker et Docker Compose ? Sinon, quelle distribution Linux, et Node.js peut-il y être installé ?
  2. Quel est l’espace disque disponible et le stockage est-il persistant (pas de conteneur éphémère) ?
  3. Un reverse proxy est-il déjà en place (Nginx, Apache, HAProxy) ? Qui l’administre ?
  4. Qui gère la zone DNS du domaine, et quel nom exact doit porter le site ?
  5. Le certificat TLS sera-t-il Let’s Encrypt automatique ou fourni par l’ONAS ?
  6. Les connexions sortantes HTTPS depuis le serveur sont-elles autorisées (nécessaires pour l’envoi d’e-mails) ?
  7. L’ONAS ouvre-t-il un compte Brevo à son nom, et quelle adresse d’expédition utiliser ?
  8. Quelles adresses de service doivent recevoir chacun des 7 types de demandes citoyennes ?
  9. Quelle est la politique de sauvegarde en vigueur, et peut-elle intégrer les deux artefacts (fichier base + dossier médias) ?
  10. Le serveur est-il exposé sur Internet ou derrière un pare-feu nécessitant une ouverture de flux ?