124543d658
- Agent studioE5 standalone en Go (console + systray) - VPN on-demand via tailscaled + tailscale up (authkey Headscale) - Resolver/serveur dans le tailnet studioe5 - Caddy on-demand TLS pour les instances - Nouveaux endpoints serveur /api/internal/send-to-node - Suppression des anciens binaires edubox-agent - Suivi dans SUIVI_VPN_ONDEMAND.md
388 lines
17 KiB
Markdown
388 lines
17 KiB
Markdown
# Analyse : Intégration de PrestaShop dans EduBox
|
|
|
|
## 1. Architecture actuelle d'EduBox (vue d'ensemble)
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
│ Serveur cloud (ex: alfrednobel.edudeploy.com) │
|
|
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ ┌─────────────┐ │
|
|
│ │ Caddy │──▶│ Next.js │──▶│ Resolver Go │──▶│ PostgreSQL │ │
|
|
│ │ TLS on-demand│ │ (dashboard) │ │ (proxy inst.)│ │ (état) │ │
|
|
│ └─────────────┘ └──────────────┘ └──────────────┘ └─────────────┘ │
|
|
│ │ │ WebSocket 3001 │
|
|
│ │ ▼ │
|
|
│ │ Agent EduBox (Go) sur PC étudiant via Tailscale/Headscale │
|
|
│ │ ┌─────────────┐ │
|
|
│ └─────────────▶│ WordPress │ (mu-plugin edubox-public-url.php) │
|
|
│ │ Docker/Podman │
|
|
└─────────────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Flux d'une requête publique
|
|
|
|
1. Le navigateur demande `https://<instance-id>.alfrednobel.edudeploy.com/`
|
|
2. Caddy termine TLS et transmet au resolver Go (`:443 → resolver:2020`)
|
|
3. Le resolver lit PostgreSQL pour trouver le nœud (Tailscale IP) et le port de l'instance
|
|
4. Le resolver fait du reverse proxy HTTP vers `http://<tailscale-ip>:<port>`
|
|
5. Le resolver ajoute les headers `X-Forwarded-Proto: https`, `X-Forwarded-Host`, `X-Forwarded-Port: 443`
|
|
6. L'agent Go sur le PC étudiant a lancé le conteneur WordPress sur `localhost:<port>` (bind `{PORT}:80`)
|
|
7. WordPress reçoit la requête en HTTP interne mais le **mu-plugin** `edubox-public-url.php` détecte `HTTP_HOST`/`X-Forwarded-Proto` et redéfinit `WP_HOME`/`WP_SITEURL` à la volée.
|
|
|
|
### Pourquoi WordPress fonctionne
|
|
|
|
- WordPress permet de redéfinir `WP_HOME` et `WP_SITEURL` via `wp-config.php` ou un mu-plugin.
|
|
- Le mu-plugin intercepte chaque requête et calcule l'URL publique depuis les headers proxy.
|
|
- WordPress accepte d'être servi depuis plusieurs domaines simultanément (localhost + sous-domaine public).
|
|
|
|
---
|
|
|
|
## 2. Pourquoi PrestaShop est différent (et plus difficile)
|
|
|
|
### 2.1 Le domaine est stocké en base de données
|
|
|
|
PrestaShop enregistre l'URL canonique à plusieurs endroits :
|
|
|
|
- `ps_configuration` :
|
|
- `PS_SHOP_DOMAIN`
|
|
- `PS_SHOP_DOMAIN_SSL`
|
|
- `PS_SSL_ENABLED`
|
|
- `ps_shop_url` :
|
|
- `domain`
|
|
- `domain_ssl`
|
|
|
|
Ces valeurs sont écrites **une seule fois lors de l'installation automatique** (`PS_INSTALL_AUTO=1`) via `index_cli.php --domain=<PS_DOMAIN>`.
|
|
|
|
### 2.2 Redirections canoniques strictes
|
|
|
|
Dans `classes/controller/FrontController.php` :
|
|
|
|
```php
|
|
$match_url = Tools::getCurrentUrlProtocolPrefix() . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
|
|
if (!preg_match('/^' . Tools::pRegexp(rawurldecode($canonical_url), '/') . '([&?].*)?$/', $match_url)) {
|
|
// ... redirect vers $canonical_url
|
|
}
|
|
```
|
|
|
|
- Si l'URL demandée ne correspond pas exactement à l'URL canonique stockée, PrestaShop envoie une 301/302.
|
|
- L'URL canonique est générée avec `Tools::getShopDomainSsl(true)` qui combine :
|
|
- `ShopUrl::getMainShopDomainSSL()` (domaine figé en base)
|
|
- `Configuration::get('PS_SSL_ENABLED')` (protocole figé en base)
|
|
|
|
### 2.3 Détection SSL
|
|
|
|
`Tools::usingSecureMode()` truste bien `HTTP_X_FORWARDED_PROTO: https` (testé et confirmé dans `classes/Tools.php`) :
|
|
|
|
```php
|
|
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
|
|
return Tools::strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https';
|
|
}
|
|
```
|
|
|
|
Donc le resolver transmet correctement le protocole. Le problème ne vient **pas** d'un défaut de PrestaShop sur la détection SSL en lui-même.
|
|
|
|
### 2.4 Le vrai problème : l'URL canonique est figée et ne correspond pas à l'URL demandée
|
|
|
|
Scénario avec `PS_ENABLE_SSL=1` et `PS_DOMAIN=<id>.alfrednobel.edudeploy.com` :
|
|
|
|
- URL demandée : `https://<id>.alfrednobel.edudeploy.com/`
|
|
- Requête interne : `http://<tailscale-ip>:<port>/` avec `X-Forwarded-Proto: https`
|
|
- PrestaShop détecte : secure mode = true
|
|
- URL canonique : `https://<id>.alfrednobel.edudeploy.com/`
|
|
- `match_url` : `https://<id>.alfrednobol.edudeploy.com/`
|
|
- Devrait correspondre… mais dans les tests, une boucle a quand même eu lieu.
|
|
|
|
Causes probables de la boucle :
|
|
|
|
1. **Port dans `HTTP_HOST`** : le resolver envoie `req.Host = host` (sans `:443`), mais Apache/PHP peut parfois enrichir `HTTP_HOST` avec le port interne (`<id>.domain:80`) selon la configuration.
|
|
2. **Cache navigateur** : une 301 est mise en cache, masquant le fix.
|
|
3. **Apache `.htaccess` généré par PrestaShop** : contient des règles de rewrite qui peuvent interagir avec le proxy (ex: rediriger `/` vers `index.php` avec un protocole différent).
|
|
4. **Cookie/SESSION** : la session PHP ou un cookie sécurisé peut forcer une reconnexion/redirection.
|
|
|
|
### 2.5 Accès `localhost:<port>` redirige vers le domaine public
|
|
|
|
C'est le comportement normal de PrestaShop quand `PS_DOMAIN` est le domaine public. Pour EduBox, ce n'est pas bloquant si l'accès étudiant se fait via l'URL publique ou via le Tailscale IP du professeur.
|
|
|
|
---
|
|
|
|
## 3. Pistes de solutions
|
|
|
|
### Solution A — Utiliser le mécanisme natif `PS_HANDLE_DYNAMIC_DOMAIN=1` (recommandée à tester en premier)
|
|
|
|
L'image Docker officielle `prestashop/prestashop` embarque un script `docker_updt_ps_domains.php` qui est copié à la racine et utilisé comme `DirectoryIndex` quand `PS_HANDLE_DYNAMIC_DOMAIN=1`.
|
|
|
|
Ce script fait :
|
|
|
|
```php
|
|
$domain = Tools::getHttpHost();
|
|
$url = ShopUrl::getShopUrls(Configuration::get('PS_SHOP_DEFAULT'))->where('main', '=', 1)->getFirst();
|
|
if ($url) {
|
|
$url->domain = $domain;
|
|
$url->domain_ssl = $domain;
|
|
$url->save();
|
|
Configuration::updateValue('PS_SHOP_DOMAIN', $domain);
|
|
Configuration::updateValue('PS_SHOP_DOMAIN_SSL', $domain);
|
|
Tools::generateHtaccess();
|
|
Tools::generateRobotsFile();
|
|
Tools::clearSmartyCache();
|
|
Media::clearCache();
|
|
}
|
|
Tools::redirect("index.php");
|
|
```
|
|
|
|
**Avantages**
|
|
- Mécanisme officiel, pas de module à maintenir.
|
|
- Met à jour le domaine dynamiquement à chaque requête sur `/`.
|
|
|
|
**Inconvénients**
|
|
- Exécution PHP + requêtes SQL + régénération `.htaccess` à chaque requête sur `/` → latence perceptible.
|
|
- Ne gère pas nativement le HTTPS vs HTTP (domain_ssl = domain, sans tenir compte de X-Forwarded-Proto).
|
|
- Nécessite d'être combiné avec une confiance des headers proxy.
|
|
|
|
**Implémentation proposée**
|
|
|
|
Dans le `composeConfig` du template PrestaShop :
|
|
|
|
```yaml
|
|
environment:
|
|
PS_DOMAIN: {PUBLIC_DOMAIN}
|
|
PS_ENABLE_SSL: "1"
|
|
PS_HANDLE_DYNAMIC_DOMAIN: "1"
|
|
PS_INSTALL_AUTO: "1"
|
|
PS_INSTALL_DB: "0"
|
|
```
|
|
|
|
Et monter un fichier Apache `proxy.conf` dans `/etc/apache2/conf-enabled/` :
|
|
|
|
```apache
|
|
SetEnvIf X-Forwarded-Proto https HTTPS=on
|
|
SetEnvIf X-Forwarded-Proto https SERVER_PORT=443
|
|
SetEnvIf X-Forwarded-Host ^(.+)$ HTTP_HOST=$1
|
|
```
|
|
|
|
Cela permet à `Tools::usingSecureMode()` de retourner `true` et à `Tools::getHttpHost()` de retourner le bon host public.
|
|
|
|
**Risques**
|
|
- Boucle possible si `PS_ENABLE_SSL=1` mais Apache ne reçoit pas `HTTPS=on` (d'où l'importance du `SetEnvIf`).
|
|
- Performance : le `docker_updt_ps_domains.php` est exécuté à chaque hit sur `/`.
|
|
|
|
---
|
|
|
|
### Solution B — Créer un module/override PrestaShop "EduBox Public URL" (équivalent du mu-plugin WordPress)
|
|
|
|
Créer un override `override/classes/Configuration.php` (ou un module hooké tôt) qui surcharge `Configuration::get()` pour les clés `PS_SHOP_DOMAIN`, `PS_SHOP_DOMAIN_SSL` et `PS_SSL_ENABLED`.
|
|
|
|
Exemple d'override :
|
|
|
|
```php
|
|
<?php
|
|
class Configuration extends ConfigurationCore {
|
|
public static function get($id_lang = null, $id_shop_group = null, $id_shop = null) {
|
|
$key = func_num_args() > 0 && is_string(func_get_arg(0)) ? func_get_arg(0) : $id_lang;
|
|
if ($key === 'PS_SHOP_DOMAIN' || $key === 'PS_SHOP_DOMAIN_SSL') {
|
|
return Tools::getHttpHost(false, false, true); // host sans port
|
|
}
|
|
if ($key === 'PS_SSL_ENABLED') {
|
|
return Tools::usingSecureMode() ? '1' : '0';
|
|
}
|
|
return call_user_func_array(['ConfigurationCore', 'get'], func_get_args());
|
|
}
|
|
}
|
|
```
|
|
|
|
**Avantages**
|
|
- Le plus proche du mu-plugin WordPress : aucune réécriture de réponses, pas de latence sur `/`.
|
|
- Une fois l'override chargé, toutes les URLs générées par PrestaShop utilisent le domaine de la requête courante.
|
|
|
|
**Inconvénients / pièges**
|
|
- L'override doit être pris en compte par l'autoloader. Sous PrestaShop 8, il faut vider `app/cache/prod/class_index.php`, `var/cache/*` et régénérer l'index avec `PrestaShop\Autoload\PrestashopAutoload::getInstance()->generateIndex()`.
|
|
- L'image Docker officielle embarque un `class_index.php` pré-généré qu'il faut invalider.
|
|
- Si l'installation se fait avec un override déjà présent, PrestaShop peut ne pas l'activer immédiatement.
|
|
|
|
**Implémentation proposée**
|
|
|
|
1. Créer `agent/psplugins/Configuration.php` (embarqué dans l'agent).
|
|
2. Au `start`/`reset` d'une instance PrestaShop, l'agent :
|
|
- copie l'override dans `/var/www/html/override/classes/Configuration.php`
|
|
- vide les caches (`rm -rf app/cache/* var/cache/*`)
|
|
- régénère l'autoloader (`php -r "require 'config/config.inc.php'; PrestaShop\Autoload\PrestashopAutoload::getInstance()->generateIndex();"`)
|
|
3. Ajouter le montage de l'override dans le `composeConfig` PrestaShop via un placeholder `{PS_OVERRIDES_DIR}`.
|
|
4. Garder la config Apache `SetEnvIf X-Forwarded-Proto https HTTPS=on` pour la détection SSL.
|
|
|
|
**Risques**
|
|
- Fragilité des versions : l'override dépend de la signature de `ConfigurationCore::get()` qui peut changer.
|
|
- Nécessite de bien gérer les caches à chaque redémarrage du conteneur.
|
|
|
|
---
|
|
|
|
### Solution C — Installation "localhost" + réécriture complète par le proxy
|
|
|
|
Installer PrestaShop avec `PS_DOMAIN=localhost:{PORT}` et `PS_ENABLE_SSL=0`.
|
|
Le conteneur vit en HTTP interne. Le resolver réécrit :
|
|
|
|
- Le header `Location` : `http://localhost:<port>/...` → `https://<id>.domain/...`
|
|
- Le body HTML/CSS/JS : toutes les occurrences de `http://localhost:<port>` et `//localhost:<port>`
|
|
|
|
C'est l'approche "WordPress-like".
|
|
|
|
**Avantages**
|
|
- Pas besoin de modifier PrestaShop.
|
|
- Le conteneur est totalement agnostique du domaine public.
|
|
|
|
**Inconvénients**
|
|
- PrestaShop génère énormément d'URLs absolues (assets, liens admin, webhooks, modules, API). La réécriture body n'est jamais exhaustive.
|
|
- Les requêtes AJAX/fetch peuvent pointer vers `localhost:<port>` et échouer côté client.
|
|
- Le back-office (`/admin`) génère des redirections complexes.
|
|
- Très fragile sur le long terme.
|
|
|
|
**Verdict** : **non recommandée** pour PrestaShop (contrairement à WordPress).
|
|
|
|
---
|
|
|
|
### Solution D — Image Docker PrestaShop personnalisée (patch durable)
|
|
|
|
Créer un `Dockerfile` dérivé de `prestashop/prestashop:8.1` qui :
|
|
|
|
1. Applique un patch à `classes/Configuration.php` ou `classes/Tools.php` pour supporter nativement `X-Forwarded-Host`/`X-Forwarded-Proto`.
|
|
2. Ou embarque directement l'override EduBox + un script d'init qui vide les caches.
|
|
3. Configure Apache pour trust les headers proxy.
|
|
|
|
**Avantages**
|
|
- Totalement reproductible : pas d'opération manuelle sur le conteneur en cours de vie.
|
|
- Peut être versionnée et testée indépendamment.
|
|
|
|
**Inconvénients**
|
|
- Nécessite de maintenir et publier une image Docker.
|
|
- Ajoute une étape de build CI/CD.
|
|
- Si PrestaShop sort une nouvelle version, il faut rebaser le patch.
|
|
|
|
**Implémentation possible**
|
|
|
|
```dockerfile
|
|
FROM prestashop/prestashop:8.1
|
|
COPY edubox-proxy.conf /etc/apache2/conf-enabled/
|
|
COPY edubox-override/ /var/www/html/override/
|
|
RUN chown -R www-data:www-data /var/www/html/override
|
|
```
|
|
|
|
---
|
|
|
|
### Solution E — Proxy "intelligent" avec substitution à la volée
|
|
|
|
Remplacer le resolver Go par (ou ajouter devant) un proxy qui fait de la substitution HTML/CSS/JS beaucoup plus fine, par exemple :
|
|
|
|
- Nginx avec `sub_filter`
|
|
- Apache `mod_substitute`
|
|
- Un middleware Node.js type `http-proxy-middleware` avec `selfHandleResponse`
|
|
|
|
**Avantages**
|
|
- Peut corriger les liens absolus que PrestaShop génère.
|
|
|
|
**Inconvénients**
|
|
- Même problème que la solution C : impossible d'être exhaustif.
|
|
- Ajoute de la latence et de la complexité.
|
|
- Peut casser le JS (si on remplace des chaînes dans du code minifié).
|
|
|
|
**Verdict** : complément possible, mais pas solution principale.
|
|
|
|
---
|
|
|
|
### Solution F — Désactiver les redirections canoniques et SSL forcé
|
|
|
|
Forcer PrestaShop à ne plus faire de redirections canoniques (`PS_CANONICAL_REDIRECT=0`) et à désactiver SSL partout (`PS_SSL_ENABLED=0`, `PS_SSL_ENABLED_EVERYWHERE=0`).
|
|
|
|
**Avantages**
|
|
- Élimine les boucles de redirection.
|
|
|
|
**Inconvénients**
|
|
- Nécessite un accès au back-office pour modifier les paramètres.
|
|
- Les liens générés restent en `http://` et pointent vers le domaine d'installation.
|
|
- Mauvaise expérience utilisateur (avertissements navigateur, mixed-content si certains assets passent en http).
|
|
|
|
**Verdict** : à éviter en production.
|
|
|
|
---
|
|
|
|
## 4. Recommandation
|
|
|
|
Je recommande une **combinaison des solutions A et B**, testée méthodiquement :
|
|
|
|
### Phase 1 — Solution A (test rapide)
|
|
|
|
1. Réintroduire un template PrestaShop 8.1 dans `server/prisma/seed.ts` avec :
|
|
- `PS_DOMAIN: {PUBLIC_DOMAIN}`
|
|
- `PS_ENABLE_SSL: "1"`
|
|
- `PS_HANDLE_DYNAMIC_DOMAIN: "1"`
|
|
- montage d'une config Apache `proxy.conf` pour trust `X-Forwarded-Proto`
|
|
2. Rebuilder le serveur, relancer le seed.
|
|
3. Créer une nouvelle instance PrestaShop (pas de reset d'ancienne instance).
|
|
4. Tester avec `curl -v -L --max-redirs 5 https://<id>.domain/` et en navigation privée.
|
|
|
|
### Phase 2 — Solution B (solution cible)
|
|
|
|
Si la solution A est trop lente ou instable, passer à l'override `Configuration.php` :
|
|
|
|
1. Créer `agent/psplugins/Configuration.php`.
|
|
2. Modifier l'agent pour copier l'override et vider/régénérer les caches au démarrage d'une instance PrestaShop.
|
|
3. Ajouter le montage `{PS_OVERRIDES_DIR}` dans le compose template.
|
|
4. Conserver la config Apache `SetEnvIf X-Forwarded-Proto`.
|
|
5. Tester avec `localhost:<port>` ET `https://<id>.domain/`.
|
|
|
|
### Phase 3 (optionnel) — Solution D
|
|
|
|
Si les solutions A/B sont trop fragiles d'une version de PrestaShop à l'autre, créer une image Docker dérivée patchée et la référencer dans le template.
|
|
|
|
---
|
|
|
|
## 5. Points d'attention pour l'implémentation
|
|
|
|
### Headers proxy
|
|
|
|
Le resolver Go transmet déjà les bons headers. Vérifier qu'aucun middleware Next.js ne les modifie (le middleware actuel ne fait que `NextResponse.next()`).
|
|
|
|
### Binding de port
|
|
|
|
Le commit `dd49993` a changé le binding de `127.0.0.1:{PORT}:80` à `{PORT}:80`. Cela signifie que le conteneur PrestaShop est accessible depuis n'importe quelle interface sur le PC étudiant. C'est nécessaire car le Tailscale proxy de l'agent écoute sur toutes les interfaces. **Il ne faut pas revenir à `127.0.0.1` sauf si on change la chaîne de proxy.**
|
|
|
|
### Cache navigateur
|
|
|
|
Toujours tester en navigation privée et avec `curl -v -L --max-redirs 5` pour éviter les 301 mises en cache.
|
|
|
|
### Vider les caches PrestaShop
|
|
|
|
À chaque changement de domaine/config, il faut vider :
|
|
|
|
```bash
|
|
rm -rf /var/www/html/app/cache/*
|
|
rm -rf /var/www/html/var/cache/*
|
|
php -r "require '/var/www/html/config/config.inc.php'; PrestaShop\Autoload\PrestashopAutoload::getInstance()->generateIndex();"
|
|
```
|
|
|
|
### Logs utiles
|
|
|
|
Sur le PC étudiant :
|
|
|
|
```bash
|
|
podman logs -f <id>-app-1
|
|
podman exec <id>-app-1 cat /var/log/apache2/error.log
|
|
```
|
|
|
|
Sur le serveur :
|
|
|
|
```bash
|
|
docker logs -f edubox-resolver
|
|
docker logs -f edubox-caddy
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Synthèse comparative
|
|
|
|
| Solution | Complexité | Robustesse | Perf. | Maintien | Recommandation |
|
|
|----------|-----------|------------|-------|----------|----------------|
|
|
| A — `PS_HANDLE_DYNAMIC_DOMAIN` | Faible | Moyenne | Moyenne (latence `/`) | Faible | **À tester en premier** |
|
|
| B — Override `Configuration` | Moyenne | Forte | Bonne | Moyen | **Solution cible** |
|
|
| C — localhost + rewrite proxy | Moyenne | Faible | Bonne | Faible | Non recommandée |
|
|
| D — Image Docker patchée | Forte | Très forte | Bonne | Fort | Option long terme |
|
|
| E — Proxy substitution | Moyenne | Faible | Moyenne | Faible | Complément seulement |
|
|
| F — Désactiver SSL/canonical | Faible | Faible | Bonne | Faible | À éviter |
|