Installer l'édition PHP
L'édition PHP se dépose telle quelle sur un hébergement mutualisé : pas de Composer, pas de gestionnaire de paquets, pas de service à faire tourner. Un dossier, un fichier de configuration, une tâche planifiée. Ce tutoriel va du dossier vide à l'instance en production, y compris ce que la plupart des guides laissent de côté : les permissions, le TLS, la tâche planifiée, les sauvegardes et la mise à jour.
Ce qu'il faut#
| Élément | Version | Pourquoi |
|---|---|---|
| PHP | 8.1 ou plus récent | Types d'union, enum, readonly, never : le code les utilise. |
pdo_sqlite | — | La base entière. Sans elle, rien ne tourne. |
mbstring | — | Découpage correct des chaînes dans seize langues. |
openssl | recommandé | Chiffrement des secrets en base (mots de passe SMTP, jetons d'externalisation). |
curl | recommandé | Webhooks sortants et dépôt des sauvegardes chez un tiers. |
intl | recommandé | Dates écrites comme dans l'édition Node. Sans elle, les noms de mois viennent des dictionnaires. |
L'assistant d'installation vérifie tout cela et le montre à l'écran avant de vous laisser continuer : ce qui est bloquant arrête l'installation, ce qui est facultatif est signalé sans bloquer.
Il n'y a rien à installer avec composer : le lecteur IMAP, l'archive
tar.gz, le PDF, le CSV, le code QR et le client HTTP sont écrits dans le
produit. C'est ce qui permet de le déposer sur un hébergement où vous n'avez pas de
ligne de commande.
1. Déposer les fichiers#
Récupérez l'archive de l'édition PHP, puis déposez-la. Le dossier ressemble à ceci :
toutadmin/
├── app/ le code (jamais servi par le serveur web)
│ ├── Core/ noyau : routage, base, sessions, sécurité
│ ├── Modules/ métier : RH, gestion, projets, qualité…
│ ├── Controllers/
│ ├── views/ gabarits
│ ├── locales/ les seize dictionnaires
│ └── schema.sql le schéma, identique à celui de l'édition Node
├── public/ ← la racine web pointe ici, et nulle part ailleurs
│ ├── index.php le contrôleur frontal
│ ├── css/ js/
├── data/ base, coffre-fort, parapheur, pièces reçues, sauvegardes
├── tools/ cron.php, check-keys.php
├── tests/
├── config.sample.php
└── VERSION
public/, pas sur le dossier
C'est le point le plus important de cette page. Si le serveur sert le dossier entier,
data/app.sqlite devient téléchargeable par n'importe qui : toute la base,
mots de passe hachés et coffre-fort compris. Le dossier data/ est au-dessus
de public/ exprès.
Sur un hébergement mutualisé#
Deux cas se présentent. Si votre hébergeur permet de choisir la racine d'un domaine
(cPanel, Plesk, o2switch, Infomaniak, OVH en mode « dossier racine »), déposez le dossier
hors de www/ et faites pointer le domaine sur toutadmin/public.
Si la racine est imposée — souvent www/ ou public_html/ — déposez
le contenu de public/ dedans et le reste au-dessus, puis
corrigez le chemin en tête de public/index.php :
www/ ← racine imposée
├── index.php
├── css/ js/
toutadmin/ ← le reste, hors de portée du web
├── app/
├── data/
└── config.php
// www/index.php — la ligne à ajuster
require_once __DIR__ . '/../toutadmin/app/bootstrap.php';
En dernier recours, si vous ne pouvez rien mettre au-dessus de la racine, le fichier
.htaccess livré dans data/ refuse déjà l'accès. C'est une
ceinture, pas une solution : préférez toujours le dossier hors racine.
2. Écrire la configuration#
cp config.sample.php config.php
Puis ouvrez config.php :
<?php
return [
// Base SQLite et dossier de données : hors de la racine web.
'db_path' => __DIR__ . '/data/app.sqlite',
'data_dir' => __DIR__ . '/data',
// Clé propre à l'instance. À engendrer une fois :
// php -r "echo bin2hex(random_bytes(32));"
'session_secret' => '…',
// Jeton d'installation : voir l'étape suivante.
'install_token' => '',
// Adresse publique, pour les liens envoyés par courriel.
'base_url' => 'https://intranet.exemple.fr',
// À n'activer que derrière un proxy de confiance.
'trust_proxy' => false,
// Plafonds : tentatives de connexion par quart d'heure et par adresse,
// requêtes par minute et par adresse.
'login_rate_limit' => 10,
'global_rate_limit' => 300,
// Expiration : inactivité, puis durée absolue qu'aucune activité ne repousse.
'session_idle_minutes' => 60,
'session_max_hours' => 12,
];
Il scelle les signatures du parapheur et chiffre les secrets en base. Le perdre
invalide des signatures. Si vous laissez la valeur d'exemple, le produit en engendre
un tout seul dans data/.instance-key (permissions 0600) —
pensez alors à sauvegarder ce fichier avec le reste.
Vous pouvez aussi tenir la configuration ailleurs : la variable d'environnement
TOUTADMIN_CONFIG désigne un autre fichier.
3. Poser un jeton d'installation#
Entre le dépôt des fichiers et votre passage dans l'assistant, l'instance est à qui la trouve : le premier arrivé crée le compte d'administration. La fenêtre est courte, elle suffit. Posez donc une valeur au hasard avant la mise en ligne :
php -r "echo bin2hex(random_bytes(16));"
# puis dans config.php :
'install_token' => 'c3f1…',
L'assistant la demande, la compare à temps constant — une comparaison ordinaire laisserait deviner le jeton caractère par caractère — et journalise chaque refus. Sans le jeton, rien n'est créé. Une fois l'instance installée, l'assistant se referme de lui-même : le jeton peut rester, il ne sert plus.
4. Permissions#
| Chemin | Droits | Qui écrit |
|---|---|---|
data/ | 0770 | Le serveur web, et lui seul |
data/app.sqlite | 0660 | Créé par l'installation |
config.php | 0640 | Personne — lecture seule pour le serveur |
app/, public/ | 0755 | Personne — lecture seule |
# exemple sur un serveur où PHP tourne sous www-data
sudo chown -R vous:www-data /var/www/toutadmin
sudo find /var/www/toutadmin -type d -exec chmod 750 {} \;
sudo find /var/www/toutadmin -type f -exec chmod 640 {} \;
sudo chmod 770 /var/www/toutadmin/data
sudo chmod 750 /var/www/toutadmin/public
SQLite écrit aussi des fichiers voisins (-wal, -shm) : c'est le
dossier qui doit être inscriptible, pas seulement la base.
5. Le serveur web#
Nginx#
server {
listen 443 ssl http2;
server_name intranet.exemple.fr;
root /var/www/toutadmin/public; # et rien d'autre
index index.php;
ssl_certificate /etc/letsencrypt/live/intranet.exemple.fr/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/intranet.exemple.fr/privkey.pem;
client_max_body_size 20M; # dépôts au coffre-fort, pièces reçues
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
# Les fichiers déposés ne sont jamais servis directement : ils passent
# par une route qui vérifie la session et l'empreinte.
location ~ ^/(data|app|tools|tests)/ { deny all; }
}
server {
listen 80;
server_name intranet.exemple.fr;
return 301 https://$host$request_uri;
}
Apache#
<VirtualHost *:443>
ServerName intranet.exemple.fr
DocumentRoot /var/www/toutadmin/public
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/intranet.exemple.fr/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/intranet.exemple.fr/privkey.pem
<Directory /var/www/toutadmin/public>
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
Le fichier public/.htaccess livré renvoie déjà toutes les adresses vers
index.php. Sur un hébergement mutualisé Apache, c'est tout ce qu'il faut.
TLS#
Le produit pose l'en-tête Strict-Transport-Security dès que la connexion est
chiffrée, et jamais en clair : annoncé depuis une page non chiffrée il ne serait pas lu,
et il enfermerait un essai local en https pendant six mois. Sur un serveur
que vous administrez :
sudo certbot --nginx -d intranet.exemple.fr
Derrière un proxy ou un répartiteur de charge qui termine le TLS, passez
'trust_proxy' => true — et seulement là : sinon n'importe qui pourrait
annoncer l'adresse IP et le protocole de son choix dans un en-tête.
6. La tâche planifiée#
Un site PHP ne tourne qu'au moment d'une requête. Ce que l'édition Node fait dans son balayage horaire — huit opérations — se fait ici depuis le cron de l'hébergeur :
* * * * * /usr/bin/php /var/www/toutadmin/tools/cron.php >> /var/log/toutadmin-cron.log 2>&1
| Ce que fait la tâche | À quel rythme |
|---|---|
| Ferme les comptes dont le contrat est arrivé à terme | à chaque passage |
| Émet les factures des abonnements arrivés à échéance | à chaque passage, sans jamais facturer deux fois |
| Transforme les échéances en notifications | à chaque passage, dédupliquées |
| Purge les notifications lues et le journal d'audit | selon la conservation choisie |
| Relève la boîte aux lettres comptable (IMAP) | si la relève est configurée |
| Vide la file des webhooks, avec ses réessais | à chaque passage |
| Crée la sauvegarde automatique et l'externalise | quand l'intervalle est écoulé |
Appeler le script plus souvent que l'intervalle configuré ne sauvegarde pas plus souvent :
rien ne se déclenche avant l'échéance. Sur un hébergement qui limite le cron à un passage
toutes les quinze minutes, remplacez * * * * * par */15 * * * * —
les webhooks partiront simplement avec ce délai.
Certains hébergements d'entrée de gamme n'en offrent pas. Un appel HTTP depuis un
service tiers (uptime robot et consorts) ne suffit pas : cron.php
refuse d'être exécuté autrement qu'en ligne de commande, précisément pour qu'une
adresse publique ne puisse pas déclencher une sauvegarde. Dans ce cas, la sauvegarde
se lance à la main depuis l'écran Sauvegardes.
7. L'assistant#
Ouvrez votre domaine. Tant qu'aucun compte n'existe, toute adresse renvoie vers
/installation. L'assistant tient en une page :
- Les prérequis, vérifiés et affichés — ce qui bloque est marqué comme tel.
- Le jeton d'installation, s'il est configuré.
- L'organisation : le nom affiché partout, et le nombre de jours de congés annuels attribué à chaque nouveau salarié non-freelance.
- La langue par défaut de l'instance, parmi les 16.
- Le compte d'administration : adresse et mot de passe d'au moins douze caractères.
Tout est écrit en une seule transaction. Dès qu'un compte existe,
/installation renvoie à la page de connexion : l'assistant s'est refermé
tout seul, il n'y a pas de fichier à supprimer à la main.
8. Vérifier que tout marche#
# les tests, sur la machine (ils n'écrivent pas dans votre base)
php tests/run.php
# les traductions : aucun écran n'emploie une clé absente des dictionnaires
php tools/check-keys.php
Puis, dans l'interface : créez un membre, connectez-vous avec lui, déposez une pièce jointe, lancez une sauvegarde manuelle et vérifiez-la depuis l'écran Sauvegardes. Ces quatre gestes touchent la base, les fichiers, les droits et l'archive — c'est-à-dire tout ce que l'hébergement doit savoir faire.
9. Mettre à jour#
# 1. une sauvegarde d'abord, toujours
php tools/cron.php # ou l'écran Sauvegardes
# 2. remplacer le code, pas les données
# app/ public/ tools/ tests/ VERSION ← remplacés
# data/ config.php ← conservés tels quels
# 3. la base se met à jour toute seule à la première requête
Le schéma évolue par migrations idempotentes : rejouer la mise à jour ne casse rien. Une instance de l'édition Node et une instance de l'édition PHP partagent le même schéma — 142 tables, 1311 colonnes — et les mêmes mots de passe : une base passe de l'une à l'autre sans conversion.
10. Sauvegarder, vraiment#
L'archive tar.gz emporte la base (copiée par VACUUM INTO, donc
cohérente même pendant une écriture) et les cinq dossiers de fichiers : photos de profil,
CV reçus, coffre-fort, parapheur, pièces reçues. Chaque fichier y porte son empreinte
SHA-256, vérifiée à la restauration.
Une archive restée sur le serveur qu'elle protège ne protège de rien : configurez une destination distante (FTPS ou Google Drive) depuis l'écran Sauvegardes. Le détail est sur la page Sauvegardes et restauration.
Essayer en local d'abord#
cp config.sample.php config.php
php -S localhost:8000 -t public public/index.php
Le serveur intégré de PHP suffit pour découvrir le produit. Il ne convient pas à la production : un seul processus, pas de TLS, pas de limite de charge.
Documentation de Toutadmin — construite le 2026-09-13. Site autonome, indépendant du logiciel.