## 3. Architecture technique Trois couches, chacune volontairement minimale : - **Base de données** : MySQL (base ASPIC, utilisateur dédié crm_user — même logique que pour les autres applications du VPS : un compte limité à sa base plutôt que root). - **API** : quelques fichiers PHP (PDO) exposant le CRUD contacts en JSON, packagés dans un conteneur léger (image officielle php:8.3-apache + extensions pdo_mysql / mysqli). - **Frontend** : un seul fichier index.html en JavaScript vanilla — pas de build, pas de dépendance, tableau + formulaire d'ajout/modification/suppression appelant l'API en fetch(). ### Recette de déploiement réutilisable Pour éviter de redécouvrir les mêmes pièges, la recette suivante (validée sur une précédente application PHP du VPS) a été réutilisée telle quelle : image officielle + extensions installées à chaud au démarrage (plutôt qu'un Dockerfile/build:, non testé sur ce Docker Manager), et un service SFTP dédié pour déposer les fichiers, avec le chown fait par le conteneur applicatif lui-même à chaque démarrage (plus fiable qu'un conteneur de correction de permissions séparé, dont l'ordre de démarrage n'est pas garanti sur cette interface). **docker-compose.yml** (situé dans `/docker/aspic/` sur le VPS — chemin confirmé le 30 août 2026, voir section 23) ```yaml services: crm-app: image: 'php:8.3-apache' restart: unless-stopped command: > bash -c "chown -R 1001:1001 /var/www/html && docker-php-ext-install pdo_mysql mysqli && apache2-foreground" environment: DB_HOST: "mysql" DB_NAME: "ASPIC" DB_USER: "crm_user" DB_PASS: "***" BREVO_API_KEY: "***" LIEN_DOC_CLE_SECRETE: "***" ROUTER_DB_HOST: "mysql2" ROUTER_DB_USER: "crm_user" ROUTER_DB_PASS: "***" volumes: - ./src:/var/www/html networks: - proxy sftp: image: atmoz/sftp:latest restart: unless-stopped volumes: - ./src:/home/jeanmarc/www ports: - "2224:22" command: jeanmarc:***:1001::www networks: proxy: external: true ``` *`BREVO_API_KEY` et `LIEN_DOC_CLE_SECRETE` ajoutées le 26 août 2026 (voir section 18) — renseignées et déployées le jour même, fonctionnelles en production.* **`ROUTER_DB_HOST`/`ROUTER_DB_USER`/`ROUTER_DB_PASS` ajoutées le 30 août 2026 (voir section 24, MIRADOR) — connexion vers l'instance `mysql2` qui héberge désormais MIRADOR.** ### Convention de nommage et de livraison des fichiers (actée le 21 août 2026) Déployer consiste à copier dans le répertoire de l'application (le volume `./src` monté dans le conteneur, voir `docker-compose.yml` ci-dessus) les fichiers finaux de celle-ci. **Convention de livraison actée :** Claude livre désormais les fichiers sous leur nom final "propre", sans préfixe (`identification.html`, `liste_contacts.html`, `aspic.php`, `contacts.php`, `db.php`) — les anciens préfixes (`ASPIC_ecran_`, `aspic_`) utilisés en début de projet sont abandonnés. Jean-Marc conserve de son côté l'habitude d'incrémenter un numéro de version derrière chaque fichier reçu pour garder un historique, et fait lui-même le renommage final (y compris le passage `.html` → `.php` le cas échéant) au moment du transfert FTP. **Depuis le 30 août 2026 (section 23), ce dossier `./src` est aussi directement la copie de travail du dépôt Git — voir cette section pour le nouveau circuit de livraison/versionnage.** ### Points d'attention retenus pour une prochaine application PHP - UID 1001 (pas 0/root) pour l'utilisateur SFTP — un compte root est bloqué en authentification par mot de passe par la configuration SSH par défaut de l'image. - Mot de passe SFTP : éviter les caractères spéciaux actifs en shell (`: , * $ \` & | ; < > ( )` guillemets, espace) — préférer un mot de passe purement alphanumérique. - Faire le chown directement dans la commande de démarrage du conteneur applicatif (pas via un conteneur séparé) : plus robuste, ne dépend pas de l'ordre de démarrage entre conteneurs. - Repartir sur un dossier de données neuf en cas de comportement erratique persistant (permissions, authentification) plutôt que de s'acharner sur un dossier qui a accumulé plusieurs changements de configuration. - **Un conteneur avec un volume bind-mount (`./chemin:/dans/le/conteneur`) reste attaché à l'objet-dossier présent sur l'hôte au moment de son démarrage. Si ce dossier est renommé/recréé sur l'hôte (ex. `mv dossier dossier_old` puis recréation d'un `dossier` neuf) pendant que le conteneur tourne, celui-ci continue de voir l'ancien contenu jusqu'à son redémarrage — voir le piège rencontré en section 23 avec le conteneur SFTP.**