# Publication des mises à jour GCMS (GitLab + manifeste signé)

Ce document décrit **ta** configuration côté GdevStudio : clés, GitLab CI, hébergement des fichiers, puis ce que le client met dans son `.env`.

Le flux côté client (admin GCMS, bouton « Mettre à jour ») est décrit dans l’interface **Réglages → Mise à jour GCMS**.

---

## 1. Principe

1. Tu pousses un **tag** Git `v1.2.3` sur le dépôt GCMS.
2. GitLab CI exécute [`.gitlab-ci.yml`](../.gitlab-ci.yml) : construction du ZIP (allowlist), calcul **SHA256**, génération de **`release.json`** avec **signature RSA** (SHA256).
3. Tu déposes **`gcms-1.2.3.zip`** et **`release.json`** sur un **HTTPS** public, à l’URL définie par `GCMS_RELEASE_BASE_URL` (voir §4).
4. Chaque instance GCMS cliente interroge l’URL du manifeste (`GCMS_UPDATE_MANIFEST_URL`), vérifie la signature avec la **clé publique**, puis peut télécharger le ZIP si l’admin clique sur « Mettre à jour ».

---

## 2. Générer la paire de clés RSA (une fois)

Sur ta machine (ou en local sécurisé) :

```bash
openssl genrsa -out gcms-release-private.pem 3072
openssl rsa -in gcms-release-private.pem -pubout -out gcms-update-public.pem
```

- **`gcms-release-private.pem`** : **secret** — ne jamais commit. Tu la stockeras dans GitLab (§3).
- **`gcms-update-public.pem`** : **publique** — tu la donnes aux installations GCMS (fichier à la racine du projet **ou** chemin `GCMS_UPDATE_PUBLIC_KEY_PATH`, voir `.env.example`).

Pour embarquer la clé publique dans le dépôt sans la nommer `gcms-update-public.pem` (ignoré par `.gitignore`), tu peux copier le contenu PEM dans la doc interne ou utiliser un autre nom versionné, par ex. `docs/gcms-update-public.pem.example`, puis les clients renomment/copient.

---

## 3. Configurer GitLab (pas à pas)

### 3.1 Ouvrir les variables CI/CD

1. Ouvre ton projet **GitLab** contenant le code GCMS.
2. Menu **Settings** → **CI/CD**.
3. Section **Variables** → **Expand** → **Add variable**.

### 3.2 Variable `GCMS_RELEASE_PRIVATE_KEY_B64`

1. Encode la clé privée en base64 (une seule ligne) :

   ```bash
   base64 -i gcms-release-private.pem | tr -d '\n'
   ```

   (Sur Linux GNU : `base64 -w0 gcms-release-private.pem`.)

2. Dans GitLab : **Key** = `GCMS_RELEASE_PRIVATE_KEY_B64`, **Value** = le résultat de la commande.
3. Coche **Mask variable** (et **Protect variable** si tu utilises des tags protégés uniquement).

### 3.3 Variable `GCMS_RELEASE_BASE_URL`

C’est l’URL **HTTPS** du **dossier** où seront servis les fichiers, **sans slash final**.

Exemples :

- `https://cdn.gdevstudio.fr/gcms`
- `https://gdevstudio.fr/downloads/gcms`

Le pipeline supposera que le ZIP sera accessible à :

`{GCMS_RELEASE_BASE_URL}/gcms-{VERSION}.zip`

(ex. `https://cdn.gdevstudio.fr/gcms/gcms-1.2.3.zip`)

Et tu devras y placer aussi **`release.json`** (souvent sous le nom `release.json` à la même base, ex. `https://cdn.gdevstudio.fr/gcms/release.json`).

**Important :** l’URL dans le manifeste (`package_url`) doit **correspondre exactement** à l’endroit où tu uploades le ZIP après le build.

1. **Key** = `GCMS_RELEASE_BASE_URL`
2. **Value** = ton URL de dossier (sans `/` final)

### 3.4 Activer les pipelines sur tags

- Vérifie que les **tags** ne sont pas exclus des pipelines (paramètres du projet / règles CI).
- Le fichier [`.gitlab-ci.yml`](../.gitlab-ci.yml) ne lance le job que si le tag correspond à `vX.Y.Z` (ex. `v1.0.0`).

### 3.5 Première release

1. Mets à jour la version dans [`config/cms.php`](../config/cms.php) (`version`) pour refléter la release (référence humaine ; côté client, après MAJ, c’est surtout `storage/app/gcms-version.lock` qui compte).
2. Commit + push sur la branche principale.
3. Crée un tag annoté ou léger :

   ```bash
   git tag v1.0.1
   git push origin v1.0.1
   ```

4. Dans GitLab : **Build → Pipelines** (ou **Jobs**), ouvre le job **`release_gcms`**.
5. Télécharge les **artifacts** dans `build/` :
   - **`gcms-VERSION.zip`** — paquet **mise à jour** (allowlist, sans `vendor`, celui référencé par `release.json`) ;
   - **`gcms-full-VERSION.zip`** — **installation / téléchargement** (`vendor/` + `public/build` après `composer install --no-dev` et `npm run build`) ;
   - **`release.json`** — manifeste signé.

### 3.6 Mettre les fichiers en ligne

**Option A — Déploiement automatique depuis GitLab (recommandé si tu as `update.gdevstudio.fr` sur un VPS)**

1. Sur le serveur Debian, crée un utilisateur dédié (ex. `deploy`) **ou** utilise `root` (moins idéal mais simple).
2. Installe la **clé publique SSH** correspondant à une clé privée que tu garderas **uniquement** dans GitLab :

   ```bash
   # Sur le serveur, en tant qu’utilisateur qui recevra les fichiers
   mkdir -p ~/.ssh && chmod 700 ~/.ssh
   nano ~/.ssh/authorized_keys   # colle la clé publique (une ligne)
   chmod 600 ~/.ssh/authorized_keys
   ```

3. Cet utilisateur doit pouvoir **écrire** dans le dossier (ex. `/var/www/gdevstudio-static/gcms`) :

   ```bash
   sudo chown deploy:deploy /var/www/gdevstudio-static/gcms
   # ou : sudo usermod -aG www-data deploy && sudo chgrp www-data ... && chmod 775 ...
   ```

4. Dans **GitLab → Settings → CI/CD → Variables**, ajoute :

   | Variable | Exemple | Notes |
   |----------|---------|--------|
   | `GCMS_DEPLOY_AUTO` | `true` | **Sans** cette variable (ou autre valeur), le job de déploiement **ne s’exécute pas**. |
   | `GCMS_DEPLOY_HOST` | `IP_OU_hostname` | IP publique du serveur ou `update.gdevstudio.fr` si ça résout bien vers ce serveur. |
   | `GCMS_DEPLOY_USER` | `deploy` | Utilisateur SSH. |
   | `GCMS_DEPLOY_PATH` | `/var/www/gdevstudio-static/gcms` | Optionnel si tu gardes ce chemin par défaut. |
   | `GCMS_DEPLOY_SSH_KEY_B64` | *(base64)* | Clé **privée** SSH (tout le fichier `id_ed25519` ou `id_rsa`), encodée : `base64 -w0 ~/.ssh/id_ed25519 \| tr -d '\n'` — variable **masked**. |

5. À chaque tag `vX.Y.Z`, après le job **`release_gcms`**, le job **`deploy_update_gcms`** copie **`release.json`**, **`gcms-X.Y.Z.zip`**, **`gcms-full-X.Y.Z.zip`** et une page **`index.html`** minimale (fichier source [`scripts/ci/gcms-update-root-index.html`](../scripts/ci/gcms-update-root-index.html)) vers `${GCMS_DEPLOY_PATH}/` via **SCP**. L’URL publique du ZIP complet est du type `{GCMS_RELEASE_BASE_URL}/gcms-full-X.Y.Z.zip` (bouton « Télécharger le CMS » sur ton site vitrine). Sans `index.html`, Nginx renvoie souvent **403** sur `/` alors que **`/release.json`** reste en **200**.

6. Si Nginx lit les fichiers en tant que `www-data`, assure-toi que les fichiers déployés sont lisibles (ex. `chmod 644`, groupe `www-data`, ou `chown` après déploiement via un script cron sur le serveur).

**Option B — Manuel**

1. Télécharge les **artifacts** du job `release_gcms` depuis GitLab.
2. Upload **`release.json`**, **`gcms-1.0.1.zip`** (MAJ) et **`gcms-full-1.0.1.zip`** (installation) vers l’URL prévue (FTP, `scp`, etc.).

---

## 4. Côté client (chaque site GCMS)

**Modèle d’environnement production :** copier [`.env.example`](../.env.example) vers `.env` sur le serveur (`APP_ENV=production`, `APP_DEBUG=false`, MySQL, sessions chiffrées, `GCMS_UPDATE_ALLOW_UNSIGNED=false`).

Variables mises à jour GCMS typiques :

```env
GCMS_UPDATE_ENABLED=true
GCMS_UPDATE_MANIFEST_URL=https://update.gdevstudio.fr/release.json
GCMS_UPDATE_ALLOW_UNSIGNED=false
```

**Clé publique RSA** (même paire que les releases signées) — au choix :

- **`docs/gcms-update-public.pem`** (recommandé : tu peux la commiter dans ton dépôt « distribution » ou la livrer avec le paquet),
- ou **`gcms-update-public.pem`** à la racine du projet (fichier souvent ignoré par Git),
- ou `GCMS_UPDATE_PUBLIC_KEY_PATH` / `GCMS_UPDATE_PUBLIC_KEY` (PEM sur une ligne avec `\n`).

Puis sur le serveur :

```bash
php artisan key:generate
php artisan migrate --force
php artisan config:cache
```

**Vérification planifiée :** `gcms:check-update` tous les jours à **03:15** (voir [`bootstrap/app.php`](../bootstrap/app.php)). Cron serveur :

```cron
* * * * * cd /chemin/vers/gcms && php artisan schedule:run >> /dev/null 2>&1
```

### 4.1 Checklist production rapide

| Élément | Détail |
|---------|--------|
| `.env` | Jamais `APP_DEBUG=true` ni sqlite en prod ; `APP_URL` en HTTPS. |
| Mises à jour | `GCMS_UPDATE_ALLOW_UNSIGNED=false` + fichier PEM présent. |
| GitLab (dépôt instance) | Variables masquées : `APP_KEY`, mots de passe DB, clés API ; pas de secrets dans le code. |
| Après déploiement | `php artisan config:cache`, `php artisan route:cache`, `php artisan view:cache` (optionnel mais courant). |

### 4.2 Déployer une instance via GitLab

Exemple de pipeline **rsync** vers ton VPS (dépôt séparé « site client ») : [`docs/gitlab-ci-instance.example.yml`](gitlab-ci-instance.example.yml). Variables : `INSTANCE_DEPLOY_HOST`, `INSTANCE_DEPLOY_USER`, `INSTANCE_DEPLOY_SSH_KEY_B64`, `INSTANCE_DEPLOY_PATH`. Adapter les `rsync --exclude` (le modèle exclut `.env`, `content/`, `vendor/`, etc.).

### Développement local

Avec **`APP_ENV=local`**, si **`GCMS_UPDATE_ALLOW_UNSIGNED` est absent ou vide**, le CMS accepte un manifeste **sans** fichier PEM (confort dev uniquement). Pour forcer la signature en local : `GCMS_UPDATE_ALLOW_UNSIGNED=false` + PEM. Ne pas répliquer ce mode sur les instances clientes.

---

## 5. Contenu des ZIP

### 5.1 `gcms-VERSION.zip` (mise à jour in-app)

Aligné sur [`config/gcms-update.php`](../config/gcms-update.php) : `app`, `bootstrap`, `config`, `database`, `plugins`, `resources`, `routes`, `themes/classic`, `public` (hors `public/storage`), `artisan`, `composer.json`, `composer.lock`.

**Non inclus :** `vendor`, `content`, `.env`, `storage`, `node_modules`. Si une release modifie les dépendances Composer, mets `requires_composer: true` dans le manifeste (généré avec `REQUIRES_COMPOSER=1` avant `build-release-json.sh`) : le bouton « Mettre à jour » automatique sera **refusé** ; il faudra une procédure manuelle (`composer install`).

### 5.2 `gcms-full-VERSION.zip` (installation / téléchargement)

Produit par [`scripts/build-gcms-full-release.sh`](../scripts/build-gcms-full-release.sh) après **`composer install --no-dev`** et **`npm run build`** dans le job CI : inclut **`vendor/`**, **`public/build/`**, le reste du projet utile en production. **Exclut** entre autres `.git`, `node_modules`, `.env*`, caches `storage/framework`, `tests`, le dossier `build/` du pipeline. Pense à **`.env`**, droits **`storage/`**, **`php artisan key:generate`** / migrations après extraction sur un nouvel hébergement.

**Marketplace / licence (distribution)** : ajouter dans le ZIP **`bootstrap/gcms-infrastructure.key`** (une ligne, identique à `LICENSE_API_KEY` du serveur de licence). Ce fichier n’est pas versionné ; injectez-le dans le job CI (variable GitLab protégée + étape `echo "$GCMS_INFRASTRUCTURE_KEY" > bootstrap/gcms-infrastructure.key`). Voir [`bootstrap/gcms-infrastructure.key.example`](../bootstrap/gcms-infrastructure.key.example).

Pour une release « composer requis », adapte le script CI ou édite `build/release.json` avant upload.

---

## 6. Format du manifeste et signature

Champs : `latest_version`, `package_url`, `sha256` (hex 64), `min_php`, `requires_composer` (booléen), `issued_at`, `signature` (base64).

**Payload signé** (lignes séparées par `\n`, sans guillemets) :

```text
{latest_version}
{package_url}
{sha256}
{min_php}
{0 ou 1 pour requires_composer}
```

Signature : `openssl dgst -sha256 -sign private.pem` sur ce payload, encodé en base64 (voir [`scripts/ci/build-release-json.sh`](../scripts/ci/build-release-json.sh)).

---

## 7. Dépannage

| Problème | Piste |
|----------|--------|
| Signature invalide | Même paire de clés ? Payload identique (ordre des lignes, pas d’espace en trop) ? |
| SHA256 incorrect | ZIP sur le CDN **strictement identique** à celui du job CI (pas de re-compression). |
| 404 sur le manifeste | URL `GCMS_UPDATE_MANIFEST_URL` exacte, HTTPS valide. |
| Mise à jour échoue en copie | Droits d’écriture sur les dossiers du CMS, espace disque, `max_execution_time`. |

---

## 8. Test local du script de build (sans GitLab)

```bash
cd /chemin/vers/gcms
export VERSION=1.0.0
bash scripts/build-gcms-release.sh
export GCMS_RELEASE_BASE_URL=https://example.com/gcms
export GCMS_RELEASE_PRIVATE_KEY_FILE=/chemin/vers/gcms-release-private.pem
bash scripts/ci/build-release-json.sh
cat build/release.json
```

Tu dois voir le JSON et pouvoir vérifier la signature avec un petit script PHP ou `openssl dgst -sha256 -verify gcms-update-public.pem -signature ...`.
