T ToutadminDocumentation

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émentVersionPourquoi
PHP8.1 ou plus récentTypes d'union, enum, readonly, never : le code les utilise.
pdo_sqliteLa base entière. Sans elle, rien ne tourne.
mbstringDécoupage correct des chaînes dans seize langues.
opensslrecommandéChiffrement des secrets en base (mots de passe SMTP, jetons d'externalisation).
curlrecommandéWebhooks sortants et dépôt des sauvegardes chez un tiers.
intlrecommandé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.

Aucune dépendance

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
La racine web pointe sur 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,
];
Le secret d'instance

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#

CheminDroitsQui écrit
data/0770Le serveur web, et lui seul
data/app.sqlite0660Créé par l'installation
config.php0640Personne — lecture seule pour le serveur
app/, public/0755Personne — 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'auditselon 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'externalisequand 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.

Sur un hébergement sans cron

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 :

  1. Les prérequis, vérifiés et affichés — ce qui bloque est marqué comme tel.
  2. Le jeton d'installation, s'il est configuré.
  3. L'organisation : le nom affiché partout, et le nombre de jours de congés annuels attribué à chaque nouveau salarié non-freelance.
  4. La langue par défaut de l'instance, parmi les 16.
  5. 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.