---
title: "Scripts bin/"
description: "Point d'entrée en ligne de commande de Hydrogen. Trois familles :"
---

Point d'entrée en ligne de commande de Hydrogen. Trois familles :

- **Crons / workers** — à planifier (Task Scheduler en local, cron en prod). Ils
  vidangent des tampons, recalculent des agrégats ou régénèrent des fichiers
  statiques.
- **Opérations ponctuelles** — déploiement, bundling des migrations, application
  des réglages Meilisearch. Lancés à la main au moment voulu.
- **Outils de dev / debug** — harnais de test et diagnostic, jamais en prod.

> **Invocation PHP.** Le binaire n'est pas dans le `PATH` sur la machine de dev :
> utiliser le chemin complet, p. ex.
> `/c/laragon/bin/php/php-8.4.4-nts-Win32-vs17-x64/php.exe bin/<script>.php`.
> Sur le serveur, `php bin/<script>.php` suffit.

Tous les scripts chargent `.env` via `Hydrogen\Config\Env::load()` puis
construisent le conteneur PHP-DI (`useAutowiring(true)` + `useAttributes(true)`),
sauf `robots-generate.php` (pas besoin du conteneur).

**Convention de codes de sortie** : `0` succès · `1` échec de bootstrap / IO ·
`2` échec d'exécution (worker à relancer) ou arguments invalides.

---

## Crons / workers

À cadencer périodiquement. Sauf mention contraire, un **seul** worker suffit :
les tampons sont vidangés FIFO par id auto-incrémenté et chaque lot est commité
atomiquement, donc deux workers concurrents ne feraient que se disputer les mêmes
lignes (le perdant noop).

### `describe_worker.py` *(Python, worker IA)*

**Seul script Python** de `bin/`. Worker **hors-bande** de description IA : il draine
la file `work.media_to_describe` **sans** passer par l'endpoint synchrone
`/describe-ai` (qui timeoute sur les modèles lents). Par média : récupère la
vignette JPEG (`GET /admin/media/{hex}/base64?format=jpg`), appelle le modèle
vision **directement** (`/v1/chat/completions`), puis persiste via
`POST /admin/media/describe` (cœur) + `POST /admin/media/{hex}/enrichment`
(poi/person_count), et retire la ligne de file.

```bash
# dépendances (une fois) : py -m pip install PyMySQL requests
py -3 bin/describe_worker.py --env C:\chemin\.env --log C:\chemin\worker.log
py -3 bin/describe_worker.py --dry-run --batch 1        # test à blanc
```

- **Cadence** : toutes les **minutes** — un **verrou OS** (fcntl/msvcrt) fait sortir
  une exécution si une précédente tourne encore, et se relâche même en cas de crash.
- **Multi-environnement** : la file `work` est **partagée** (dev/preprod/prod). Chaque
  ligne est estampillée `environment` à l'enqueue depuis `WORK_QUEUE_ENV` ; le worker
  ne claim **que** sa propre valeur. `WORK_QUEUE_ENV` + `APP_URL` + `ADMIN_API_TOKEN`
  doivent viser le **même** environnement.
- **Options** : `--env <fichier>`, `--batch <n>`, `--dry-run`, `--log <fichier>`
  (auto-log + rotation `DESCRIBE_WORKER_LOG_MAX_BYTES`), `--verbose`.
- **File** : la ligne est retirée sur **succès** et sur **échec spécifique au média**
  (4xx, image rejetée, JSON illisible) ; **gardée** sur échec transitoire d'infra
  (connexion / timeout / 5xx) pour re-tentative.
- **Modèle** : éviter un modèle « raisonnant » (thinking) — il épuise le budget tokens
  et renvoie `content` vide ; rester sur un modèle rapide non-raisonnant.

### `notifications-flush.php`

Pousse les notifications en file vers OneSignal (1 ligne → 1 push, N lignes → un
digest par destinataire).

```bash
php bin/notifications-flush.php
```

- **Cadence** : `NOTIFICATION_DIGEST_INTERVAL_MINUTES` (2 en dev, 5 en prod).
- **Sortie** : `recipients`, `pushed`, `skipped`, `failed`.
- **Service** : `NotificationFlushService`.

### `media-counters-flush.php`

Vidange le tampon de compteurs média, met à jour `media_stats` + `media_view_daily`,
puis GC la table de déduplication.

```bash
php bin/media-counters-flush.php
```

- **Cadence** : `MEDIA_COUNTERS_FLUSH_TICK_SECONDS` (défaut 30 s en prod).
- **Sortie** : `drained`, `bumped`, `deletedEvents`, `gcRows`.
- **Service** : `MediaCountersFlushService`.

### `user-counters-flush.php`

Identique au précédent pour les profils utilisateur : `user_stats` +
`user_view_daily`.

```bash
php bin/user-counters-flush.php
```

- **Cadence** : `USER_COUNTERS_FLUSH_TICK_SECONDS` (défaut 30 s en prod).
- **Sortie** : `drained`, `bumped`, `deletedEvents`, `gcRows`.
- **Service** : `UserCountersFlushService`.

### `tracking-flush.php`

Vidange le tampon de clics (`tracking_event`) et met à jour `tracking_stats` +
`tracking_daily`. Les **conversions** ne passent pas par ici (écrites en synchrone
par l'endpoint postback admin).

```bash
php bin/tracking-flush.php
```

- **Cadence** : `TRACKING_FLUSH_TICK_SECONDS` (défaut 30 s en prod).
- **Sortie** : `drained`, `bumped`, `deletedEvents`.
- **Service** : `TrackingFlushService`.

### `account-purge.php`

Efface **irréversiblement** les comptes dont la fenêtre de grâce RGPD est écoulée :
suppression des médias possédés (fichiers + DB + index), anonymisation de la ligne
`user`, retrait du document de recherche et du token de suppression.

```bash
php bin/account-purge.php [limit]
```

- **Argument** : `limit` plafonne le nombre de comptes traités ce tick (défaut
  `ACCOUNT_PURGE_BATCH_SIZE`, fallback 50).
- **Cadence** : quotidienne. Idempotent — un crash retente au tick suivant.
- **Sortie** : `purgedUsers`, `erasedMedias`.
- **Service** : `AccountPurgeService`.

### `platform-metrics-rollup.php`

Recalcule le rollup KPI quotidien (`hxa_bo.platform_metric_daily`) lu par les
dashboards de tendances du back-office.

```bash
php bin/platform-metrics-rollup.php
```

- **Cadence** : une fois par jour, hors pointe (p. ex. 03:00). La fenêtre
  `PLATFORM_METRICS_LOOKBACK_DAYS` (défaut 7) re-plie les derniers jours pour
  corriger les lignes arrivées en retard. UPSERT → rattrapage manuel sûr.
- **Sortie** : `days`, `flowRows`, `snapshotRows`, `countryRows`.
- **Service** : `PlatformMetricsRollupService`.

### `sitemap-generate.php`

Régénère `public/sitemap.xml` à partir des providers de sitemap enregistrés.
Écriture atomique (`.tmp` + `rename()`) pour qu'un crawler ne voie jamais un
document à moitié écrit.

```bash
php bin/sitemap-generate.php
```

- **Cadence** : périodique, pour rester en phase avec le catalogue (nouveaux pays
  indexés, nouvelles pages du registry…).
- **Sortie** : nombre d'URLs, octets, durée.
- **Fichier statique** : servi directement par le serveur web, hors hot path PHP.

### `robots-generate.php`

Régénère `public/robots.txt` à partir de `APP_URL`. Même écriture atomique.

```bash
php bin/robots-generate.php
```

- **Cadence** : à chaque changement de `APP_URL` (typiquement au déploiement) ou
  dans le même tick que `sitemap-generate.php`.
- **Prérequis** : `APP_URL` non vide (sinon code `1`).
- **Sortie** : `Disallow: /api/` + `/admin/`, ligne `Sitemap:` vers `sitemap.xml`.

---

## Opérations ponctuelles

### `deploy.php`

Déploiement FTP/FTPS à la demande, push du **delta** uniquement (cf.
[docs/deploy.md](deploy.md) pour la configuration et les détails).

```bash
php bin/deploy.php [--config=PATH] [--dry-run] [--mirror] [--help]
php bin/deploy.php --rollback [--backup=ID] [--dry-run] [--config=PATH]
```

| Option | Effet |
|---|---|
| `--config=PATH` | Chemin du fichier de config (défaut `./deploy.config.php`). |
| `--dry-run` | Affiche le plan sans contacter le serveur. |
| `--mirror` | Supprime aussi les fichiers distants absents en local. |
| `--rollback` | Annule le dernier déploiement depuis sa sauvegarde. |
| `--backup=ID` | Sauvegarde précise à restaurer (défaut : la plus récente). |
| `--help` | Affiche l'aide. |

- **Codes** : `0` ok / dry-run · `1` config ou transfert · `2` arguments invalides.

### `migrations-bundle.php`

Regroupe les migrations SQL **non encore déployées** en **un fichier par base**
(`database/deploy/<clé>.sql`), à exécuter sur le serveur puis supprimer. Pas de
runner automatique : le schéma est appliqué à la main.

```bash
php bin/migrations-bundle.php             # génère les bundles (sûr, relançable)
php bin/migrations-bundle.php --dry-run   # liste le delta sans écrire
php bin/migrations-bundle.php --mark      # avance le watermark (APRÈS un déploiement réussi)
```

- **Delta** = migrations dont le préfixe `YYYY_MM_DD_HHMMSS` est supérieur au
  watermark de `database/migrations/.bundle-state.json`.
- **Base cible** = directive d'en-tête `-- @database: <clé>` sur sa propre ligne.
  Clés : `main` (défaut → `DB_NAME`), `bo` (`BO_DB_NAME`), `work` (`WORK_DB_NAME`).
  Le fichier généré n'émet **pas** de `USE` : l'opérateur choisit la base à
  l'exécution.
- **Flux** : générer → exécuter les `.sql` → `--mark` → supprimer
  `database/deploy/`.
- **Codes** : `0` ok · `1` IO · `2` arguments invalides.

### `media-meili-apply-settings.php`

Pousse les réglages d'index média + synonymes dont dépend le pipeline hashtags.
Idempotent (`updateSettings` / `updateSynonyms` sont des upserts côté serveur).

```bash
php bin/media-meili-apply-settings.php
```

À lancer :

- après le premier déploiement (l'index refuse les filtres `hashtags=` et les
  `facetSearch` tant que `filterableAttributes` ne liste pas `hashtags`),
- après édition de `config/hashtag_synonyms.php` (Meili remplace la map en entier),
- après modification de la forme des réglages dans
  `MeilisearchMediaSync::applySettings()`.

- **Sortie** : confirmation settings + nombre de synonymes.
- **Codes** : `0` ok · `1` settings KO · `2` settings ok mais synonymes KO
  (relance sûre).

---

## Outils de dev / debug

> **Non destinés à la production.** Ces scripts écrivent en base et sur disque ;
> ne jamais les lancer sur un environnement réel.

### `debug_google_token.php`

Diagnostique pourquoi un `id_token` Google échoue à la vérification : header JOSE
décodé (kid, alg), payload non vérifié (iss, aud, exp, email…), puis résultat de
la vraie vérification signature + claims.

```bash
php bin/debug_google_token.php "<id_token>"
```

### `test-media-upload.php` · `test-media-http.php` · `test-media-glide.php` · `test-media-crud.php`

Harnais de test ad-hoc du pipeline média. Ils fabriquent un JPEG synthétique via
Imagick, ciblent le **premier utilisateur confirmé** trouvé en base, et exercent
respectivement :

- **`test-media-upload.php`** — l'upload via le service domaine (pHash, etc.).
- **`test-media-http.php`** — l'upload via une requête PSR-7 (3 slots de fichier).
- **`test-media-glide.php`** — l'upload puis la transformation Glide.
- **`test-media-crud.php`** — le cycle CRUD complet, y compris le cas interdit
  (média d'un autre utilisateur). ⚠️ **Purge les médias existants** de
  l'utilisateur de test pour être déterministe.

```bash
php bin/test-media-crud.php
```

Aucun argument ni flag — tout est câblé en dur dans le script.
