feat(desktop): signature updater Tauri + wizard persistant + protocole E2E (#77)
CI / lint (push) Successful in 1m11s
CI / security (push) Successful in 43s
CI / test (push) Successful in 2m22s
CI / build (push) Successful in 1m41s
CI / e2e (push) Successful in 10m50s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s
CI / lint (push) Successful in 1m11s
CI / security (push) Successful in 43s
CI / test (push) Successful in 2m22s
CI / build (push) Successful in 1m41s
CI / e2e (push) Successful in 10m50s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s
- Signature des mises a jour Tauri : paire de cles minisign generee, cle publique dans tauri.conf.json, createUpdaterArtifacts actif, secrets CI exposes (repli build non signe si secret absent). - Wizard 1er lancement : etat persistant wizard_done cote Rust (get_wizard_state/complete_wizard) pour ne plus reafficher la banniere, retro-compatible. - Protocole des 6 tests E2E manuels : docs/DESKTOP_E2E_CHECKLIST.md. - CI : desktop.test.mjs ajoute au job lint. - Docs : ROADMAP, fiche desktop-tauri, CHANGELOG, README desktop, guide releases. - fix(build): retirer les libs WeasyPrint inutiles du stage builder Docker.
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
# ObsiGate Desktop — Protocole de tests E2E manuels
|
||||
|
||||
> **Rôle :** valider les 6 scénarios de bout en bout du desktop Tauri (#77) qui ne
|
||||
> peuvent pas être automatisés (interactions OS : installeur, tray, notifications,
|
||||
> association de fichiers, auto-update, désinstallation).
|
||||
> **Statut :** protocole documenté — à exécuter manuellement par un humain.
|
||||
> **Références :** [feature desktop-tauri](./features/desktop-tauri.md) ·
|
||||
> [Roadmap](./ROADMAP.md) · [Build & releases](./DEVELOPMENT_AND_RELEASES.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. Prérequis
|
||||
|
||||
- Un **build release** de l'application :
|
||||
- Windows : `desktop\build-windows.bat` → `ObsiGate_x.y.z_x64.msi` / `-setup.exe`
|
||||
- Linux : `desktop/build-linux.sh` → `obsigate_x.y.z_amd64.deb` / `.AppImage`
|
||||
- Une **machine vierge ou un compte utilisateur propre** (pas d'installation
|
||||
précédente) pour les tests d'installation/désinstallation.
|
||||
- Le **mot de passe admin** affiché au premier lancement (ou `OBSIGATE_ADMIN_PASSWORD`
|
||||
défini avant le lancement).
|
||||
- Accès en écriture aux logs : `%APPDATA%\ObsiGate\logs\backend.log` (Windows) ou
|
||||
`~/.config/obsigate/logs/backend.log` (Linux).
|
||||
|
||||
## 2. Comment remplir ce protocole
|
||||
|
||||
Pour chaque test : noter la **version testée** (`menu Aide → À propos`), le
|
||||
**commit** du build, la **date**, puis cocher `✅ OK` ou `❌ Échec` et joindre les
|
||||
logs/ captures en cas d'échec. Ne cocher un test qu'après avoir observé le
|
||||
**résultat attendu**.
|
||||
|
||||
| Test | Version | Commit | Date | Résultat |
|
||||
|---|---|---|---|---|
|
||||
| T1 — Installation | | | | ⬜ |
|
||||
| T2 — Tray icon | | | | ⬜ |
|
||||
| T3 — Notifications natives | | | | ⬜ |
|
||||
| T4 — Association `.md` | | | | ⬜ |
|
||||
| T5 — Auto-update | | | | ⬜ |
|
||||
| T6 — Désinstallation | | | | ⬜ |
|
||||
|
||||
---
|
||||
|
||||
## T1 — Installation → premier lancement → ouverture d'un fichier
|
||||
|
||||
**Objectif :** l'installeur installe l'app et le premier lancement démarre le
|
||||
backend + affiche l'interface.
|
||||
|
||||
1. Lancer l'installeur (`.msi` ou `.deb`/`.AppImage`).
|
||||
2. Installer dans le chemin par défaut → vérifier la création du raccourci
|
||||
(bureau / menu Démarrer / menu applications).
|
||||
3. Lancer ObsiGate depuis le raccourci.
|
||||
4. Observer l'écran de démarrage (« ObsiGate démarre… ») puis l'interface.
|
||||
5. Au premier lancement, cliquer **« Choisir mon dossier »** dans la bannière du
|
||||
wizard et sélectionner un dossier vault.
|
||||
6. Ouvrir un fichier `.md` depuis l'arborescence.
|
||||
|
||||
**Résultat attendu :**
|
||||
- Aucun terminal / console visible.
|
||||
- Le backend répond (`http://127.0.0.1:17890/api/health` → 200) en ~2 s.
|
||||
- Le fichier s'ouvre dans le viewer.
|
||||
- La bannière du wizard **ne réapparaît pas** au lancement suivant.
|
||||
|
||||
**Échec si :** écran blanc, backend non démarré, wizard qui revient à chaque
|
||||
lancement.
|
||||
|
||||
---
|
||||
|
||||
## T2 — Tray icon → réduire → restaurer
|
||||
|
||||
**Objectif :** le tray fonctionne et contrôle la fenêtre.
|
||||
|
||||
1. Vérifier la présence de l'icône ObsiGate dans la zone de notification.
|
||||
2. Clic **gauche** sur l'icône → la fenêtre se cache.
|
||||
3. Clic **gauche** à nouveau → la fenêtre réapparaît et prend le focus.
|
||||
4. Clic **droit** → menu (Ouvrir ObsiGate / À propos / Quitter).
|
||||
5. Fermer la fenêtre avec le **X** → elle se réduit dans le tray, le backend
|
||||
continue de tourner (l'icône reste).
|
||||
|
||||
**Résultat attendu :** toggle visible/caché immédiat, menu contextuel complet,
|
||||
fermeture par X = réduction (pas d'arrêt du backend).
|
||||
|
||||
---
|
||||
|
||||
## T3 — Notifications natives (fichier modifié → popup OS)
|
||||
|
||||
**Objectif :** les notifications natives remplacent le push web.
|
||||
|
||||
1. Activer les notifications dans les préférences (par vault si proposé).
|
||||
2. Modifier un fichier surveillé (édition externe dans le vault, ou édition
|
||||
in-app avec le watcher actif).
|
||||
3. Observer la notification du système d'exploitation.
|
||||
|
||||
**Résultat attendu :** popup OS affichant le nom du fichier/vault et l'action ;
|
||||
un clic ouvre le fichier concerné. La notification apparaît même si la fenêtre
|
||||
ObsiGate est réduite.
|
||||
|
||||
> Note : sur Windows, vérifier que les notifications ne sont pas bloquées dans
|
||||
> *Paramètres → Système → Notifications*.
|
||||
|
||||
---
|
||||
|
||||
## T4 — Association `.md` → double-clic → ouvre dans ObsiGate
|
||||
|
||||
**Objectif :** l'association de fichiers ouvre l'app.
|
||||
|
||||
1. Vérifier que `.md` est associé à « ObsiGate Markdown » (Windows :
|
||||
*Paramètres → Applications par défaut* ; Linux : `xdg-mime query default text/markdown`).
|
||||
2. **Fermer** complètement ObsiGate (tray → Quitter).
|
||||
3. Double-cliquer sur un fichier `.md` dans l'explorateur / gestionnaire de fichiers.
|
||||
4. Observer le lancement d'ObsiGate et l'ouverture du fichier.
|
||||
|
||||
**Résultat attendu :** ObsiGate démarre et ouvre le fichier (ou le vault
|
||||
contenant le fichier). Un second double-clic alors que l'app tourne **focus la
|
||||
fenêtre existante** (single-instance) au lieu de lancer un doublon.
|
||||
|
||||
---
|
||||
|
||||
## T5 — Auto-update → nouvelle version → installation
|
||||
|
||||
**Objectif :** l'updater détecte et installe une nouvelle version.
|
||||
|
||||
1. S'assurer qu'une **release plus récente** existe sur Gitea (avec les artefacts
|
||||
et le manifeste de mise à jour signé).
|
||||
2. Lancer la version N.
|
||||
3. Déclencher la vérification de mise à jour (menu ou au démarrage selon l'UI).
|
||||
4. Accepter la mise à jour → l'app télécharge, vérifie la signature et installe.
|
||||
5. Relancer → vérifier la version affichée (`À propos`).
|
||||
|
||||
**Résultat attendu :** détection de la version N+1, téléchargement, installation
|
||||
sans intervention manuelle, version mise à jour après redémarrage.
|
||||
|
||||
**Prérequis bloquant :** la `pubkey` de l'updater dans `desktop/tauri.conf.json`
|
||||
ne doit **pas** être le placeholder `OBSIGATE_UPDATE_PUBKEY_PLACEHOLDER`, et la
|
||||
clé privée correspondante doit être fournie au build
|
||||
(`TAURI_SIGNING_PRIVATE_KEY`). Voir §4.
|
||||
|
||||
---
|
||||
|
||||
## T6 — Désinstallation propre
|
||||
|
||||
**Objectif :** la désinstallation ne laisse aucun processus ni résidu gênant.
|
||||
|
||||
1. Fermer ObsiGate (tray → Quitter) pour éviter un processus orphelin.
|
||||
2. Désinstaller via le panneau de configuration (Windows) ou `dpkg -r obsigate`
|
||||
/ supprimer l'AppImage (Linux).
|
||||
3. Vérifier qu'aucun processus `obsigate-desktop` / Python backend ne tourne
|
||||
encore.
|
||||
4. Vérifier les résidus : raccourcis supprimés, entrée « Applications par
|
||||
défaut » retirée.
|
||||
5. (Optionnel) Vérifier le comportement des données utilisateur
|
||||
(`%APPDATA%\ObsiGate` / `~/.config/obsigate`) : conservées ou supprimées selon
|
||||
le choix documenté.
|
||||
|
||||
**Résultat attendu :** désinstallation sans erreur, aucun processus résiduel,
|
||||
aucun raccourci cassé.
|
||||
|
||||
---
|
||||
|
||||
## 3. Emplacements utiles
|
||||
|
||||
| Élément | Windows | Linux |
|
||||
|---|---|---|
|
||||
| Config | `%APPDATA%\ObsiGate\config.json` | `~/.config/obsigate/config.json` |
|
||||
| Logs backend | `%APPDATA%\ObsiGate\logs\backend.log` | `~/.config/obsigate/logs/backend.log` |
|
||||
| Données (index, comptes) | `%APPDATA%\ObsiGate\data\` | `~/.config/obsigate/data\` |
|
||||
|
||||
## 4. Signature de code & auto-update
|
||||
|
||||
- **Signature Windows (optionnelle, hors périmètre de ce protocole) :** sans
|
||||
certificat, SmartScreen affiche un avertissement au premier lancement
|
||||
(*Informations complémentaires → Exécuter quand même*). Le script
|
||||
`desktop/scripts/sign-windows.ps1` signe automatiquement si
|
||||
`OBSIGATE_SIGN_CERT_PFX` est défini ; sinon il est un no-op explicite.
|
||||
Alternatives détaillées dans le [README desktop](../desktop/README.md).
|
||||
- **Signature de l'updater Tauri (gratuite, distincte de la signature Windows) :**
|
||||
générer une paire de clés, renseigner la `pubkey` dans
|
||||
`desktop/tauri.conf.json` et exposer la clé privée au build via
|
||||
`TAURI_SIGNING_PRIVATE_KEY`. La clé privée **ne doit jamais être commitée**.
|
||||
|
||||
```bash
|
||||
cargo tauri signer generate -w ~/.tauri/obsigate.key
|
||||
# → copier la clé publique affichée dans plugins.updater.pubkey
|
||||
```
|
||||
|
||||
## 5. Clôture
|
||||
|
||||
Une fois les 6 tests exécutés et OK, reporter le résultat dans
|
||||
[`docs/features/desktop-tauri.md`](./features/desktop-tauri.md) (section F) et
|
||||
mettre à jour le statut du #77 dans la [Roadmap](./ROADMAP.md).
|
||||
@@ -57,6 +57,90 @@ cargo tauri dev
|
||||
|
||||
---
|
||||
|
||||
## 2bis. Signature des mises à jour (updater Tauri)
|
||||
|
||||
La signature de l'auto-update Tauri est **indépendante** de la signature de code
|
||||
Windows et **gratuite**. Elle garantit qu'une mise à jour téléchargée provient bien
|
||||
de vous. Elle repose sur une paire de clés `minisign` :
|
||||
|
||||
- La **clé publique** est embarquée dans `desktop/tauri.conf.json`
|
||||
(`plugins.updater.pubkey`).
|
||||
- La **clé privée** signe les artefacts au build. Elle ne doit **jamais** être
|
||||
commitée (ignorée par `.gitignore`).
|
||||
|
||||
### A. Générer la paire de clés (une seule fois)
|
||||
|
||||
```bash
|
||||
cd desktop
|
||||
cargo tauri signer generate -w obsigate-updater.key
|
||||
# La clé publique s'affiche et est écrite dans obsigate-updater.key.pub
|
||||
```
|
||||
|
||||
> ⚠️ Conservez la clé privée en lieu sûr (gestionnaire de secrets). Si vous la
|
||||
> perdez, les mises à jour ne pourront plus être signées. Pour la protéger par mot
|
||||
> de passe : ajoutez `-p "<mot de passe>"`.
|
||||
|
||||
Copiez le contenu de `obsigate-updater.key.pub` dans
|
||||
`desktop/tauri.conf.json` → `plugins.updater.pubkey`.
|
||||
|
||||
### B. Build local signé
|
||||
|
||||
```powershell
|
||||
# Windows PowerShell
|
||||
$env:TAURI_SIGNING_PRIVATE_KEY = Get-Content -Raw .\obsigate-updater.key
|
||||
# $env:TAURI_SIGNING_PRIVATE_KEY_PASSWORD = "<mot de passe>" # si la clé en a un
|
||||
cargo tauri build --bundles nsis,msi
|
||||
```
|
||||
|
||||
```bash
|
||||
# Linux / Bash
|
||||
export TAURI_SIGNING_PRIVATE_KEY="$(cat obsigate-updater.key)"
|
||||
# export TAURI_SIGNING_PRIVATE_KEY_PASSWORD="<mot de passe>"
|
||||
cargo tauri build --bundles appimage,deb
|
||||
```
|
||||
|
||||
Le CLI produit des fichiers `.sig` à côté de chaque artefact
|
||||
(`*.exe.sig`, `*.msi.sig`, `*.AppImage.sig`, `*.deb.sig`).
|
||||
|
||||
> Sans clé définie, `createUpdaterArtifacts` est actif et le build échoue : dans
|
||||
> le CI, l'étape désactive automatiquement les artefacts de mise à jour si le
|
||||
> secret est absent.
|
||||
|
||||
### C. Secrets CI (Gitea)
|
||||
|
||||
Dans **Dépôt → Paramètres → Actions → Secrets**, créez :
|
||||
|
||||
| Secret | Valeur |
|
||||
|---|---|
|
||||
| `TAURI_SIGNING_PRIVATE_KEY` | contenu **intégral** du fichier `.key` |
|
||||
| `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` | mot de passe de la clé (vide si aucun) |
|
||||
|
||||
Le workflow `.gitea/workflows/desktop-build.yml` les expose aux étapes de build ;
|
||||
les fichiers `.sig` sont uploadés comme artefacts.
|
||||
|
||||
### D. Manifeste de mise à jour (`latest.json`)
|
||||
|
||||
Le CLI Tauri génère les `.sig` mais **pas** le manifeste JSON consommé par
|
||||
l'updater. L'endpoint configuré
|
||||
(`.../releases/latest`) doit servir un document de la forme :
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "2.3.0",
|
||||
"notes": "…",
|
||||
"pub_date": "2026-09-12T00:00:00Z",
|
||||
"platforms": {
|
||||
"windows-x86_64": { "signature": "<contenu .msi.sig>", "url": "<URL du .msi>" },
|
||||
"linux-x86_64": { "signature": "<contenu .AppImage.sig>", "url": "<URL .AppImage>" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Sans ce manifeste, l'updater ne détecte aucune mise à jour (la signature, elle,
|
||||
est déjà opérationnelle).
|
||||
|
||||
---
|
||||
|
||||
## 3. Commit, Push & Tagging Git
|
||||
|
||||
Une fois les modifications testées et validées :
|
||||
|
||||
+6
-6
@@ -33,12 +33,12 @@
|
||||
### 77. Application Desktop native — Tauri (Windows / Linux / macOS)
|
||||
|
||||
- **Effort :** 8-12 jours | **Impact :** 🟡 | **Framework :** Tauri v2 (Rust + Webview)
|
||||
- **Statut :** livré (A→F) — projet Tauri, backend Python embarqué, fonctionnalités natives, build CI, UX, 16 tests Rust. Détail complet : [features/desktop-tauri.md](./features/desktop-tauri.md)
|
||||
- **Statut :** livré (A→F) — projet Tauri, backend Python embarqué, fonctionnalités natives, jumplist vaults, bannière 1er lancement, build CI, UX, 24 tests Rust. Détail complet : [features/desktop-tauri.md](./features/desktop-tauri.md)
|
||||
- **Reste à faire :**
|
||||
- [ ] Signature de code Windows (optionnel mais recommandé)
|
||||
- [ ] Wizard « Choisissez votre vault » au 1er lancement (optionnel)
|
||||
- [ ] Jumplist vaults récents dans le menu Démarrer (optionnel)
|
||||
- [ ] 6 tests E2E **manuels** : installation, tray icon, notifications natives, association `.md`, auto-update, désinstallation
|
||||
- [x] **Signature de l'updater Tauri** (gratuit) : paire de clés générée, `pubkey` renseignée, `createUpdaterArtifacts` activé, secrets CI câblés
|
||||
- [ ] Générer le manifeste `latest.json` pour que l'updater détecte les mises à jour
|
||||
- [ ] **Signature de code Windows** : non retenue (pas de certificat) — alternatives : livrer non signé, SignPath.io (OSS gratuit), Certum OSS, Azure Trusted Signing, certificat EV
|
||||
- [ ] Exécuter les 6 tests E2E **manuels** — protocole documenté : [DESKTOP_E2E_CHECKLIST.md](./DESKTOP_E2E_CHECKLIST.md)
|
||||
|
||||
---
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
| Priorité | Items | Effort total estimé |
|
||||
|---|---|---|
|
||||
| ✅ Complété | #1 → #59, #61–72, #74–76, #78–80 | ~103 jours réalisés |
|
||||
| 🔵 P2 restant | #77 Desktop : signature code (optionnel), wizard 1er lancement (optionnel), 6 tests E2E **manuels** | ~1-2 jours |
|
||||
| 🔵 P2 restant | #77 Desktop : manifeste `latest.json`, signature de code (non retenue), 6 tests E2E **manuels** ([protocole](./DESKTOP_E2E_CHECKLIST.md)) | ~0,5-1 jour |
|
||||
| ⚪ P4 restant | #73 Sync (6-8j) | 6-8 jours |
|
||||
| **Total restant** | **2 items + finitions** | **~7-10 jours** |
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# #77 — Application Desktop native — Tauri (Windows / Linux / macOS)
|
||||
|
||||
> **Statut :** 🔵 En cours — livré : A/B/C/D/E/F ; reste signature de code (optionnel), wizard 1er lancement (optionnel), 6 tests E2E manuels
|
||||
> **Statut :** 🔵 En cours — livré : A/B/C/D/E/F ; reste la signature de code Windows (optionnelle, non retenue) et l'exécution des 6 tests E2E **manuels** ([protocole](../DESKTOP_E2E_CHECKLIST.md))
|
||||
> **Effort :** 8-12 jours | **Impact :** 🟡 | **Framework :** Tauri v2 (Rust + Webview)
|
||||
> **Références :** [Roadmap](../ROADMAP.md) · [Guide de build & releases](../DEVELOPMENT_AND_RELEASES.md)
|
||||
|
||||
@@ -57,7 +57,8 @@
|
||||
- [x] **Build Windows local** : `cargo build --release` vérifié (rustc 1.94.1) — `cargo tauri build --bundles msi` prêt
|
||||
- [x] **Build Linux local** : workflow CI couvre `.deb`, `.rpm`, `.AppImage`
|
||||
- [x] **Auto-update** : `tauri-plugin-updater` configuré → vérifie `https://git.dracodev.net/api/v1/repos/Projets/ObsiGate/releases/latest`
|
||||
- [ ] **Signature de code** : configurer le certificat (optionnel mais recommandé pour Windows)
|
||||
- [x] **Signature de l'updater Tauri** (gratuite, ≠ signature Windows) : paire de clés `minisign` générée, clé publique dans `plugins.updater.pubkey`, `bundle.createUpdaterArtifacts: true`, secrets Gitea `TAURI_SIGNING_PRIVATE_KEY` / `_PASSWORD` exposés au CI (build non signé en repli si le secret est absent). Reste : générer le manifeste `latest.json` pour que l'updater détecte les mises à jour ([guide](../DEVELOPMENT_AND_RELEASES.md#2bis-signature-des-mises-à-jour-updater-tauri))
|
||||
- [ ] **Signature de code Windows** : non retenue (pas de certificat) — alternatives : livrer non signé, SignPath.io (OSS gratuit), Certum Open Source, Azure Trusted Signing, certificat EV
|
||||
- [x] **Page de release** : README desktop existe (`desktop/README.md`)
|
||||
|
||||
## E. Expérience utilisateur (1 jour) — ✅ COMPLÉTÉ
|
||||
@@ -65,19 +66,18 @@
|
||||
- [x] Gestion des erreurs : backend crash → `showBackendCrashBanner()` appelé par le monitor loop toutes les 5s
|
||||
- [x] Sauvegarde des préférences desktop : position/taille fenêtre sauvées dans `%APPDATA%/ObsiGate/config.json` au close + restauration au startup
|
||||
- [x] Première expérience : config par défaut auto-créée au premier lancement (vault `~/voute_obsidian`, dir `~USERPROFILE`)
|
||||
- [ ] Wizard interactif « Choisissez votre vault » au premier lancement (optionnel — config auto suffisante)
|
||||
- [ ] Jumplist vaults récents dans le menu Démarrer (optionnel)
|
||||
- [x] Bannière de premier lancement « Choisissez votre vault » (`frontend/js/desktop.js`) — non bloquante ; l'état est persisté côté Rust (`wizard_done` dans `config.json`) pour ne pas réapparaître après un choix ou un clic « Plus tard », même si le `localStorage` de la webview est vidé
|
||||
- [x] Jumplist vaults récents dans le menu Démarrer (`desktop/src/jumplist.rs`, Windows)
|
||||
|
||||
## F. Tests (1 jour) — ✅ COMPLÉTÉ
|
||||
- [x] 16 tests Rust unitaires : config roundtrip, JSON parsing (empty/partial/corrupted), vault dedup, dir remove, backend URL, paths, branding, edge cases
|
||||
- [x] 24 tests Rust unitaires : config roundtrip, JSON parsing (empty/partial/corrupted/legacy), `wizard_done`, vault dedup, dir remove, backend URL, paths, branding, jumplist args, edge cases
|
||||
- [x] Build debug + release vérifié (rustc 1.94.1, tauri-cli 2.11.4)
|
||||
- [x] CI desktop workflow existant (desktop-build.yml)
|
||||
- [ ] Test E2E : installation → premier lancement → wizard vault → ouverture fichier (manuel)
|
||||
- [ ] Test E2E : tray icon → réduire → restaurer (manuel)
|
||||
- [ ] Test E2E : notifications natives → fichier modifié → popup OS (manuel)
|
||||
- [ ] Test E2E : association `.md` → double-clic → ouvre dans ObsiGate (manuel)
|
||||
- [ ] Test E2E : auto-update → nouvelle version → install (manuel)
|
||||
- [ ] Test E2E : désinstallation propre (manuel)
|
||||
- [ ] **6 tests E2E manuels** — protocole détaillé (prérequis, étapes, résultat
|
||||
attendu) dans [docs/DESKTOP_E2E_CHECKLIST.md](../DESKTOP_E2E_CHECKLIST.md) :
|
||||
installation → 1er lancement → wizard → ouverture fichier ; tray ; notifications
|
||||
natives ; association `.md` ; auto-update ; désinstallation propre. À exécuter
|
||||
et cocher par un humain sur un build release.
|
||||
|
||||
- **Prérequis techniques :**
|
||||
- Rust ≥ 1.75 (stable) — installé via `rustup`
|
||||
|
||||
Reference in New Issue
Block a user