Déploiement

Une migration de schéma est en attente et son ordre est contraignant.

PostTable::buildOnlinePostsQuery() utilise select(''), que Doctrine 1 développe en la liste des colonnes *déclarées par le modèle. Déployer le nouveau BasePost avant d’avoir exécuté l'`ALTER TABLE` fait donc lever Unknown column 'p.track_duration' à chaque requête qui l’emprunte.

Ce que ça casse, et qui dépasse la seule API : /posts, le flux RSS, la playlist XSPF, et toutes les écritures de l’administrationPost::preSave() renseigne les deux nouvelles colonnes, donc elles figurent dans chaque INSERT et chaque UPDATE. Créer ou modifier un post échoue. Seules / et /post/<slug> survivent, parce qu’elles passent par FIELDS_BASIC, qui ne liste pas les nouvelles colonnes.

Sur la base de production, avant de synchroniser les sources :

ALTER TABLE post
  ADD COLUMN track_duration INT NULL COMMENT 'Duree du morceau en secondes',
  ADD COLUMN track_size INT NULL COMMENT 'Taille du fichier en octets',
  ADD INDEX online_publish_idx (is_online, publish_on),
  ADD INDEX track_author_idx (track_author(191));

La production tourne sur MariaDB 10.11, non sur MySQL — le conteneur de développement a été aligné dessus. L’opération y est ALGORITHM=INPLACE, LOCK=NONE et sub-seconde sur les ~8 200 lignes actuelles : pas de fenêtre de maintenance nécessaire.

Détail complet et étapes suivantes : API Subsonic.

Comment la mise en ligne se produit

Plesk tire main à chaque poussée. Il n’y a pas de geste de mise en ligne : une fusion sur main est une mise en production. Mesure du 2026-08-19, commit ba9d5c2 : poussé à 04:37:15 UTC, fichier écrit sur l’origine à 04:37:20 — cinq secondes.

Conséquences, qui expliquent plusieurs choix du dépôt :

  • Aucune migration de schéma ne s’exécute au déploiement. Il faut la passer à la main avant la fusion (voir l’encadré en tête de page).

  • Il n’existe aucune étape de mise en ligne où accrocher une commande. Tout ce qui doit suivre un déploiement s’accroche ailleurs : à un workflow GitHub déclenché sur push, ou aux « actions de déploiement supplémentaires » de l’extension Git de Plesk.

Les caches, et lequel a mordu

Trois couches, indépendantes, à ne pas confondre quand un fichier ne bouge pas :

Couche Ce qu’elle fait

Cloudflare (edge)

Sert une copie du fichier. Rien ne la prévient d’un déploiement. C’est elle qui a servi pendant des heures un bande-usee.js antérieur à deux correctifs déjà en ligne sur l’origine, le 2026-08-19.

Navigateur

Obéit au cache-control que l’origine émet désormais elle-même, depuis src/web/.htaccess. Une purge n’atteint pas ce cache-là : la seule façon de le reprendre est qu’il expire, d’où un max-age court.

Service worker (src/web/sw.js)

Troisième couche, distincte des deux autres.

Ce qui les traite :

  • .github/workflows/purge-cloudflare.yml purge la zone après chaque poussée sur main, puis vérifie que les octets servis par Cloudflare sont ceux du dépôt. Il a besoin de deux secrets : CLOUDFLARE_API_TOKEN (permission « Zone / Cache Purge ») et CLOUDFLARE_ZONE_ID.

  • Les assets de désastre portent une empreinte ?v=<date du fichier>, posée par sfDesastreManager. Leur adresse change quand le fichier change, donc aucun cache ne peut les servir périmés — y compris celui des navigateurs, que la purge n’atteint pas.

  • src/web/.htaccess donne un Cache-Control à chaque fichier statique. Deux régimes, parce que les deux caches n’ont pas les mêmes moyens de se corriger :

    Les pages HTML ne sont pas touchées : les actions posent leurs propres en-têtes.

Ce bloc et le réglage Cloudflare Browser Cache TTL forment une paire, et l’ordre compte.

Basculer le réglage sur « Respect Existing Headers » avant que ce .htaccess soit en ligne ferait tomber les navigateurs sur leur cache heuristique — une fraction de l’âge du fichier, soit quelques minutes pour un fichier récent et des années pour jquery.js, qui date de 2012. Le piège des 31 jours serait remplacé par un piège imprévisible.

Donc : déployer d’abord, constater que l’origine émet bien l’en-tête, basculer le réglage ensuite.

Pour constater qu’un fichier est bien arrivé, interroger l'URL nue.

Ajouter ?cachebust=… à la main désigne une ressource différente aux yeux de Cloudflare, qui va donc la chercher à l’origine. On teste alors une adresse que personne ne demande, et on conclut à tort que le déploiement est bon.

Déploiement par rsync (déprécié)

make deploy n’est plus le chemin de mise en ligne : Plesk tire main tout seul. La cible reste décrite ici parce qu’elle sert encore à pousser ce que git ne porte pas, src/vendor en particulier.

# Simulation, valeur par défaut de RSYNC_PARAMETERS
make deploy PROFILE=www.musiqueapproximative.net

# Envoi réel
make deploy PROFILE=www.musiqueapproximative.net RSYNC_PARAMETERS=

La cible enchaîne make configure, composer install --no-dev puis rsync. L’étape composer install est indispensable : src/vendor est gitignoré mais part bien par rsync, et sans elle la production se retrouve sans dépendances — getID3 notamment, dont Post::preSave() a besoin à chaque enregistrement.

Après synchronisation, sur l’hôte :

php symfony cache:clear
php symfony musiqueapproximative:scan-tracks --env=prod --limit=200

Le rattrapage renseigne durées et tailles. Tant qu’il n’a pas tourné, aucun morceau n’a de durée et les clients Subsonic n’affichent ni barre de progression ni seek. Commencer par --limit=200 pour mesurer avant de lancer les ~7 000 fichiers.

Déploiement avec Docker

Prérequis

  • Docker et Docker Compose installés

  • Accès au serveur de production

  • Variables d’environnement configurées

Utilisation de l’image GHCR

# Pull de l'image depuis GHCR
docker pull ghcr.io/constructions-incongrues/musiqueapproximative:latest

# Démarrage avec docker-compose
docker-compose up -d

Configuration de production

Créer un fichier .env avec les variables nécessaires :

DATABASE_URL=mysql://user:password@host:3306/database
APP_ENV=prod
APP_DEBUG=false

Déploiement manuel

Publication d’une nouvelle version

VERSION=<VERSION>
git hf release start ${VERSION}
git hf release finish ${VERSION}

Déploiement vers le serveur

VERSION=<VERSION>
git checkout ${VERSION}
ant configure build deploy -Dprofile=pastishosting

Vérifications post-déploiement

Après le déploiement, vérifier :

  1. Application accessible : Tester l’URL de production

  2. Base de données : Vérifier la connexion

  3. Logs : Consulter les logs pour détecter les erreurs

  4. Performance : Vérifier les temps de réponse

# Vérifier les logs
docker-compose logs -f

# Vérifier l'état des conteneurs
docker-compose ps

# Tester la connexion à la base de données
docker-compose exec php php symfony doctrine:check-schema

Rollback

En cas de problème, revenir à la version précédente :

# Avec Docker
docker-compose down
docker pull ghcr.io/constructions-incongrues/musiqueapproximative:previous-tag
docker-compose up -d

# Avec Git
git checkout <previous-version>
ant configure build deploy -Dprofile=pastishosting

Monitoring

Logs

Les logs sont disponibles dans :

  • Application : src/log/

  • Docker : docker-compose logs

  • Nginx : Logs du conteneur nginx

Métriques

Surveiller :

  • Utilisation CPU/RAM

  • Temps de réponse

  • Erreurs 5xx

  • Espace disque

Sauvegarde

Base de données

# Backup
docker-compose exec db mysqldump -u root -p database > backup.sql

# Restore
docker-compose exec -T db mysql -u root -p database < backup.sql

Fichiers

Sauvegarder régulièrement :

  • src/web/tracks/ - Fichiers audio

  • src/web/avatars/ - Avatars utilisateurs

  • src/data/ - Données de configuration

Troubleshooting

L’application ne démarre pas

  1. Vérifier les logs : docker-compose logs

  2. Vérifier la configuration : .env et docker-compose.yml

  3. Vérifier les permissions des fichiers

Erreurs de base de données

  1. Vérifier la connexion : docker-compose exec php php symfony doctrine:check-schema

  2. Vérifier les migrations : État de la base de données

  3. Restaurer depuis un backup si nécessaire

Performance dégradée

  1. Vider le cache : docker-compose exec php php symfony cache:clear

  2. Vérifier les ressources système

  3. Analyser les slow queries