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 — §2.1 en une commande, §2.4 sans Docker
- 3. Référence des variables d'environnement
- 4. Configuration du SSO Microsoft Entra ID
- 5. Configuration du collecteur email
- 6. Configuration de l'inventaire automatique
- 7. Sauvegarde et restauration
- 8. Mise à jour
- 9. Exploitation
- 10. Dépannage
1. Prérequis#
Chemin recommandé (§2.1) — Docker avec le plugin Compose, et rien d'autre. Ni Node.js, ni PostgreSQL, ni reverse proxy à installer : tout est fourni par les images, certificat TLS compris.
Installation manuelle (§2.4 et suivantes), si vous ne voulez pas de conteneurs :
| Composant | Version | Remarque |
|---|---|---|
| Node.js | 20 LTS minimum, 22 recommandé | |
| PostgreSQL | 14 minimum | |
| Reverse proxy | nginx, Caddy, Traefik… | requis (voir §2.8) |
Optionnels, activés seulement s'ils sont configurés : un serveur SMTP (notifications), une boîte IMAP (collecteur email), un tenant Microsoft Entra ID (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#
Deux chemins. Le premier convient à la très grande majorité des cas et ne demande que Docker ; le second s'adresse à qui ne veut pas de conteneurs.
| §2.1 à §2.3 — en une commande | §2.4 à §2.9 — installation manuelle | |
|---|---|---|
| Prérequis | Docker + plugin Compose | Node.js, PostgreSQL, reverse proxy |
| Certificat TLS | obtenu et renouvelé tout seul | à votre charge (Let's Encrypt) |
| Mise à jour | docker compose pull && up -d |
git pull, rebuild, redémarrage |
| À construire | rien, les images sont publiées | client et dépendances |
2.1 Installation en une commande#
curl -fsSL https://parqueo.fr/install.sh | sh -s -- --domaine parqueo.exemple.fr
Le script vérifie Docker, télécharge la configuration, génère les secrets, tire les images publiées et démarre la stack. Il ne compile rien : ni git, ni Node, ni les sources ne sont nécessaires sur le serveur.
Sans --domaine, Parqueo répond en HTTP sur le port 80 — pratique pour évaluer
sur un réseau interne. Avec un nom de domaine, Caddy demande un certificat
Let's Encrypt au premier démarrage et le renouvelle seul ; les ports 80 et
443 doivent alors être joignables depuis Internet, le 80 servant à la
validation du domaine et à la redirection vers HTTPS.
| Élément | Détail |
|---|---|
| Répertoire | ./parqueo (surchargeable par --repertoire) |
| Configuration | .env, créé en 600, secrets générés |
| Données | volumes parqueo-db, parqueo-uploads, parqueo-caddy (certificats) |
| Images | ghcr.io/lcanet2/parqueo-api et -web |
JWT_SECRET et POSTGRES_PASSWORD sont générés une fois et ne doivent plus
changer : le premier invalide toutes les sessions ouvertes, le second rend la
base inaccessible. Une réexécution du script conserve un .env existant.
Pour une installation hors ligne ou depuis un miroir interne, PARQUEO_SOURCE
redirige le téléchargement de la configuration.
2.2 Premier démarrage#
Ouvrez l'adresse affichée par le script. Le premier écran vous fait créer le compte administrateur : Parqueo n'est livré avec aucun mot de passe par défaut.
La route qui crée ce compte ne répond que tant que la base ne contient aucun utilisateur, et se referme définitivement ensuite. Le critère est bien « aucun utilisateur » et non « aucun administrateur » : rouvrir l'installation sur une base en service laisserait n'importe qui s'y déclarer administrateur.
Ensuite : créez les équipes et les catégories, puis parcourez Paramètres.
L'amorçage automatique de la base (SEED_ON_START, actif par défaut) ne pose
qu'une catégorie et une équipe, sans lesquelles aucun ticket n'est ouvrable.
Il ne crée aucun compte, et reste sans effet sur une base déjà amorcée.
2.3 Exploitation courante#
Depuis le répertoire d'installation :
docker compose pull && docker compose up -d # mettre à jour
docker compose down # arrêter, données conservées
docker compose down -v # tout effacer, volumes compris
docker compose logs -f api # suivre les journaux
Les migrations de base sont appliquées au démarrage du conteneur api : une
mise à jour ne demande aucune commande supplémentaire. Pour les sauvegardes,
voir §7.
Les sections suivantes décrivent l'installation manuelle, sans Docker.
Exemple pour une machine Debian/Ubuntu, application déployée dans
/opt/parqueo, servie par nginx sur https://parqueo.exemple.fr.
2.4 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
2.5 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;"
2.6 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 §3
sudo chmod 600 .env
sudo -u parqueo npx prisma migrate deploy
sudo -u parqueo npm run seed
Installez toutes les dépendances (npm ci, sans --omit=dev) : la CLI
Prisma qui applique les migrations est une dépendance de développement.
migrate deploy applique les migrations existantes sans jamais réinitialiser la
base — c'est la commande de production. npm run seed pose la catégorie et
l'équipe par défaut ; il ne crée aucun compte, celui-ci se crée au premier
accès (§2.2).
Générer un JWT_SECRET solide :
node -e "console.log(require('crypto').randomBytes(48).toString('base64url'))"
L'API refuse de démarrer sans JWT_SECRET, ou avec un secret de moins de 16
caractères, et le dit explicitement dans le journal. Elle démarrait autrefois
sans, pour n'échouer qu'à la première tentative de connexion.
2.7 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.
2.8 Reverse proxy#
Le client appelle /api en relatif : le proxy doit servir le front et l'API
sur la même origine. C'est la seule contrainte de déploiement 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)
# les rejetterait avant d'atteindre Node. 12M couvre aussi les rapports
# d'inventaire (5 Mo sur /api/inventory). Si vous poussez des inventaires
# GLPI très volumineux, l'application accepte jusqu'à 15 Mo sur
# /api/inventory/glpi : montez alors cette valeur à 16M.
client_max_body_size 12M;
root /opt/parqueo/client/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html; # SPA : repli sur 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;
}
Le jeton SSO transite en query string (?sso_token=…) : si vous journalisez les
URL complètes, filtrez ce paramètre.
2.9 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 .env est lu par dotenv depuis le répertoire de travail (pas besoin de
EnvironmentFile). WorkingDirectory est critique : les pièces jointes sont
écrites dans uploads/ en chemin relatif ; démarrer ailleurs rend les fichiers
existants introuvables.
Le premier démarrage se déroule ensuite comme au §2.2.
3. Référence des variables d'environnement#
Fichier server/.env. Seules les quatre premières sont nécessaires au
démarrage.
3.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 |
3.2 Email sortant#
| Variable | Défaut | Rôle |
|---|---|---|
SMTP_HOST |
vide | si vide, aucun email n'est envoyé |
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> |
3.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 |
3.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 |
3.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 |
3.6 Inventaire automatique#
| Variable | Défaut | Rôle |
|---|---|---|
INVENTORY_TOKEN |
vide | si vide, les endpoints /api/inventory sont désactivés ; sinon, secret présenté par les agents/scripts |
INTUNE_ENABLED |
false |
connecteur Intune (permission Graph requise, voir §6) |
INTUNE_SYNC_HOURS |
6 |
cadence de la synchro Intune (0 = seulement à la demande) |
SNMP_ENABLED |
false |
scan réseau SNMP |
SNMP_RANGES |
vide | plages CIDR à scanner, séparées par des virgules |
SNMP_COMMUNITY |
public |
communauté SNMP v2c (lecture seule) |
SNMP_SCAN_HOURS |
0 |
cadence du scan (0 = seulement à la demande) |
4. Configuration du SSO Microsoft Entra ID#
4.1 Côté Entra (portail Azure)#
- Inscriptions d'applications → Nouvelle inscription.
- Nom :
Parqueo. Types de comptes : comptes de cet annuaire uniquement. - URI de redirection, type Web :
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(déléguées Microsoft Graph, généralement déjà présentes).
4.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.
4.3 Comportement#
- Un compte inconnu est créé au premier login en rôle
user; les élévations de rôle restent manuelles. - Un compte SSO n'a pas de mot de passe local : le formulaire classique le refuse.
- Les comptes locaux fonctionnent en parallèle — accès de secours si le SSO tombe.
- Le secret client expire. Notez la date : à l'expiration, le SSO échoue d'un bloc. Gardez au moins un compte admin local.
5. 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 toute 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 — ce qui rend les notifications répondables directement ; - les adresses
SMTP_FROMetIMAP_USERsont ignorées (anti-boucle).
Conseil : utilisez la même adresse pour SMTP_FROM et IMAP_USER, pour que
les réponses aux notifications reviennent dans le collecteur. Au démarrage, les
journaux affichent [collecteur] boîte support@exemple.fr relevée toutes les 60s.
6. Configuration de l'inventaire automatique#
L'inventaire peut se remplir seul via trois moyens, combinables. Tous convergent
vers le même traitement (déduplication par uuid puis numéro de série ; les
champs saisis à la main ne sont jamais écrasés). Guide d'usage côté interface :
documentation fonctionnelle §9.
6.1 Push par agent ou script (token partagé)#
INVENTORY_TOKEN="<long-secret-aléatoire>"
Sans lui, POST /api/inventory et /api/inventory/glpi répondent 404. Les
appelants le présentent en en-tête X-Parqueo-Token (ou Authorization: Bearer).
Script maison →
POST /api/inventory, corps JSON{ uuid?, serial?, name?, type?, manufacturer?, model?, os?, cpu?, ramMb?, diskGb?, software? }(au moinsuuidouserial). Idéal en tâche planifiée (cron, GPO). Le tableausoftwareremplace la liste connue ; absent, elle est conservée.Agent GLPI → installez l'agent (MSI/GPO, paquet Linux, pkg macOS) et pointez sa cible sur l'endpoint GLPI de Parqueo, avec le token en en-tête :
# agent.cfg server = https://parqueo.exemple.fr/api/inventory/glpi httpd-headers = X-Parqueo-Token: <long-secret-aléatoire>Parqueo accepte le format d'inventaire natif (JSON, compressé zlib/gzip ou non). Testez sur un poste pilote avant de généraliser.
6.2 Connecteur Microsoft Intune (sans agent)#
Pour un parc enrôlé dans Intune, Parqueo interroge Microsoft Graph — rien à déployer sur les postes.
Sur l'app registration Entra du SSO (§4), ajoutez la permission applicative Graph
DeviceManagementManagedDevices.Read.All, puis accordez le consentement administrateur.Dans le
.env(lesSSO_*doivent déjà être renseignés) :INTUNE_ENABLED=true INTUNE_SYNC_HOURS=6Redémarrez l'API : une synchro part au démarrage, puis toutes les
INTUNE_SYNC_HOURS. Un admin peut aussi la lancer depuis Inventaire → Synchroniser Intune.
6.3 Scan réseau SNMP (sans agent)#
Pour les équipements sans agent (imprimantes, switches, NAS, onduleurs) :
SNMP_ENABLED=true
SNMP_RANGES="192.168.1.0/24,10.0.0.0/28"
SNMP_COMMUNITY="public"
SNMP_SCAN_HOURS=0
Le serveur doit joindre les équipements en UDP 161 (pare-feux). Le scan se
lance depuis Inventaire → Scanner le réseau, et en tâche de fond si
SNMP_SCAN_HOURS > 0. Préférez un adressage fixe : l'identité de
déduplication d'un équipement réseau dérive de son nom système.
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 mais absentes du disque : les deux vont ensemble.
8. Mise à jour#
Installation par images (§2.1) — deux commandes, depuis le répertoire d'installation :
docker compose pull
docker compose up -d
Les migrations de base sont appliquées au démarrage du conteneur api. Pour
rester sur une version précise plutôt que sur la dernière, renseignez
PARQUEO_VERSION dans le .env (par exemple PARQUEO_VERSION=0.1.0).
Installation manuelle (§2.4) :
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 embarquant une migration : Prisma ne propose pas de retour arrière automatique.
9. Exploitation#
9.1 Journaux#
Tout part sur la sortie standard, donc dans journald :
sudo journalctl -u parqueo -f
Préfixes utiles :
| Préfixe | Origine |
|---|---|
[collecteur] |
collecteur IMAP |
[clôture auto] |
clôture automatique horaire |
[intune] / [snmp] |
synchro Intune / scan réseau |
[mail] |
échec d'envoi SMTP |
[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. - La table
ticket_commentsporte messages et journal d'événements : elle croît vite.
9.3 Tâches planifiées internes#
Aucune crontab à créer : la clôture automatique (horaire), le collecteur email
(selon IMAP_POLL_SECONDS), et — si activées — la synchro Intune
(INTUNE_SYNC_HOURS) et le scan SNMP (SNMP_SCAN_HOURS) tournent dans le
processus de l'API et s'arrêtent avec lui.
10. Dépannage#
| Symptôme | Cause probable | Correction |
|---|---|---|
EADDRINUSE :::4000 |
une autre instance tourne déjà | 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 |
| 413 à l'envoi d'une pièce jointe | client_max_body_size nginx trop bas |
passer à 12M |
413 sur /api/inventory depuis un agent |
rapport au-delà de 5 Mo (15 Mo pour /glpi), ou client_max_body_size nginx trop bas |
relever client_max_body_size ; réduire le périmètre remonté par l'agent |
| L'agent d'inventaire ne remonte rien, sans erreur visible | l'agent poste sur une autre URL, ou INVENTORY_TOKEN absent (l'API répond alors 404) |
vérifier le token et les journaux de l'agent |
| « Fichier manquant ou type non autorisé » | extension hors liste blanche (images, PDF, docs bureautiques, archives, logs) | convertir ou compresser le fichier |
| 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 |
| 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 |
| Pièces jointes introuvables après un déplacement | processus lancé hors de server/ |
corriger WorkingDirectory |
| Migration bloquée | schéma divergent | npx prisma migrate status ; ne jamais lancer migrate dev en production |