Administrateurs système
Parqueo — documentation d'installation
Installation, configuration et exploitation. Public visé : la personne qui déploie et maintient l'application.
- 1. Prérequis
- 2. Installation en développement
- 3. Installation en production
- 4. Référence des variables d'environnement
- 5. Configuration du SSO Microsoft Entra ID
- 6. Configuration du collecteur email
- 7. Sauvegarde et restauration
- 8. Mise à jour
- 9. Exploitation
- 10. Dépannage
1. Prérequis#
| Composant | Version | Remarque |
|---|---|---|
| Node.js | 20 LTS minimum, 22 recommandé | l'application est développée et testée sur 22.x |
| npm | fourni avec Node | |
| PostgreSQL | 14 minimum | testé sur 18 |
| Reverse proxy | nginx, Caddy, Traefik… | requis en production (voir §3.5) |
Optionnels, activés seulement s'ils sont configurés :
- un serveur SMTP pour les notifications ;
- une boîte IMAP dédiée pour le collecteur email ;
- un tenant Microsoft Entra ID pour le SSO.
Ressources : l'API est un processus Node mono-thread, léger. Pour quelques dizaines d'utilisateurs simultanés, 1 vCPU et 1 Go de RAM suffisent, PostgreSQL compris. L'espace disque est dicté par les pièces jointes (10 Mo par fichier au maximum).
2. Installation en développement#
2.1 Base de données#
sudo -u postgres psql -c "CREATE USER parqueo WITH PASSWORD 'parqueo_dev';"
sudo -u postgres psql -c "CREATE DATABASE parqueo OWNER parqueo;"
2.2 API#
cd server
npm install
cp .env.example .env # puis éditer JWT_SECRET au minimum
npx prisma migrate dev # applique les 11 migrations
npm run seed # 1 équipe, 1 catégorie, 1 compte admin
npm run dev # nodemon, port 4000
Le seed crée le compte admin@parqueo.local / admin1234 — à changer
immédiatement.
2.3 Client#
Dans un second terminal :
cd client
npm install
npm run dev # Vite, port 5173
Vite proxifie /api vers http://localhost:4000 : ouvrez
http://localhost:5173, pas le port 4000.
2.4 Vérifications#
curl http://localhost:4000/api/health # {"ok":true}
cd server && npm test # 26 tests
cd client && npm run lint
Sans SMTP_HOST, aucun email n'est envoyé : les messages sont écrits dans la
console de l'API, préfixés [mail non envoyé — SMTP non configuré]. C'est le
comportement attendu en développement.
2.5 Jeu de démonstration#
Attention —
prisma/seed-demo.jsest actuellement inutilisable : il référenceprisma.workflowRule, un modèle supprimé par la migrationdrop_workflow_rules, et s'interrompt sur unTypeError. Utiliseznpm run seeden attendant sa correction.
3. Installation en production#
Exemple pour une machine Debian/Ubuntu, application déployée dans
/opt/parqueo, servie par nginx sur https://parqueo.exemple.fr.
3.1 Utilisateur système et code#
sudo adduser --system --group --home /opt/parqueo parqueo
sudo -u parqueo git clone https://github.com/Lcanet2/parqueo.git /opt/parqueo
3.2 Base de données#
sudo -u postgres psql -c "CREATE USER parqueo WITH PASSWORD '<mot-de-passe-solide>';"
sudo -u postgres psql -c "CREATE DATABASE parqueo OWNER parqueo;"
3.3 API#
cd /opt/parqueo/server
sudo -u parqueo npm ci
sudo -u parqueo npx prisma generate
sudo -u parqueo cp .env.example .env
sudo -u parqueo nano .env # voir §4
sudo chmod 600 .env
sudo -u parqueo npx prisma migrate deploy
sudo -u parqueo npm run seed
Pas de
--omit=devici. La CLI Prisma est une dépendance de développement, et c'est elle qui applique les migrations : avec--omit=dev,prisma generateetprisma migrate deployne trouveraient pas le binaire local etnpxirait chercher une version arbitraire sur le registre. Le serveur n'exécute de toute façon quesrc/, les dépendances de développement ne coûtent que de l'espace disque.
migrate deploy applique les migrations existantes sans jamais en générer ni
réinitialiser la base — c'est la commande à utiliser en production, pas
migrate dev.
Un JWT_SECRET solide :
node -e "console.log(require('crypto').randomBytes(48).toString('base64url'))"
3.4 Client#
cd /opt/parqueo/client
sudo -u parqueo npm ci
sudo -u parqueo npm run build # produit client/dist
dist/ est un ensemble de fichiers statiques : aucun processus Node ne tourne
pour le front.
3.5 Reverse proxy#
Le client appelle /api en relatif. Le proxy doit donc servir le front et
l'API sur la même origine. C'est la seule contrainte de déploiement
réellement structurante.
server {
listen 443 ssl http2;
server_name parqueo.exemple.fr;
ssl_certificate /etc/letsencrypt/live/parqueo.exemple.fr/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/parqueo.exemple.fr/privkey.pem;
# Pièces jointes : 10 Mo côté application, la limite nginx par défaut
# (1 Mo) rejetterait les fichiers avant même d'atteindre Node.
client_max_body_size 12M;
root /opt/parqueo/client/dist;
index index.html;
# SPA : toute route inconnue retombe sur index.html
location / {
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://127.0.0.1:4000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 80;
server_name parqueo.exemple.fr;
return 301 https://$host$request_uri;
}
Deux points d'attention :
client_max_body_sizedoit dépasser 10 Mo, sinon les pièces jointes volumineuses échouent en 413 sans jamais atteindre l'application.- Le jeton SSO transite en query string (
?sso_token=…). Si vous journalisez les URL complètes, filtrez ce paramètre.
3.6 Service systemd#
/etc/systemd/system/parqueo.service :
[Unit]
Description=Parqueo API
After=network.target postgresql.service
Requires=postgresql.service
[Service]
Type=simple
User=parqueo
Group=parqueo
# Impératif : uploads/ est résolu relativement au répertoire de travail.
WorkingDirectory=/opt/parqueo/server
ExecStart=/usr/bin/node src/index.js
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ReadWritePaths=/opt/parqueo/server/uploads
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now parqueo
sudo systemctl status parqueo
Le fichier .env est lu par dotenv depuis le répertoire de travail : pas
besoin de EnvironmentFile.
WorkingDirectoryest critique : multer écrit dansuploads/en chemin relatif. Démarrer le processus ailleurs crée un second dossier d'uploads et rend les pièces jointes existantes introuvables.
3.7 Premier démarrage#
- ouvrir
https://parqueo.exemple.fr; - se connecter avec
admin@parqueo.local/admin1234; - changer le mot de passe (Administration → Utilisateurs) ou créer un compte admin nominatif puis supprimer celui du seed ;
- créer les équipes et les catégories ;
- parcourir Paramètres.
4. Référence des variables d'environnement#
Fichier server/.env. Seules les quatre premières sont nécessaires au
démarrage.
4.1 Cœur#
| Variable | Défaut | Rôle |
|---|---|---|
DATABASE_URL |
— | chaîne de connexion PostgreSQL (requis) |
JWT_SECRET |
— | clé de signature des jetons (requis) — la changer déconnecte tout le monde |
PORT |
4000 |
port d'écoute de l'API |
CLIENT_ORIGIN |
— | origine autorisée par CORS ; en production, l'URL publique du site |
APP_URL |
http://localhost:$PORT |
URL publique de l'API, utilisée dans les liens des emails |
4.2 Email sortant#
| Variable | Défaut | Rôle |
|---|---|---|
SMTP_HOST |
vide | si vide, aucun email n'est envoyé (mode console) |
SMTP_PORT |
587 |
|
SMTP_USER / SMTP_PASS |
vide | authentification, omise si SMTP_USER est vide |
SMTP_FROM |
— | expéditeur, ex. Parqueo <no-reply@exemple.fr> |
4.3 Clôture automatique#
| Variable | Défaut | Rôle |
|---|---|---|
AUTO_CLOSE_DAYS |
7 |
valeur initiale seulement : une fois enregistré dans Paramètres, c'est la base qui fait foi |
4.4 SSO Microsoft Entra ID#
| Variable | Défaut | Rôle |
|---|---|---|
SSO_TENANT_ID |
vide | identifiant d'annuaire — les trois premières activent le SSO |
SSO_CLIENT_ID |
vide | identifiant d'application |
SSO_CLIENT_SECRET |
vide | secret client |
SSO_REDIRECT_URI |
APP_URL + /api/auth/sso/callback |
doit correspondre exactement à l'app registration |
SSO_ALLOWED_DOMAINS |
vide | garde-fou : domaines email acceptés, séparés par des virgules |
SSO_POST_LOGIN_URL |
CLIENT_ORIGIN |
où renvoyer le navigateur après connexion |
4.5 Collecteur email#
| Variable | Défaut | Rôle |
|---|---|---|
IMAP_HOST |
vide | si vide, le collecteur est désactivé |
IMAP_PORT |
993 |
|
IMAP_USER / IMAP_PASS |
— | identifiants de la boîte |
IMAP_TLS |
true |
false pour désactiver TLS |
IMAP_POLL_SECONDS |
60 |
intervalle de relève, plancher à 15 s |
IMAP_CATEGORY_ID |
première catégorie | catégorie des tickets créés par email |
5. Configuration du SSO Microsoft Entra ID#
5.1 Côté Entra (portail Azure)#
- Azure Active Directory → Inscriptions d'applications → Nouvelle inscription.
- Nom :
Parqueo. Types de comptes : comptes de cet annuaire uniquement (mono-tenant). - URI de redirection : type Web, valeur
https://parqueo.exemple.fr/api/auth/sso/callback. - Relever l'ID d'application (client) et l'ID d'annuaire (locataire).
- Certificats et secrets → Nouveau secret client. Copier la valeur (elle n'est plus affichée ensuite) et noter la date d'expiration.
- Autorisations d'API :
openid,profile,email(autorisations déléguées Microsoft Graph). Elles sont généralement déjà présentes.
5.2 Côté Parqueo#
SSO_TENANT_ID="<id-annuaire>"
SSO_CLIENT_ID="<id-application>"
SSO_CLIENT_SECRET="<valeur-du-secret>"
SSO_REDIRECT_URI="https://parqueo.exemple.fr/api/auth/sso/callback"
SSO_ALLOWED_DOMAINS="exemple.fr"
Redémarrer l'API. Le bouton « Se connecter avec Microsoft » apparaît
automatiquement sur la page de connexion — le client interroge
GET /api/auth/config pour savoir si le SSO est actif.
5.3 Comportement#
- Un compte inconnu est créé au premier login avec le rôle
user. Les élévations de rôle restent manuelles. - Un compte créé par SSO n'a pas de mot de passe local : le formulaire classique le refuse avec un message explicite.
- Les comptes locaux continuent de fonctionner en parallèle — utile pour les prestataires et pour garder un accès de secours si le SSO tombe.
- Le secret client expire. Notez la date : à l'expiration, la connexion SSO échoue d'un bloc. Gardez au moins un compte admin local.
6. Configuration du collecteur email#
Dédiez une boîte au support (ex. support@exemple.fr) — le collecteur marque
les messages comme lus et relève l'intégralité de la boîte de réception.
IMAP_HOST="imap.exemple.fr"
IMAP_PORT=993
IMAP_USER="support@exemple.fr"
IMAP_PASS="<mot-de-passe>"
IMAP_POLL_SECONDS=60
IMAP_CATEGORY_ID=1
SMTP_FROM="Support <support@exemple.fr>"
Fonctionnement :
- un email d'un utilisateur connu crée un ticket ; un expéditeur inconnu est ignoré (et journalisé) ;
- une réponse dont le sujet contient
Ticket #ndevient un commentaire sur ce ticket — c'est ce qui rend les notifications répondables directement ; - les adresses
SMTP_FROMetIMAP_USERsont ignorées : anti-boucle ; - pièces jointes acceptées selon la même liste blanche que l'interface web, 10 Mo maximum.
Conseil : utilisez la même adresse pour SMTP_FROM et IMAP_USER, afin que
les réponses aux notifications reviennent naturellement dans le collecteur.
Vérification au démarrage, dans les journaux :
[collecteur] boîte support@exemple.fr relevée toutes les 60s
7. Sauvegarde et restauration#
Deux choses à sauvegarder : la base et server/uploads/. Le reste se
réinstalle depuis Git.
7.1 Sauvegarde#
#!/bin/sh
# /opt/parqueo/backup.sh
set -e
DEST=/var/backups/parqueo
STAMP=$(date +%F)
mkdir -p "$DEST"
sudo -u postgres pg_dump -Fc parqueo > "$DEST/parqueo-$STAMP.dump"
tar czf "$DEST/uploads-$STAMP.tar.gz" -C /opt/parqueo/server uploads
find "$DEST" -type f -mtime +30 -delete
Dans la crontab root : 15 2 * * * /opt/parqueo/backup.sh.
N'oubliez pas server/.env : il contient JWT_SECRET et les secrets SMTP/SSO.
Sauvegardez-le séparément, dans un coffre — pas dans la même archive que la
base.
7.2 Restauration#
sudo systemctl stop parqueo
sudo -u postgres dropdb parqueo
sudo -u postgres createdb parqueo -O parqueo
sudo -u postgres pg_restore -d parqueo /var/backups/parqueo/parqueo-2026-07-23.dump
sudo tar xzf /var/backups/parqueo/uploads-2026-07-23.tar.gz -C /opt/parqueo/server
sudo chown -R parqueo:parqueo /opt/parqueo/server/uploads
sudo systemctl start parqueo
Restaurer la base sans les uploads laisse des pièces jointes référencées en base mais absentes du disque : le téléchargement échoue. Les deux vont ensemble.
8. Mise à jour#
sudo systemctl stop parqueo
cd /opt/parqueo
sudo -u parqueo git pull
cd server
sudo -u parqueo npm ci
sudo -u parqueo npx prisma generate
sudo -u parqueo npx prisma migrate deploy
cd ../client
sudo -u parqueo npm ci
sudo -u parqueo npm run build
sudo systemctl start parqueo
Sauvegardez avant toute mise à jour qui embarque une migration : Prisma ne propose pas de retour arrière automatique.
Si le déploiement introduit un changement de clé de stockage côté navigateur
(comme le passage de itdesk_token à parqueo_token lors du changement de
marque), les sessions ouvertes sautent et chacun doit se reconnecter. Prévenez
les utilisateurs.
9. Exploitation#
9.1 Journaux#
Tout part sur la sortie standard, donc dans journald :
sudo journalctl -u parqueo -f
sudo journalctl -u parqueo --since "1 hour ago" | grep -E "collecteur|workflow|mail"
Préfixes utiles :
| Préfixe | Origine |
|---|---|
[collecteur] |
collecteur IMAP (tickets créés, emails ignorés, erreurs) |
[clôture auto] |
clôture automatique horaire |
[mail] |
échec d'envoi SMTP |
[mail non envoyé — SMTP non configuré] |
SMTP absent |
[workflow « … »] |
action de workflow en échec |
9.2 Supervision#
- Sonde de vie :
GET /api/health→{"ok":true}. - Surveillez l'espace disque de
server/uploads/: rien n'est jamais purgé, y compris les fichiers des tickets supprimés. - Surveillez la taille de la table
ticket_comments: elle porte à la fois les messages et le journal d'événements, et croît vite.
9.3 Tâches planifiées internes#
Aucune crontab à créer : la clôture automatique (horaire) et le collecteur
email (selon IMAP_POLL_SECONDS) tournent dans le processus de l'API. Ils
s'arrêtent avec lui.
10. Dépannage#
| Symptôme | Cause probable | Correction |
|---|---|---|
EADDRINUSE :::4000 |
une autre instance tourne déjà | ss -ltnp | grep 4000, arrêter le processus ou changer PORT |
| Page blanche, 404 sur les routes internes | nginx sans repli SPA | ajouter try_files $uri $uri/ /index.html |
| Toutes les requêtes API en 401 | JWT_SECRET modifié, ou jeton expiré (7 j) |
se reconnecter |
| 403 « Accès refusé » sur une page admin | rôle insuffisant, ou rôle changé sans reconnexion | se déconnecter/reconnecter : le rôle est figé dans le jeton |
| Erreurs CORS en développement | appel direct au port 4000 | passer par 5173, ou aligner CLIENT_ORIGIN |
| 413 à l'envoi d'une pièce jointe | client_max_body_size nginx trop bas |
passer à 12M |
| « Fichier manquant ou type non autorisé » | extension hors liste blanche | voir la liste dans la doc technique §9 |
| Aucun email reçu | SMTP_HOST vide |
les journaux affichent [mail non envoyé…] |
| Emails entrants ignorés | expéditeur inconnu de Parqueo | créer le compte, ou l'importer en masse |
| Le collecteur ne démarre pas | IMAP_HOST vide |
aucune ligne [collecteur] au démarrage |
| Boucle de tickets créés par email | SMTP_FROM ≠ IMAP_USER et boîte auto-répondeuse |
aligner les deux adresses |
| Bouton SSO absent | une des trois variables SSO_* manque |
vérifier GET /api/auth/config |
AADSTS50011 (redirect URI) |
l'URI ne correspond pas à l'app registration | aligner SSO_REDIRECT_URI au caractère près |
| « Session SSO expirée » | plus de 10 minutes entre le départ et le retour | recommencer la connexion |
| Pièces jointes introuvables après un déplacement | processus lancé hors de server/ |
corriger WorkingDirectory |
prisma.workflowRule is undefined |
seed-demo.js, script cassé |
utiliser npm run seed |
| Migration bloquée | schéma divergent | npx prisma migrate status, ne jamais lancer migrate dev en production |