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#

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 :

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#

Attentionprisma/seed-demo.js est actuellement inutilisable : il référence prisma.workflowRule, un modèle supprimé par la migration drop_workflow_rules, et s'interrompt sur un TypeError. Utilisez npm run seed en 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=dev ici. La CLI Prisma est une dépendance de développement, et c'est elle qui applique les migrations : avec --omit=dev, prisma generate et prisma migrate deploy ne trouveraient pas le binaire local et npx irait chercher une version arbitraire sur le registre. Le serveur n'exécute de toute façon que src/, 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 :

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.

WorkingDirectory est critique : multer écrit dans uploads/ 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#

  1. ouvrir https://parqueo.exemple.fr ;
  2. se connecter avec admin@parqueo.local / admin1234 ;
  3. changer le mot de passe (Administration → Utilisateurs) ou créer un compte admin nominatif puis supprimer celui du seed ;
  4. créer les équipes et les catégories ;
  5. 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)#

  1. Azure Active Directory → Inscriptions d'applications → Nouvelle inscription.
  2. Nom : Parqueo. Types de comptes : comptes de cet annuaire uniquement (mono-tenant).
  3. URI de redirection : type Web, valeur https://parqueo.exemple.fr/api/auth/sso/callback.
  4. Relever l'ID d'application (client) et l'ID d'annuaire (locataire).
  5. Certificats et secrets → Nouveau secret client. Copier la valeur (elle n'est plus affichée ensuite) et noter la date d'expiration.
  6. 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#


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 :

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#

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_FROMIMAP_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