# Bucket & Code Server IDE : guide de l’utilisateur

Bucket & Code Server IDE (QPKG `bucketvs`) conserve du code source et des fichiers sur un NAS QNAP et y compile des
logiciels. Il apporte son propre serveur web, ses propres outils et ses propres comptes.

Ce guide décrit le paquet tel qu’il fonctionne sur le NAS.

## Sommaire

- [Ce qui tourne sur le NAS](#ce-qui-tourne-sur-le-nas)
- [Installer le paquet](#installer-le-paquet)
- [Première connexion](#première-connexion)
- [Travailler dans le panneau](#travailler-dans-le-panneau)
- [Comptes](#comptes)
- [Le dossier de données](#le-dossier-de-données)
- [Réseau](#réseau)
- [Derrière un proxy inverse (Plesk)](#derrière-un-proxy-inverse-plesk)
- [Noms d’hôte des modules](#noms-dhôte-des-modules)
- [Publier sur Internet (Cloudflare Tunnel)](#publier-sur-internet-cloudflare-tunnel)
- [Modules](#modules)
- [Git](#git)
- [Runners CI](#runners-ci)
- [SVN et Bucket](#svn-et-bucket)
- [Éditeur et outils d’IA](#éditeur-et-outils-dia)
- [Toolchains et environnements](#toolchains-et-environnements)
- [App Builder](#app-builder)
- [Dépôt de paquets](#dépôt-de-paquets)
- [Atelier du noyau](#atelier-du-noyau)
- [Tâches cron et opérations](#tâches-cron-et-opérations)
- [Sauvegardes](#sauvegardes)
- [Notifications](#notifications)
- [Journaux et paquet de diagnostic](#journaux-et-paquet-de-diagnostic)
- [Mises à jour](#mises-à-jour)
- [La commande bvs](#la-commande-bvs)
- [Fichiers et dossiers](#fichiers-et-dossiers)

## Ce qui tourne sur le NAS

| Élément | Ce qu’il fait | Où il écoute |
|---|---|---|
| nginx | la seule porte sur le réseau local : sert le panneau et transmet chaque chemin à son module | 9950 (HTTP), 9951 (HTTPS) |
| bvsd | démarre et surveille tous les autres processus, garde les comptes et les réglages, exécute les tâches et les tâches cron, répond au panneau | deux sockets Unix dans `/usr/bin/bucketvs/var/run/` |
| Git (Forgejo) | hébergement Git avec une interface web | un socket Unix dans `/usr/bin/bucketvs/var/run/forgejo/`, et 9952 pour Git via SSH |
| SVN et Bucket (httpd) | dépôts Subversion et dossiers WebDAV avec historique | 127.0.0.1:9961, ou le port libre suivant quand un autre programme l’occupe |
| Éditeur (code-server) | VS Code dans le navigateur, une instance par compte | un socket Unix par compte dans `/usr/bin/bucketvs/var/run/ide/` |
| CI | le runner Forgejo Actions | pas de port à lui ; nginx écoute pour lui sur 127.0.0.1:9960 tant que le module est activé, ou sur le port libre suivant quand un autre programme occupe 9960 |

Le cœur (nginx, bvsd, le panneau, les tâches cron, les toolchains) est toujours actif. Git, CI, SVN et Bucket, et
l’Éditeur sont des modules qu’un admin active et désactive. Sur chaque adresse du NAS, les modules répondent sous leurs
chemins : `/git/`, `/svn/`, `/bucket/`, `/ide/`. Le panneau est à `/`, et ce guide à `/guide/`.

Tant que le paquet tourne, ses programmes sont dans `/usr/bin/bucketvs/bin`. Tout ce que le paquet conserve (comptes,
réglages, dépôts, environnements, journaux) se trouve dans un seul dossier sur un volume de données : le [dossier de
données](#le-dossier-de-données).

## Installer le paquet

Le paquet demande QTS 6.0 ou QuTS hero 6.0 ou plus récent, sur un NAS x86_64 ou ARM 64 bits ; il existe un paquet par
architecture (`bucketvs_<version>_x86_64.qpkg`, `bucketvs_<version>_arm_64.qpkg`). Il est signé : installez-le depuis
l’App Center (un dépôt qui le propose, ou **Installer manuellement** avec le fichier téléchargé). L’App Center vérifie
la signature ; il n’est pas nécessaire d’autoriser les applications sans signature valide.

L’installation crée l’utilisateur de service `bucketvs` (pour Forgejo et httpd), le lien `/usr/bin/bucketvs` vers le
dossier d’installation, et démarre bvsd. Les journaux système indiquent le dossier de données.

### Où commence le dossier de données

Au premier démarrage, le dossier de données est `BucketVS` sur le volume où le paquet est installé, par exemple
`/share/CACHEDEV1_DATA/BucketVS` (QTS) ou `/share/ZFS2_DATA/BucketVS` (QuTS hero). Elle peut ensuite être déplacée sur
un autre volume depuis le panneau ([Déplacer le dossier de données](#déplacer-le-dossier-de-données)).

Le paquet retient son dossier de données dans `/etc/config/bucketvs-data-root`, un fichier qui survit au paquet.
Supprimer le paquet laisse en place le dossier de données et ce fichier, si bien qu’une installation après une
suppression repart sur les mêmes données et les mêmes comptes. Un démarrage ne dépend jamais d’un dossier absent. Quand
le dossier de données a disparu (supprimé, sur un disque remplacé, ou sur un volume hors ligne à ce moment), le
démarrage crée `BucketVS` sur le volume du paquet, un nouveau dossier sauf si un dossier de ce nom s’y trouve déjà, et
dans un nouveau dossier la [première connexion](#première-connexion) définit le mot de passe de l’administrateur. Cela
vaut après une réinstallation comme pour un paquet installé, au démarrage du NAS aussi. Les journaux système contiennent
un avertissement à ce sujet, et des fichiers `.prev` gardent l’ancien chemin : `/etc/config/bucketvs-data-root.prev` et,
pour un paquet installé, aussi `etc/data-root.prev` dans le dossier d’installation. Si l’ancien dossier était sur un
volume qui n’était que hors ligne à ce moment, remettez le volume en ligne, arrêtez le paquet, copiez ce fichier
par-dessus `etc/data-root` dans le dossier d’installation (par exemple
`/share/CACHEDEV1_DATA/.qpkg/bucketvs/etc/data-root`) et démarrez le paquet : il tourne de nouveau sur les anciennes
données.

### Ouvrir le panneau depuis l’App Center

**Ouvrir** sur le paquet dans l’App Center ouvre le panneau sur le port 9950 (HTTP) ou 9951 (HTTPS). Quand les ports
sont changés dans le panneau ([Ports](#ports)), le paquet communique les nouveaux à l’App Center à son démarrage
suivant.

### Démarrage automatique depuis une session SSH

Tant que l’interrupteur du paquet est activé dans l’App Center, le NAS lance le paquet à son démarrage. Dans une
session SSH sur le NAS, en admin, le script de service règle le même interrupteur :

```sh
/etc/init.d/bucketvs.sh disable   # le prochain démarrage est refusé, y compris celui du NAS
/etc/init.d/bucketvs.sh enable    # le paquet démarre de nouveau avec le NAS
```

Chacune de ces commandes n’écrit que l’interrupteur (`Enable` dans `/etc/config/qpkg.conf`), affiche ce qu’elle a
réglé et écrit la même ligne dans les journaux système. Un paquet en marche continue de tourner après `disable`, et
`enable` ne démarre rien : c’est le rôle de `stop` et `start`, et `start` refuse tant que le paquet est désactivé.
`status` indique si bvsd tourne.

## Première connexion

Ouvrez le panneau à `https://<adresse du NAS>:9951/` ou `http://<adresse du NAS>:9950/`. L’adresse HTTPS utilise
d’abord un certificat auto-signé, donc le navigateur affiche un avertissement tant que l’appareil ne lui fait pas
confiance ; la section [Le certificat](#le-certificat) montre comment comparer son empreinte.

Une nouvelle installation n’a aucun compte. Tant que le premier administrateur n’a pas de mot de passe, le panneau
s’ouvre sur un seul écran, « Définir le mot de passe administrateur », pour le compte `administrator`. Saisissez un
mot de passe deux fois : au moins 10 caractères et au plus 72 octets. L’enregistrer crée le compte et vous connecte.

```mermaid
flowchart TD
    start["Le paquet démarre<br/>sans aucun compte"] --> screen["Définir le mot de passe administrateur<br/>60 minutes, réseau local seulement"]
    screen -->|"enregistré"| panel["administrator connecté"]
    screen -->|"60 minutes écoulées"| restart["Redémarrer le paquet dans l’App Center :<br/>une nouvelle fenêtre de 60 minutes"]
    restart --> screen
```

- L’écran accepte un mot de passe pendant 60 minutes après le démarrage du paquet. Ensuite, il indique que la fenêtre
  de configuration est fermée ; redémarrer Bucket & Code Server IDE dans l’App Center ouvre une nouvelle fenêtre de 60
  minutes.
- Il n’accepte un mot de passe que depuis le réseau local : depuis le NAS lui-même, ou depuis une adresse d’une plage
  privée (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `169.254.0.0/16`, `fc00::/7`, `fe80::/10`).
- Tant que l’écran est ouvert, les journaux système de QTS affichent une entrée d’information pour chaque adresse du
  NAS sur le réseau local, par exemple :

  ```text
  bucketvs: no administrator yet: open https://192.168.1.20:9951/ within 60 minutes to set the administrator's password
  ```

- Il refuse une requête passée par un proxy (une requête qui porte `X-Forwarded-For`, `X-Forwarded-Host`,
  `X-Forwarded-Proto`, `X-Real-IP` ou `Forwarded`), même depuis un proxy du réseau local : ouvrez le panneau
  directement depuis un ordinateur du réseau local pour définir le mot de passe.

Depuis une session SSH sur le NAS, `/usr/bin/bucketvs/bin/bvs user password administrator` crée le compte s’il
n’existe pas encore et affiche un mot de passe aléatoire ; le panneau demande alors votre propre mot de passe à la
première connexion ([Quand un admin est bloqué](#quand-un-admin-est-bloqué)).

Après la première connexion, la Vue d’ensemble indique que seul le cœur tourne. La tuile de chaque module dit ce qu’il
apporte et propose un bouton qui l’active, par exemple **Activer Git** : activez ceux dont vous avez besoin
([Modules](#modules)).

## Travailler dans le panneau

Le panneau fonctionne comme un éditeur de code : chaque page s’ouvre dans un onglet, et une palette de commandes
atteint les pages et les actions par leur nom.

- **Barre d’activités** (les icônes à gauche). Chaque icône ouvre sa page et affiche son arbre dans la barre
  latérale : les modules, les dépôts et les buckets, les apps, les paquets, les environnements, les tâches et les
  sauvegardes. Un point sur une icône signale un module en difficulté, une compilation échouée ou une sauvegarde
  échouée. Un clic sur l’icône de la page au premier plan masque la barre latérale ; Ctrl+B fait de même.
- **Onglets.** Une page ouverte d’un simple clic est un aperçu, en italique : la page suivante s’ouvre à sa place.
  Modifiez la page ou double-cliquez sur son onglet pour la garder. Les onglets reviennent après un rechargement, pour
  chaque compte dans chaque navigateur. Dans une fenêtre d’au moins 1280 pixels de large, **Déplacer l’onglet de
  l’autre côté** (`Ctrl+\`) affiche deux pages côte à côte.
- **Les objets et leurs menus.** Un clic sur une ligne de la barre latérale, une ligne de tableau ou une carte ouvre
  cet objet : sa page passe au premier plan, l’objet entouré de vert ou ses détails ouverts. L’adresse nomme l’objet :
  précédent, suivant et un lien copié y ramènent. Un double-clic garde l’onglet. Un clic droit, Maj+F10 ou un appui
  long sur un écran tactile ouvre le menu de l’objet : les façons de l’ouvrir (dans cet onglet, dans un onglet gardé,
  de l’autre côté, dans un nouvel onglet du navigateur), ce que sa page peut en faire, et la copie de son nom et de
  son adresse. Le menu d’un onglet le ferme, ferme les autres onglets, ceux à sa droite ou tous. Seul un élément
  marqué ↗ quitte le panneau. L’icône **Administration** ouvre la page Système.
- **Palette de commandes.** Ctrl+Maj+P (ou Ctrl+K, ou F1) liste les commandes que votre rôle peut lancer : ouvrir une
  page, compiler ou publier une app, lancer une sauvegarde, redémarrer un module, ouvrir un shell dans un
  environnement. Ctrl+P va à une page, une app, un dépôt, un environnement ou un paquet avec quelques lettres de son
  nom.
- **Panneau inférieur** (Ctrl+J). **Tâches** affiche les tâches en cours et récentes avec leur progression et leur
  journal. **Problèmes** liste ce que signalent les modules : compilations échouées, destinations de sauvegarde
  désactivées, modules en difficulté, un certificat sur le point d’expirer, et les constats de l’inspecteur ELF pour
  l’app au premier plan. **Sortie** suit la tâche que vous avez lancée en dernier.
- **Déplacer et redimensionner.** Faites glisser un bord pour changer la taille de ce qu’il borde : la barre latérale,
  le panneau inférieur, deux pages côte à côte, ou deux parties d’une page côte à côte, comme un assistant et son
  résumé ou la liste des fichiers d’un projet et le fichier. Un double-clic sur un bord le remet en place. Une boîte
  de dialogue se déplace par son titre et se redimensionne par un bord ou par son coin inférieur droit ; un
  double-clic sur son titre la remet au milieu. Pour placer le panneau inférieur à côté des pages, faites glisser son
  en-tête vers le bord droit de l’éditeur ou cliquez sur le bouton à côté de son × ; faites-le glisser vers le bord
  inférieur, ou cliquez de nouveau sur le bouton, pour le remettre en bas. Un onglet peut être glissé à une autre
  place de la barre d’onglets ou de l’autre côté. Le panneau garde chaque taille et chaque place pour votre compte
  dans ce navigateur, et **Réinitialiser la disposition** (dans la palette de commandes, ou sous le bouton de votre
  compte en bas de la barre d’activités) les remet toutes. Au clavier, les flèches déplacent un bord sélectionné (Maj
  par pas plus longs, Début le remet en place), Ctrl+Maj+Gauche et Droite déplacent un onglet sélectionné, et une
  boîte de dialogue a une barre en haut et une poignée en bas à droite avec lesquelles les flèches la déplacent et la
  redimensionnent. Sur un téléphone, les boîtes de dialogue et le panneau inférieur restent où ils sont.
- **Barre d’état.** États des modules, tâches en cours, problèmes, espace libre sur le volume de données, le
  certificat, le compte connecté, la langue (l’éditeur la prend à son prochain démarrage, en anglais pour le suédois et le néerlandais ; Git la suit, sauf si vous y choisissez une autre langue), le thème (sombre, clair, comme le système, bleu nuit, graphite ou papier : il appartient à votre compte, et l’éditeur et Git le suivent, sauf si vous y choisissez un autre thème) et la version. Chaque
  élément ouvre l’endroit où regarder de plus près.
- **Clavier.** Ctrl+/ ou ? liste les raccourcis. Un onglet de navigateur garde Ctrl+W et Ctrl+Tab pour lui, donc le
  panneau ferme un onglet avec Alt+W et change d’onglet avec Alt+Page suivante et Alt+Page précédente.
- **Sur un téléphone**, les premières activités sont en bas, **Plus** ouvre la barre latérale avec le reste, et les
  pages ouvertes forment une liste en haut.

## Comptes

Le panneau a ses propres comptes. Les comptes QTS ne s’y connectent pas.

### Rôles

| Rôle | Ce qu’il peut faire |
|---|---|
| viewer | lit le panneau ; les pages Sauvegardes, Stockage, Utilisateurs et Journaux sont réservées aux admins. Dans Forgejo, il ne crée, ne forke et n’importe aucun dépôt, ne crée aucune organisation et ne pousse jamais ; il lit les dépôts SVN et les buckets sur lesquels il a des droits et n’écrit dans aucun |
| member | lit ce que lit un viewer, et écrit : il pousse vers les dépôts Git où il reçoit l’accès en écriture, écrit dans les dépôts SVN et les buckets où il a lecture et écriture, et y restaure d’anciennes versions de fichiers. Dans Forgejo, il ne crée, ne forke et n’importe aucun dépôt et ne crée aucune organisation, et il ne peut pas pousser vers un dépôt qu’un compte de rôle inférieur à developer possède ou administre, ni, tant que le module CI est activé, vers un dépôt dont les Actions sont activées. Il n’a ni éditeur, ni tâches cron, ni compilations |
| developer | ouvre en plus l’éditeur, lit les lignes `bvs use` des toolchains installées, crée, modifie et lance ses propres tâches cron, crée et compile des apps, publie des paquets dans le dépôt (un admin les liste), vérifie des modules du noyau, et crée des dépôts Git dont les workflows s’exécutent sur le NAS tant que le module CI est activé |
| admin | tout : comptes, réglages, stockage, réseau, modules, toolchains, environnements, toutes les tâches cron, activation des apps, chargement des modules du noyau, liste des paquets dans le dépôt |

Le rôle developer est root sur le NAS : ses commandes cron s’exécutent en root, tout comme le terminal de son éditeur et,
tant que le module CI est activé, les workflows des dépôts où il peut pousser. Ne le donnez qu’aux personnes à qui
vous donneriez le mot de passe admin du NAS. Pour quelqu’un qui ne fait qu’écrire dans des dépôts, le rôle member
suffit.

Sous **Votre compte**, chacun voit son propre rôle et ce qu’il permet, avec un lien vers cette section. Une page
au-dessus du rôle d’une personne indique le rôle qu’elle nécessite, et ce que seul un admin modifie est signalé comme
tel là où il apparaît. Réseau, Git et CI montrent leurs réglages aux autres comme des valeurs, avec « Non défini » là où
une valeur manque.

Les workflows s’exécutent en root sur le NAS, donc bvsd garde les Actions désactivées dans chaque dépôt Git qu’un
member ou un viewer possède ou dans lequel un tel compte peut écrire : donner à un tel compte l’accès en écriture à
un dépôt désactive ses Actions en moins d’une minute, et la page Git liste ces dépôts pour les admins. Pour utiliser
les Actions dans l’un d’eux, retirez d’abord cet accès en écriture dans Forgejo, ou transférez un dépôt qu’un tel
compte possède au compte d’un developer.

### Créer et modifier des comptes

Un admin gère les comptes sur la page Utilisateurs.

- Un nouveau compte commence avec le rôle viewer. Developer et admin se choisissent sciemment : le rôle developer est
  root sur le NAS (plus haut).
- Un nouveau compte commence sans tâche cron, même si un compte du même nom en avait.
- Un nom a de 2 à 32 caractères : d’abord une lettre minuscule, puis des lettres minuscules, des chiffres, `.`, `_` ou
  `-`. Deux caractères parmi `.`, `_`, `-` à la suite, ou un seul à la fin, sont refusés. Tout comme les noms que
  Forgejo réserve (`admin`, `api`, `user` et d’autres), `bucketvs` (l’utilisateur système du paquet), `cli` et
  `system` (bvsd les utilise lui-même).
- « Doit choisir son propre mot de passe à la première connexion » oblige le compte à définir son propre mot de passe
  avant de pouvoir faire quoi que ce soit d’autre.
- Désactiver un compte met fin à ses sessions immédiatement et arrête ses jetons d’API. Ses données restent. Ses
  [tâches cron](#tâches-planifiées) restent aussi, mais ne s’exécutent pas tant qu’il est désactivé, et une exécution
  en cours de l’une d’elles est arrêtée.
- Donner le rôle member ou viewer à un compte a le même effet sur ses tâches cron : elles restent, ne s’exécutent pas
  tant que son rôle est inférieur à developer, et une exécution en cours est arrêtée.
- Un viewer ne pousse jamais vers Git, même là où il a reçu l’accès en écriture dans Forgejo. Un compte qui doit
  pousser reçoit le rôle member.
- Supprimer un compte le retire avec ses sessions et ses jetons d’API. Son dossier personnel part dans
  `trash/home/<nom>-<heure>` dans le dossier de données, où la tâche « Vider la corbeille » le supprime après 7 jours,
  et son dossier `tmp/tmux-<nom>` est supprimé. Ses tâches cron sont supprimées avec leurs exécutions et leurs
  journaux.
- Le dernier admin actif ne peut être ni rétrogradé, ni désactivé, ni supprimé.
- « Définir un nouveau mot de passe » met fin aux sessions du compte et révoque ses jetons d’API.

### Mots de passe et sessions

- Un mot de passe a au moins 10 caractères et au plus 72 octets.
- Changer votre propre mot de passe (sous Votre compte) met fin à vos autres sessions et révoque vos jetons d’API. La
  session dans laquelle vous l’avez changé reste connectée.
- Une session se termine 12 heures après sa dernière requête, et dans tous les cas 7 jours après la connexion. Un
  compte garde au plus 32 sessions ; une connexion de plus met fin à celle utilisée le moins récemment.
- Le cookie de session est `HttpOnly` et `SameSite=Lax`. Il est marqué `Secure` quand le navigateur a atteint le NAS
  en HTTPS, directement ou via un [proxy de confiance](#proxys-de-confiance).

### Mots de passe erronés

- Cinq connexions échouées en 15 minutes depuis une même adresse bloquent cette adresse pendant 15 minutes, même avec
  le bon mot de passe. Un client IPv6 compte comme son réseau /64.
- Cinq échecs pour un compte depuis une même adresse bloquent ce compte depuis cette adresse pendant 15 minutes.
- Les échecs pour un compte depuis de nombreuses adresses ralentissent ses connexions suivantes : 125 ms après un
  échec, en doublant jusqu’à 2 secondes. Depuis le réseau local, ils ne le bloquent jamais.
- Depuis Internet (toute adresse hors du réseau local), 20 connexions échouées pour un compte depuis sa dernière
  connexion réussie le verrouillent là, quelles que soient les adresses d’où elles venaient ; le bon mot de passe y est
  alors refusé aussi. Le compte se connecte toujours depuis le réseau local. Un admin le déverrouille sur la page
  Utilisateurs (**Déverrouiller**) ou avec `bvs user unlock <nom>`, ou lui donne un nouveau mot de passe. Le nombre se
  règle sur la page Utilisateurs, sous **Connexions depuis Internet** ; 0 désactive ce verrouillage.
- Les compteurs sont gardés en mémoire, donc un redémarrage de bvsd les efface ; un verrouillage reste jusqu’à ce
  qu’on le lève.

Derrière un proxy, l’adresse comptée est celle que le proxy indique, mais seulement quand le proxy est un [proxy de
confiance](#proxys-de-confiance). Sinon, chaque client compte comme l’adresse du proxy lui-même, et cinq mauvais mots
de passe de n’importe qui bloquent tout le monde pendant 15 minutes.

### Authentification à deux facteurs

Chaque compte l’active pour lui-même sous Votre compte : scannez le code QR avec une application d’authentification
(ou saisissez la clé), puis entrez le code à 6 chiffres que l’application affiche. Ensuite, la connexion demande un
code après le mot de passe.

- Les codes suivent la RFC 6238 : 6 chiffres, un nouveau code toutes les 30 secondes. Un code en avance ou en retard
  de 30 secondes est accepté. Le même code n’est jamais accepté deux fois.
- Si un code tout frais échoue, l’horloge du téléphone est probablement décalée ; réglez-la sur l’heure automatique.
- La désactiver demande le mot de passe du compte.
- Pour un téléphone perdu, un admin la désactive sur la page Utilisateurs. Le mot de passe seul permet alors de se
  connecter.

### Jetons d’API

Les scripts et la CI utilisent des jetons d’API au lieu d’un mot de passe. Sous Votre compte, « Nouveau jeton » en
crée un :

- Il ressemble à `bvs_` suivi de 40 chiffres hexadécimaux, et il n’est affiché qu’une fois. bvsd n’en garde que le
  hachage.
- Un script l’envoie sous la forme `Authorization: Bearer bvs_...`.
- Ses portées sont `read`, `write` et `admin`, qui correspondent aux rôles viewer, developer et admin. Un jeton agit
    avec sa portée la plus haute, jamais au-dessus du rôle actuel de son propriétaire, donc rétrograder un compte
  rétrograde aussi ses jetons. Aucune portée ne correspond à member : un member crée des jetons `read`.
- Un nouveau mot de passe révoque tous les jetons du propriétaire. Les jetons d’un propriétaire désactivé cessent de
  fonctionner.
- Un jeton ne peut changer ni les mots de passe ni l’authentification à deux facteurs, et ne peut ni créer ni supprimer de
  jetons : il faut pour cela une session connectée. Un jeton divulgué ne peut donc pas bloquer son propriétaire.

### Quand un admin est bloqué

L’admin du NAS peut réinitialiser n’importe quel compte depuis une session SSH sur le NAS. La commande `bvs` parle à
bvsd par un socket que seul root peut ouvrir (`/usr/bin/bucketvs/var/run/bvsd-cli.sock`, mode 600) :

```bash
/usr/bin/bucketvs/bin/bvs user list
/usr/bin/bucketvs/bin/bvs user password administrator
/usr/bin/bucketvs/bin/bvs user totp-reset administrator
/usr/bin/bucketvs/bin/bvs user unlock administrator
```

- `bvs user password <nom>` définit un nouveau mot de passe aléatoire de 20 caractères et l’affiche. Le compte doit
  choisir le sien à la connexion suivante. Ses sessions se terminent et ses jetons d’API sont révoqués. Sur une
  nouvelle installation sans aucun compte, `bvs user password administrator` crée le compte.
- `bvs user totp-reset <nom>` désactive l’authentification à deux facteurs du compte.
- `bvs user unlock <nom>` lève le verrouillage du compte après trop de mots de passe erronés depuis Internet.

Chaque requête sur ce socket agit en tant qu’admin intégré `cli` et est écrite dans `log/audit.log` dans le dossier de
données. bvsd doit tourner pour ces commandes.

## Le dossier de données

Le dossier de données est le dossier qui contient tout ce que le paquet conserve. Son chemin est l’unique ligne de
`/usr/bin/bucketvs/etc/data-root`. La page Stockage l’affiche, avec le volume où elle se trouve et l’espace libre sur
celui-ci.

bvsd ne démarre que sur un dossier de données qui est un dossier sur un vrai volume. Le démarrage du paquet le vérifie
avant : un dossier absent, ou un dossier sur tmpfs ou ramfs, donne un nouveau dossier de données sur le volume
d’installation, comme le décrit [Où commence le dossier de données](#où-commence-le-dossier-de-données). Un volume hors
ligne laisse son point de montage sous forme de dossier vide dans la RAM du NAS, et les réglages écrits là
disparaîtraient au redémarrage suivant. bvsd lui-même ne refuse un tel dossier que s’il est lancé à la main, et la
raison est alors écrite dans les journaux système de QTS.

### Ce que bvsd donne à chaque processus

QTS garde `/tmp` dans un petit disque RAM et `/root` sur un disque RAM qu’un redémarrage vide. Chaque processus que
bvsd démarre (programmes des modules, tâches cron, installations) reçoit donc ses fichiers temporaires et son dossier
personnel dans le dossier de données :

| Variable | Valeur |
|---|---|
| `HOME` | `<dossier de données>/home/<utilisateur>` |
| `TMPDIR` | `<dossier de données>/tmp` |
| `XDG_CACHE_HOME`, `XDG_CONFIG_HOME` | `.cache` et `.config` dans ce dossier personnel |
| `npm_config_cache` | `.npm` dans ce dossier personnel |
| `PATH` | `~/.local/bin`, `~/.npm-global/bin`, puis `bin`, `sbin` et `node/bin` du paquet, puis les dossiers du système |
| `SSL_CERT_FILE`, `GIT_SSL_CAINFO`, `CURL_CA_BUNDLE` | le paquet de certificats CA du paquet, `/usr/bin/bucketvs/etc/ssl/cacert.pem` |
| `TZ` | le fuseau horaire du NAS, tel que réglé dans QTS |
| `SHELL`, `EDITOR` | `bash` et `nano` du paquet |
| `LANG`, `LESSCHARSET` | `C.UTF-8` et `utf-8`, pour que les noms de fichiers avec des lettres accentuées s’affichent tels quels |

### Déplacer le dossier de données

La page Stockage déplace tout le dossier de données vers un autre dossier sous `/share`. Choisissez un dossier
partagé ou un volume ; les données vont dans un nouveau dossier nommé `BucketVS` à l’intérieur. Pour utiliser un
dossier lui-même, ouvrez un dossier vide nommé `BucketVS`. Les dossiers partagés se créent dans QTS (Panneau de
configuration, Dossiers partagés) ; à l’intérieur d’un dossier partagé ou d’un volume, la page peut créer un nouveau
dossier. Le sélecteur s’ouvre là où se trouvent les données et l’indique ; ouvrez de là le dossier partagé ou le
volume où elles doivent aller.

La page vérifie la cible avant que le déplacement commence et liste chaque raison pour laquelle elle en refuse une :

- le chemin doit être sous `/share`, et pas `/share` lui-même ;
- aucun `:`, `&`, `|` ou caractère de contrôle dans une partie du chemin, ni dans le chemin vers lequel mène un lien ;
- ni le dossier de données actuel, ni un dossier à l’intérieur, ni un dossier qui le contient ;
- un nouveau dossier, ou un dossier vide ;
- un système de fichiers qui garde les propriétaires et les permissions Unix (FAT, exFAT et NTFS sont refusés :
  Forgejo et SVN ont besoin des propriétaires et des modes) ;
- un volume monté avec `exec`, `dev` et `suid`, dont les environnements de compilation ont besoin ;
- accessible en écriture, avec de l’espace libre pour les données plus 10 %.

```mermaid
flowchart TD
    check["Vérifier de nouveau la cible<br/>espace libre : les données + 10 %"] --> stop["Arrêter les modules<br/>le panneau reste actif"]
    stop --> copy["Copier avec<br/>rsync -aHAX --numeric-ids"]
    copy --> verify["Vérifier la copie<br/>fichiers, octets, propriétaires, modes, dates,<br/>sha256 de jusqu’à 1000 fichiers"]
    verify --> switch["Basculer le dossier de données<br/>dans /usr/bin/bucketvs/etc/data-root"]
    switch --> start["bvsd redémarre sur la nouvelle racine<br/>et démarre les modules"]
    start --> old["L’ancienne copie reste, marquée MOVED-TO,<br/>jusqu’à ce que vous la supprimiez"]
    copy -->|échec| failed["L’ancienne racine reste utilisée,<br/>les modules redémarrent"]
    verify -->|une différence| failed
```

Pendant le déplacement :

- Git, CI, SVN et Bucket, et l’Éditeur ne répondent pas. Le panneau reste actif et affiche la progression.
- Aucune nouvelle exécution cron ne démarre. Une exécution déjà en cours continue.
- Activer ou désactiver un module est refusé jusqu’à la fin du déplacement.

Après la bascule, bvsd s’arrête et redémarre sur la nouvelle racine, donc le panneau disparaît quelques secondes.

Quand le déplacement échoue, l’ancien dossier de données reste utilisé et les modules redémarrent. L’erreur de la
tâche dit ce qui a échoué et ce qu’est devenue la copie :

- Un volume plein, ou une annulation, supprime ce que le déplacement a créé.
- Tout autre échec (la copie, la vérification, l’écriture sur le disque) garde la copie, marquée par un fichier nommé
  `INCOMPLETE`, et la liste sur la page Stockage sous « Copies restantes de déplacements échoués ».
- Un arrêt du paquet pendant le déplacement garde l’ancien dossier comme dossier de données. Au démarrage suivant,
  bvsd signale le déplacement inachevé dans les journaux système et liste la copie.

### L’ancienne copie et les copies restantes

Après un déplacement réussi, l’ancien dossier de données reste où il était, avec un fichier nommé `MOVED-TO` à
l’intérieur. Rien ne l’utilise. « Supprimer l’ancienne copie » sur la page Stockage la supprime, et « Supprimer cette
copie » supprime une copie restante d’un déplacement échoué. Les deux suppressions restent sur un seul système de
fichiers et ne suivent jamais un lien. Un dossier qui est un point de montage ou le dossier propre d’un dossier
partagé est vidé et conservé.

## Réseau

La page Réseau regroupe les ports, le mode HTTP simple, les noms d’hôte des modules, les proxys de confiance, l’URL
publique et le certificat. L’enregistrement redémarre ce dont la modification a besoin et le liste.

### Ports

| Réglage | Par défaut | Ce que c’est |
|---|---|---|
| HTTPS | 9951 | le panneau et les modules via TLS |
| HTTP | 9950 | le panneau et les modules en HTTP simple |
| Git via SSH | 9952 | Git via SSH |
| Port SSH dans les URL de clonage | 0 | le port affiché dans les adresses de clonage SSH ; 0 signifie le port SSH ci-dessus. Réglez-le quand un routeur redirige un autre port vers le NAS. |
| Hôte SSH dans les URL de clonage | vide | le nom ou l’adresse affiché dans les adresses de clonage SSH. Vide signifie le nom d’hôte Git, sinon l’hôte de l’URL publique, sinon l’adresse LAN du NAS ; pour un nom publié par le Cloudflare Tunnel, l’adresse LAN et le port de Git via SSH. Réglez-le sur un nom uniquement DNS ou sur l’adresse du routeur quand le routeur redirige le port SSH. |

Les ports vont de 1024 à 65535 et diffèrent les uns des autres. Les ports des modules sur 127.0.0.1 (9960 et 9961)
sont refusés, tout comme le port qu’utilise httpd ou le serveur du runner CI quand un autre programme a pris 9960 ou
9961 (le message le nomme). Quand l’enregistrement laisse nginx sur ses anciens ports parce qu’il n’a pas pu utiliser
un nouveau, une notification en donne la raison. nginx écoute aussi en IPv6 quand le NAS l’a.

### HTTP simple

- « Servir » (par défaut) : le panneau et les modules répondent aussi en HTTP simple. Gardez ce choix quand un proxy
  du réseau local parle en HTTP simple au port 9950.
- « Rediriger vers HTTPS » : une requête en HTTP simple reçoit une redirection (307) vers le port HTTPS. Une requête
  d’un proxy de confiance qui indique que son client a utilisé HTTPS est servie en HTTP simple, pour qu’un tel proxy
  ne tourne pas en boucle.

La page de connexion avertit quand elle est arrivée en HTTP simple : le mot de passe traverse alors le réseau en
clair.

### Le certificat

Au premier démarrage, bvsd crée un certificat auto-signé (ECDSA P-256) pour :

- le nom d’hôte du NAS, les noms d’hôte des modules et `localhost` ;
- les adresses du NAS sur le réseau local et `127.0.0.1`.

Il est valide 825 jours, le maximum qu’acceptent les appareils Apple, même pour un certificat approuvé à la main. bvsd
le vérifie au démarrage et une fois par jour, et en crée un nouveau quand il reste 30 jours ou moins, quand l’ensemble
des noms change, ou quand sa clé ne correspond pas. Un nouveau certificat envoie une [notification](#notifications).
La page Réseau affiche ses noms, sa validité et son empreinte SHA-256 : comparez l’empreinte avant qu’un appareil lui
fasse confiance.

« Ajouter votre propre certificat » prend une chaîne de certificats et sa clé privée au format PEM :

- le certificat du serveur d’abord, puis les certificats intermédiaires ;
- une clé sans phrase de passe (nginx démarre sans intervention) ;
- la clé doit appartenir au premier certificat, chaque certificat doit être signé par le suivant, aucun ne doit avoir
  expiré, et le premier doit nommer ses hôtes comme noms alternatifs du sujet (SAN).

La paire est stockée dans `etc/tls/` dans le dossier de données avec le mode 600. Quand un certificat téléversé expire
ou ne passe plus ces vérifications, nginx sert à la place le certificat auto-signé et une notification en donne la
raison. Un certificat téléversé à qui il reste 30 jours ou moins envoie aussi une notification. « Nouveau certificat
auto-signé » crée une nouvelle paire auto-signée et revient à celle-ci.

Les clients qui passent par un proxy voient le certificat du proxy, pas celui-ci.

**Télécharger le certificat** sur la page Réseau enregistre le certificat que sert le panneau, sans sa clé, sous le nom
`<nom>.crt`. Ajoutez-le aux certificats de confiance d’un appareil pour que ses navigateurs fassent confiance au
panneau ; les vues des extensions de l’éditeur en ont besoin ([Extensions](#extensions)). Il faut faire de nouveau
confiance à un nouveau certificat autosigné.

### Proxys de confiance

Une liste d’adresses IP ou de plages CIDR, comme `192.168.1.5`, l’adresse du proxy placé devant le NAS. Seulement pour
une requête dont la connexion TCP vient de l’une d’elles, nginx prend :

- l’adresse du client dans `X-Forwarded-For` ;
- le schéma utilisé par le client dans `X-Forwarded-Proto` (seuls `http` et `https` comptent).

Une requête venant de toute autre adresse compte comme venant directement de son client, et nginx remplace ses
en-têtes `X-Forwarded-*` par ses propres valeurs. Une plage rend fiable chaque hôte qu’elle contient : chacun peut
alors définir l’adresse du client et le schéma, donc indiquez l’adresse propre du proxy. Une plage de toutes les
adresses (`0.0.0.0/0`, `::/0`) est refusée.

### URL publique

L’adresse seule, comme `https://git.example.com`, sans chemin. Les sections des modules indiquent quels liens
commencent par elle.

## Derrière un proxy inverse (Plesk)

La configuration habituelle place TLS sur un proxy avec un certificat public et le laisse parler en HTTP simple au
port 9950 sur le réseau local. Les exemples ci-dessous utilisent `bvs.example.com` pour le nom public, `192.168.1.20`
pour le NAS et `192.168.1.5` pour le serveur Plesk ; mettez les vôtres à leur place.

```mermaid
flowchart LR
    client["Navigateur, git, client WebDAV"] -->|"HTTPS 443"| plesk["nginx de Plesk<br/>bvs.example.com<br/>certificat public"]
    plesk -->|"HTTP 9950<br/>Host, X-Forwarded-For,<br/>X-Forwarded-Proto: https,<br/>Upgrade pour WebSocket"| nas["nginx sur le NAS<br/>192.168.1.20"]
    nas --> panel["/ panneau, /api/ bvsd"]
    nas --> mods["/git/, /svn/, /bucket/, /ide/"]
    sshc["git via SSH"] -->|"TCP 9952, sans passer par le proxy"| nas
```

### Sur le NAS

1. Sur la page Réseau, ajoutez l’adresse du serveur Plesk (telle que le NAS la voit) aux proxys de confiance.
2. Le HTTP simple peut rester sur « Servir ». « Rediriger vers HTTPS » fonctionne aussi une fois que le serveur Plesk
   est un proxy de confiance.

### Dans Plesk

Placez ces directives dans les paramètres Apache et nginx du domaine, dans le champ « Directives nginx
supplémentaires » (dans l’interface anglaise, « Additional nginx directives ») :

```nginx
location ~ ^/ {
    proxy_pass http://192.168.1.20:9950;
    proxy_http_version 1.1;

    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    # WebSocket : l’éditeur et son terminal
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $http_connection;

    # gros push et téléversements : pas de limite de taille, en flux, une heure de silence permise
    client_max_body_size 0;
    proxy_request_buffering off;
    proxy_buffering off;
    proxy_read_timeout 3600s;
    proxy_send_timeout 3600s;
}
```

`location ~ ^/` est une location à expression régulière. nginx préfère une expression régulière qui correspond à un
simple `location /` (Plesk en écrit un à lui), donc ce bloc sert chaque chemin du site, et `proxy_pass` sans chemin
transmet le chemin de la requête sans le changer. Sur la même page de paramètres, désactivez « Servir les fichiers
statiques directement par nginx » : s’il est activé, Plesk répond aux requêtes de fichiers `.js`, `.css` et images
depuis le dossier du domaine, et le panneau ne se charge pas.

Dans les paramètres d’hébergement du domaine, activez la redirection permanente 301 de HTTP vers HTTPS adaptée au
référencement (dans l’interface anglaise, « Permanent SEO-safe 301 redirect from HTTP to HTTPS »). Les directives
nginx supplémentaires s’appliquent aussi au site du domaine en HTTP simple sur le port 80 ; sans la redirection, le
site y répond aussi, et les mots de passe traversent Internet sans chiffrement.

Pourquoi chaque ligne compte :

| Directive | Ce qui ne marche pas sans elle |
|---|---|
| `Host $http_host` | nginx sur le NAS choisit le bloc serveur d’après `Host`, donc un nom d’hôte de module ne correspond jamais. bvsd compare l’`Origin` de la page avec `Host`, port compris, donc chaque modification dans le panneau échoue avec `403 csrf` ; `$host` supprimerait un port public autre que 443 et échouerait de la même façon. |
| `X-Forwarded-For` | chaque client compte comme le serveur Plesk : cinq mauvais mots de passe d’une seule personne bloquent tout le monde pendant 15 minutes |
| `X-Forwarded-Proto $scheme` | le cookie de session n’est pas marqué `Secure`, et en mode « Rediriger vers HTTPS » chaque requête est envoyée au port 9951 du nom public |
| `Upgrade`, `Connection` | la page de l’éditeur s’ouvre mais l’éditeur et son terminal ne se connectent jamais |
| `client_max_body_size 0`, `proxy_request_buffering off`, les délais | la limite de taille et les délais propres du proxy s’appliquent aux gros push et téléversements |

`X-Forwarded-For` et `X-Forwarded-Proto` ne comptent que si le serveur Plesk figure parmi les proxys de confiance.
Sinon, nginx sur le NAS les ignore.

### Git via SSH

Un proxy HTTP ne transporte pas SSH. Git via SSH va directement au port 9952 du NAS, ou passe par une redirection de
port TCP sur le routeur. « Port SSH dans les URL de clonage » sur la page Réseau définit le port qu’affichent les
adresses de clonage, et « Hôte SSH dans les URL de clonage » le nom ou l’adresse, quand le nom public n’atteint que
le proxy.

### Vérifier la configuration

- Le panneau s’ouvre à `https://bvs.example.com/`, et la connexion et l’enregistrement d’un réglage fonctionnent.
- Les outils de développement du navigateur montrent le cookie de session `bvs_session` avec l’attribut `Secure`.
- La page Journaux affiche `nginx-access` : le premier champ de chaque ligne est l’adresse propre du client, pas celle
  du serveur Plesk.

## Noms d’hôte des modules

Un module peut avoir son propre nom d’hôte (page Réseau, Noms d’hôte). nginx sert alors ce module seul à la racine du
nom. Le nom doit pointer vers le NAS, ou vers le proxy placé devant lui. Sur un proxy, chaque nom d’hôte a besoin de
son propre site, avec les mêmes directives que ci-dessus.

| Module | À la racine de son nom d’hôte | Aussi présent |
|---|---|---|
| Git | Forgejo | `/git/` sur tout autre nom répond 302 vers le nom d’hôte Git, avec le même chemin, la même requête et le même port |
| Éditeur | code-server | la page de connexion du panneau et `/api/`, pour qu’un utilisateur puisse se connecter sur ce nom |
| SVN et Bucket | httpd | les dépôts restent à `/svn/<nom>` et les buckets à `/bucket/<nom>` |

Un module désactivé répond `503` avec une phrase qui le dit, sur ses chemins et sur son nom d’hôte.

### Une seule connexion pour tous les noms d’hôte

Le cookie de session appartient au nom sur lequel il a été posé, sauf si le réglage `network.cookie_domain` désigne un
domaine parent de tous les noms d’hôte des modules. Avec `example.com`, une connexion sur `bvs.example.com` atteint
aussi `git.example.com` et `ide.example.com`. bvsd refuse un suffixe public (`com`, `com.pl`, `co.uk`,
`myqnapcloud.com`), un domaine qui n’est pas parent de tous les noms d’hôte des modules, et tout domaine tant qu’aucun
nom d’hôte de module n’est défini. Chaque site sous ce domaine reçoit le cookie, donc choisissez le parent commun le
plus étroit.

La page Réseau n’affiche pas ce réglage. L’admin du NAS le définit par le socket CLI avec le `curl` du paquet :

```bash
/usr/bin/bucketvs/bin/curl --unix-socket /usr/bin/bucketvs/var/run/bvsd-cli.sock \
  -X PUT -H 'Content-Type: application/json' \
  -d '{"network":{"cookie_domain":"example.com"}}' \
  http://bvsd/api/v1/settings
```

Une valeur vide (`""`) garde de nouveau le cookie sur chaque nom.

## Publier sur Internet (Cloudflare Tunnel)

La page **Publication** (Administration, admins seulement) met le paquet sur Internet sous des noms d’un domaine dont le
DNS est chez Cloudflare. `cloudflared`, que le paquet fournit, établit la connexion du NAS vers Cloudflare : aucun
port du routeur n’est ouvert, et Cloudflare détient les certificats des noms. Chaque requête passe par le nginx du
paquet : le panneau et son API exigent une connexion, et seul ce que vous avez rendu public (dépôts publics, leur vue
web, le dépôt de paquets) s’ouvre sans compte.

Il existe deux types de noms, un de chaque au plus :

| Type | Exemple | Ce qu’il sert |
|---|---|---|
| Nom d’hôte principal | `code.example.com` | le panneau et son API, Forgejo à `/git/`, la vue web à `/svn-web/` et `/bucket-web/`, le dépôt de paquets à `/repo/` (en lecture seule) et le guide. `/svn/` et `/bucket/` y répondent 404, et `/ide/` aussi tant que vous ne publiez pas l’éditeur (plus bas) |
| Nom d’hôte des paquets | `qpkg.example.com` | seulement GET et HEAD de `/repo/`, pour l’App Center d’autres NAS ; tout le reste répond 404 |

### Ce dont le jeton a besoin

Créez un jeton d’API sur dash.cloudflare.com (My Profile, API Tokens) avec ces permissions, pour la zone des noms :

- Account, Cloudflare Tunnel, Edit
- Zone, DNS, Edit
- Zone, Cache Rules, Edit
- Zone, Cache Purge, Purge

Le paquet garde le jeton sur le NAS dans un fichier que seul root lit, et ne l’affiche plus jamais.

Si le jeton a un Client IP Address Filtering, mettez-y les deux adresses publiques du NAS, IPv4 et IPv6 : le NAS
appelle Cloudflare en IPv6 quand il a une adresse IPv6. Sinon la page Publication refuse avec `token_ip_filter` et donne
l’adresse que Cloudflare a vue.

### Publier

Saisissez les noms et le jeton, puis **Publier**. Le paquet vérifie d’abord tout chez Cloudflare et n’y change rien
quand une vérification échoue ; le message dit quoi corriger :

- une permission qui manque au jeton, par son nom ;
- un nom dans une zone que le jeton ne voit pas ;
- un enregistrement DNS que le nom a déjà (son type et vers où il pointe) : supprimez-le chez Cloudflare ou choisissez
  un autre nom ;
- un nom d’hôte principal alors que **Navigation publique** (page Git) est désactivée, puisque ses visiteurs n’ont pas de
  compte ;
- des noms dans des zones de deux comptes Cloudflare.

Ensuite il crée le tunnel (nommé `bucketvs-<nom du NAS>` sauf si vous donnez un nom ; un tunnel de ce nom que
Cloudflare garde est repris), fait pointer chaque nom vers lui par un enregistrement CNAME proxifié, ajoute les règles
de cache et démarre cloudflared. **Publier à nouveau** avec les mêmes valeurs ne change rien. Réglez l’URL publique
(Réseau) sur l’adresse `https://` du nom d’hôte principal, pour que les liens et les adresses de clonage HTTPS de Forgejo le
nomment ; la page vous le rappelle tant qu’elles diffèrent.

Git via SSH ne passe pas par le tunnel : Cloudflare ne transporte que du HTTP vers un nom publié. L’adresse de clonage
SSH est donc l’adresse LAN du NAS avec son port de Git via SSH (9952), dans le panneau et dans la zone de clonage de
Forgejo, et la page Git et la Vue d’ensemble disent « Le clonage via SSH fonctionne sur le réseau local ; depuis
Internet, clonez via HTTPS. » Pour SSH depuis Internet, redirigez le port SSH sur le routeur et réglez **Hôte SSH
dans les URL de clonage** (Réseau) sur l’adresse publique du routeur ou sur un nom uniquement DNS qui pointe vers
elle, avec **Port SSH dans les URL de clonage** quand le routeur utilise un autre port.

Le nom d’hôte principal répond 404 à `/ide/` tant qu’un admin n’active pas **Publier l’éditeur sur le nom d’hôte principal** sur la
page **Publication**. Le terminal de l’éditeur est un shell root sur le NAS, et quiconque s’y connecte depuis Internet
comme développeur ou admin l’obtient : activez donc d’abord l’authentification à deux facteurs pour ces comptes. Interrupteur
activé, l’éditeur passe par la même connexion que sur le réseau local, son proxy de ports (`/ide/proxy/<port>/`) y
reste désactivé et Cloudflare ne garde jamais ses pages en cache. Tant qu’il est activé, la Vue d’ensemble affiche
**Éditeur sur Internet**.

Tant que le module CI est actif, un workflow poussé dans n’importe quel dépôt de ce Forgejo s’exécute en root sur le
NAS par le runner du NAS lui-même, qu’il ait été poussé depuis Internet ou depuis le réseau local : le même pouvoir que
le terminal de l’éditeur. La page **Publication** le dit sous **Ce qu’Internet atteint**. N’accordez l’accès en push qu’aux
personnes à qui vous donneriez le mot de passe admin du NAS.

### Le cache

Avec **Préréglages de cache** activé, Cloudflare garde les paquets (`*.qpkg`) un an, les icônes un jour, `repo.xml` 5
minutes et les fichiers du panneau un an ; il ne garde jamais l’API, les pages de Forgejo, les pages de connexion ni
la vue web. Une publication dans le dépôt de paquets retire aussitôt `repo.xml` (et une icône modifiée ou les fichiers
d’une version supprimée) du cache de Cloudflare. Le paquet ajoute ses propres règles à la fin des règles de cache de
la zone et ne modifie jamais les autres.

### Connexion depuis Internet

Le premier mot de passe ne peut jamais être défini par un nom publié : la configuration initiale n’accepte qu’un
client local. Les mauvais mots de passe comptent par client Internet, comme sur le réseau local. Un mot de passe
envoyé à Forgejo avec git compte aussi ; un jeton d’accès Forgejo fonctionne comme toujours. Cloudflare accepte des
requêtes jusqu’à 100 Mo, donc un push ou un envoi plus gros passe par l’adresse du réseau local.

### Arrêter et retirer

**Arrêter** met fin à cloudflared ; les noms affichent alors la page d’erreur de Cloudflare, et rien ne change chez
Cloudflare. **Retirer de Cloudflare** supprime les enregistrements DNS, les règles de cache et le tunnel que le paquet
a créés (un tunnel repris reste) et oublie le jeton. Tant que des noms sont publiés, la Vue d’ensemble affiche une
étiquette **Internet** avec l’état du tunnel.

## Modules

La Vue d’ensemble affiche chaque module avec ses processus, sa mémoire et son CPU, et l’active ou le désactive (admin
seulement). Un module désactivé dit ce qu’il apporte ; un admin l’active avec son bouton (**Activer Git**,
**Activer Éditeur**, etc.) sur sa tuile, sur sa propre page ou, pour l’Éditeur, dans l’onglet Éditeur.

- Activer un module lance une tâche : le module se prépare, l’interrupteur est enregistré, et ses processus démarrent.
  La tâche les attend jusqu’à 30 secondes.
- CI a besoin de Git : Git ne peut pas être désactivé tant que CI est activé.
  Tant que Git est désactivé, le bouton de CI s’intitule **Activer Git et CI** et active Git d’abord.
- Un module désactivé n’exécute aucun processus et n’utilise aucune mémoire.
- Le cœur est toujours actif.

| État | Signification |
|---|---|
| en cours | chaque processus du module tourne |
| démarrage | un processus a démarré il y a moins d’une seconde |
| dégradé | un processus attend d’être redémarré, les fichiers du module n’ont pas pu être écrits, ou son contrôle de santé échoue |
| redémarre en boucle | un processus s’est arrêté 5 fois en 10 minutes ; bvsd le redémarre toutes les 15 minutes, et une notification est partie |
| arrêté | les processus du module ne tournent pas (un déplacement des données les a arrêtés, ou ils n’ont pas pu démarrer) |
| désactivé | le module est désactivé |

Un processus qui s’arrête est redémarré après 1 seconde, puis 2, 4 et ainsi de suite jusqu’à 60 secondes. Après 10
minutes de fonctionnement, l’attente repart de 1 seconde. Un processus qui redémarre en boucle repart aussitôt quand
son module est désactivé puis réactivé.

## Git

Le module Git fait tourner Forgejo, une interface web pour les dépôts git avec tickets, demandes d’ajout (pull
requests), versions, wikis, LFS, miroirs et webhooks. Un admin l’active avec **Activer Git** dans la Vue d’ensemble ou
sur la page Git. La tâche prépare les dossiers et la base de données de Forgejo dans `<données>/forgejo`, ajoute
chaque compte du panneau à Forgejo, et le démarre. Forgejo s’ouvre ensuite à `/git/` sur l’adresse du panneau, ou à
`/` du nom d’hôte Git quand il en existe un (voir [Noms d’hôte des modules](#noms-dhôte-des-modules)).

**Nouveau dépôt** sur la page Git, et sur la tuile Git de la Vue d’ensemble tant qu’il n’y en a aucun, ouvre le
formulaire de Forgejo pour créer un dépôt.
Le panneau le propose, ainsi que **Migrer ou mettre en miroir un dépôt**, à partir du rôle developer : Forgejo ne
laisse un viewer ou un member créer aucun dépôt.

### Dépôts et comptes

- **Une seule connexion.** Forgejo n’a pas de connexion à lui : `/git/user/login` mène à la connexion du panneau, avec
  le code d’authentification quand le compte en a un, et revient à Forgejo ensuite.
- **Les comptes viennent du panneau.** Chaque compte du panneau a un compte Forgejo du même nom et avec le même mot de
  passe. Le rôle `admin` est administrateur du site dans Forgejo. Un compte désactivé, ou un compte qui doit d’abord
  changer son mot de passe, ne peut pas non plus se connecter à Forgejo. Une modification dans le panneau atteint
  Forgejo en quelques secondes. Les pages Utilisateurs et Git affichent l’état de chaque compte dans Forgejo, et
  **Synchroniser tous les comptes maintenant** sur la page Git relance la synchronisation.
- **Supprimer un compte** dans le panneau retire son compte Forgejo du service : il ne peut plus se connecter, et ses
  jetons et clés SSH sont supprimés, mais ses dépôts restent. Le nom ne peut être donné à un nouveau compte du panneau
  qu’après qu’un admin a renommé ou supprimé l’ancien compte Forgejo dans l’administration du site de Forgejo
  (**Comptes utilisateurs**).
- **Un compte créé dans Forgejo lui-même** n’est jamais modifié par le panneau. Un compte du panneau du même nom
  affiche la raison et attend que l’un des deux soit renommé.
- **Authentification à deux facteurs et git.** Forgejo n’accepte aucun mot de passe pour un compte dont l’authentification à deux
  facteurs est activée. git en HTTP ou HTTPS demande alors un jeton d’accès Forgejo (dans Forgejo, **Paramètres**,
  **Applications**) à la place du mot de passe. Les clés SSH fonctionnent comme avant.
- **Les dépôts** se créent, s’importent et se mettent en miroir dans les pages de Forgejo. Les importations depuis des
  adresses du réseau local sont autorisées par défaut (réglage : `git.allow_local_networks`), car un NAS fait en
  général des miroirs depuis son propre réseau.

### Cloner en HTTPS et en SSH

Sur la page Git, **Cloner un dépôt** écrit les deux commandes pour un propriétaire et un dépôt :

| Voie | Adresse | Connexion |
|---|---|---|
| HTTPS (ou HTTP) | `https://<adresse du NAS>:9951/git/<propriétaire>/<dépôt>.git` | le nom d’utilisateur et le mot de passe du panneau, ou un jeton d’accès |
| SSH | `ssh://git@<adresse du NAS>:9952/<propriétaire>/<dépôt>.git` | une clé SSH ajoutée dans Forgejo sous **Paramètres**, **Clés SSH / GPG** |

- Avec une URL publique (derrière un proxy, [URL publique](#url-publique)), l’adresse HTTPS l’utilise ; avec un nom
  d’hôte Git, le chemin est `/<propriétaire>/<dépôt>.git` sur ce nom.
- Le port SSH est `network.ssh_port` (9952), ou `network.public_ssh_port` quand un routeur y redirige un autre port.
  Le proxy ne transporte pas SSH : redirigez le port sur le routeur (voir [Git via SSH](#git-via-ssh)).
- Un nom publié par le Cloudflare Tunnel ne transporte pas SSH, donc l’adresse de clonage SSH donne alors l’adresse
  LAN du NAS, sauf si `network.public_ssh_host` (Réseau, « Hôte SSH dans les URL de clonage ») donne un nom en DNS
  seul ou l’adresse du routeur.
- Les gros push ne sont pas coupés : nginx ne fixe aucune limite de taille du corps sur `/git/`.

### Montrer des dépôts aux personnes sans compte

**Navigation publique** sur la page Git permet à quiconque atteint Forgejo de lire ses dépôts publics sans compte.
Dans Forgejo, chaque dépôt est public ou privé (ses **Paramètres**, **Visibilité**). Interrupteur activé, un visiteur
sans compte peut :

- ouvrir un dépôt public, ses dossiers et ses fichiers, et télécharger un fichier tel quel ;
- lire l’historique, ouvrir un commit avec son diff (aussi sous forme de fichier `.diff` ou `.patch`) et comparer deux
  commits ;
- voir ses versions, télécharger une archive, et le cloner en HTTP
  (`git clone https://<adresse du NAS>:9951/git/<propriétaire>/<dépôt>.git`) ;
- trouver les dépôts publics sous **Explorer** et sur le profil de leur propriétaire.

Un dépôt privé reste caché : son adresse répond « introuvable ». Tant que l’interrupteur est activé, Forgejo ne liste
en outre aucun compte (l’onglet **Utilisateurs** d’Explorer et la recherche d’utilisateurs sont désactivés, y compris
pour les utilisateurs connectés), si bien qu’un visiteur ne peut pas collecter les noms de comptes. Pousser, les
tickets, les demandes d’ajout et les paramètres demandent toujours un compte. Interrupteur désactivé, chaque page
demande d’abord au visiteur de se connecter.

Pour partager un dépôt, rendez-le public dans Forgejo, activez **Navigation publique** et envoyez l’adresse. Sur un
nom d’hôte Git, l’adresse est `https://<nom d’hôte Git>/<propriétaire>/<dépôt>`.

### Nouveau projet

**Nouveau projet** est une page qui mène un projet, étape par étape, de rien à une première version compilée et
publiée. Elle s’ouvre dans son propre onglet depuis la palette de commandes (Ctrl+Maj+P, **Nouveau projet**), le haut
de la barre latérale de l’Éditeur, l’en-tête de la barre latérale Git, la page Git et la page Projets ; un exemple
de **Projets de l’éditeur** sur la page Exemples a **Créer un projet à partir de cet exemple**. Elle demande le rôle
développeur, les modules Git et Éditeur, et votre compte dans Forgejo (la page Git montre sa synchronisation). Tant
que Git ou l’Éditeur est désactivé, la page le dit au-dessus des étapes, et un admin les active là d’un seul bouton.

1. **Modèle.** Les modèles sont groupés : Programmes (C, C++ avec CMake, Go, Rust, Node, Python, Java), Paquets pour QTS
   (un paquet QPKG, une app App Builder, un paquet depuis les sources), Noyau (un module noyau, le pilote USB-série
   ch341), Sites web (un site statique : des pages dans `src/`, un Makefile qui compile `public/`, une vérification
   des liens en `make test`) et Exemples. Chaque carte dit ce qu’une compilation produit et signale ce qui manque
   encore à ce NAS (QDK, un environnement de compilation, un module désactivé), avec un lien vers la page qui
   l’ajoute.
2. **Nom et visibilité.** Le nom nomme le dépôt et le dossier `projects/<nom>` ; la page le vérifie pendant la
   saisie. Puis le propriétaire (votre compte, ou une organisation où Forgejo vous laisse créer des dépôts), privé ou
   public, et une description si vous le souhaitez. Pour une app App Builder, le préréglage dont elle part et la
   cible.
3. **Service**, pour un paquet QPKG et un paquet depuis les sources : la façon dont le paquet démarre et s'arrête,
   construite à partir de blocs. Choisissez un préréglage (**Outil en ligne de commande** lie le programme dans
   `/usr/bin` ; **Service en arrière-plan** ajoute un compte, un dossier de données sur le volume, le programme lancé
   sous ce compte, une attente de son port et un arrêt avec délai ; **Application web** ajoute le bouton Ouvrir d'App
   Center ; **Vide** n'a que les verbes), puis modifiez ses blocs. La carte de chaque bloc montre les lignes qu'il
   écrit dans le script pendant que vous tapez, et une valeur refusée est signalée sous son champ. Le démarrage
   n'active jamais l'Enable d'App Center, et l'arrêt n'y touche jamais. La vue Projet modifie les blocs plus tard.
4. **Compilation.** L’environnement où le projet se compile, gardé pour le projet (la vue Projet le change ensuite), ou
   aucun, et bvsd en choisit un à chaque compilation. L’étape propose d’abord celui que bvsd choisirait lui-même, où
   compilent `bvs qbuild` et la CI : le Ubuntu 24.04 builder, sinon le Debian 12 builder, sinon un autre environnement
   Debian ou Ubuntu. Quand le Makefile du modèle donne à chaque app son environnement sur une ligne ENV, l’étape le dit
   et n’en demande aucun, et le projet n’en garde aucun. Pour un paquet depuis les sources, l’étape dit aussi que
   l’adresse de la source et le sha256 de la recette de départ sont provisoires : la première compilation échoue tant
   que la recette ne donne pas la vraie source de votre programme et le sha256 de ce fichier. La page montre le
   workflow que la CI lance à chaque push. **Compiler juste après la création** reste activé tant que vous ne le
   désactivez pas ; pour un paquet depuis les sources, il est d’abord désactivé, et vous pouvez l’activer. S’il n’y a
   aucun environnement Debian ou Ubuntu pour le NAS, l’étape le dit : un admin y crée l’environnement de compilation
   avec **Créer cet environnement**, puis l’étape le propose ; sinon, désactivez **Compiler juste après la création**
   et compilez le projet plus tard.
5. **Publication**, pour un paquet QPKG, une app App Builder ou un site web : **Déploiement de test sur ce NAS** (admin,
   paquets signés seulement ; pour une app, **Activer sur ce NAS**), **Publier dans le dépôt App Center de ce NAS** et
   **Publier sur mon serveur web** (admin) vers l’une des [cibles web](#votre-propre-serveur-web-cibles-web) ; **Ajouter
   une destination** en enregistre une nouvelle. Chacune est facultative. Le choix de publication devient aussi
   l’interrupteur **Publier dans le dépôt App Center** du projet (vue Projet, Réglages) : tant qu’il est activé, la CI
   du projet y publie à chaque exécution.
6. **Vérification** liste chaque réponse et les étapes qui vont s’exécuter. **Créer le projet** les envoie.
7. **Progression** suit la tâche de chaque étape : le dépôt et son premier commit, la première compilation, puis le
   déploiement de test, la publication dans App Center et la publication web, l’une après l’autre. Une ligne montre
   son état et la fin de son journal ; un clic montre le journal. Une étape en échec dit pourquoi et propose
   **Réessayer**, et les étapes suivantes attendent. À la fin, **Ouvrir le projet**, **Ouvrir dans l’éditeur** et
   **Ouvrir dans Forgejo**. Recharger la page ou revenir à son onglet relit les tâches et n’envoie aucune étape deux
   fois. Quitter la page arrête la chaîne ; la vue Projet a les mêmes étapes.

Quand bvsd refuse le projet (un nom pris par un dossier, une app ou un dépôt, un propriétaire où vous ne pouvez pas
créer), la page revient à l’étape qui porte cette réponse, montre la raison sous son champ et garde toutes les autres
réponses. Un module désactivé (un admin l’active depuis la page), l’absence de compte Forgejo et l’absence
d’environnement Debian ou Ubuntu pour une app App Builder sont expliqués dans Vérification.

Ce que fait la première tâche :

- Elle crée le dépôt en votre nom, privé sauf si vous avez choisi public.
- Elle crée le dossier `projects/<nom>` du dossier de données avec le premier commit : chaque fichier du modèle et le
  workflow `.forgejo/workflows/build.yml`, sur `main`. Le commit est le vôtre (votre nom et votre adresse sont réglés
  comme auteur dans le dossier), et les scripts du modèle y sont exécutables. Elle pousse ce commit vers le dépôt en
  votre nom. `origin` est l’adresse HTTPS du dépôt, votre prochain push y va donc. Le commit contient aussi la
  [compétence pour les assistants IA](#coder-avec-un-assistant-dia), `.claude/skills/qnap-dev/` et `AGENTS.md`. Le
  dossier est dans le dossier partagé de l’éditeur : l’éditeur de chaque développeur ouvre `projects/`, que le dépôt soit
  privé ou public.
- Elle garde l’environnement choisi pour le projet, sauf si le Makefile du modèle donne à chaque app son environnement.
  Pour une app App Builder, elle crée l’app du nom du projet avec les recettes du préréglage et la rattache au projet,
  si bien que l’app peut aussi compiler les recettes que vous ajoutez au dossier `recipes/` du projet. Elle garde aussi
  votre choix de publication comme interrupteur Publier dans le dépôt App Center du projet, avant que la première
  exécution du workflow le lise.

Le workflow s’exécute sur le runner du NAS à chaque push une fois le module CI activé, dans l’environnement que nomme le
workflow. Le workflow QPKG publie le paquet dans le [dépôt de paquets](#dépôt-de-paquets), qui n’accepte que des paquets
signés. Si le premier commit ou son push échoue après la création du dépôt, la tâche supprime le dossier, supprime à
nouveau le dépôt et dit pourquoi. Tant que l’interrupteur **Publier dans le dépôt App Center** du projet est désactivé,
`bvs repo publish` dans le workflow affiche un avis au lieu de publier, et l’exécution continue.

### La vue Projet

Chaque dossier de `projects/` dans le dossier de données a sa propre page : un projet de l’assistant, la copie d’un
exemple de la catégorie Projets de l’éditeur ou un dépôt que vous y avez cloné. Elle s’ouvre depuis la branche **Projets** de la barre latérale de
l’Éditeur, depuis la page **Projets**, avec **Ouvrir le projet** à la fin de Nouveau projet, et depuis la palette de
commandes (**Ouvrir le projet …**, ou Ctrl+P et son nom). Elle demande le rôle développeur. Le menu d’un projet sur la
page Projets a aussi Compiler, Recompiler, Publier et la publication vers les cibles web. Le même menu active ou
désactive la publication par la CI dans App Center.

La page lit le dossier et n’y change rien d’elle-même. Elle déduit le type du projet de ses fichiers : une app App
Builder du nom du dossier, un `qpkg.cfg` en haut (un paquet QPKG), un `Makefile` en haut, ou rien de cela, quand le
workflow CI du projet le compile. Quand une app App Builder porte le nom du dossier, un projet avec un `qpkg.cfg` en
haut, ou avec un Makefile et un `qpkg.cfg` dans `qpkg/`, garde sa propre compilation, et Déploiement de test et Publier
prennent toujours son QPKG.

La bande du haut contient les étapes. Une étape qui ne peut pas s’exécuter garde son bouton, désactivé, et dit pourquoi
à côté et sous la bande. Une étape que le projet ne peut pas prendre du tout quitte la bande, et une phrase en dessous
dit ce que fait le projet et où va le résultat de la compilation : le dossier du projet pour un programme, la page
Noyaux pour un module noyau, la page de chaque app pour un Makefile qui ne compile que des apps App Builder.

- **Compiler** s’exécute comme une tâche dans le panneau inférieur. Un paquet QPKG se compile avec `qbuild` de QDK, un
  projet Makefile avec `make`, tous deux dans l’environnement de compilation du projet. Le journal nomme chaque `.qpkg`
  que la compilation a écrit dans `build/` et dit s’il porte une signature de code. Une app App Builder se compile comme
  sur sa propre page. Quand la cible par défaut d’un Makefile n’exécute que des lignes `./bvsapp.sh create` et
  `./bvsapp.sh build`, Compiler fait la même chose par l’App Builder : il crée chaque app d’une ligne `create` qui
  n’existe pas encore, puis compile les apps l’une après l’autre. Une ligne `create` s’écrit `./bvsapp.sh create NOM
  FICHIER-DE-RECETTES [ENV] [LINK] [TARGET] [OTHERS]`. Un tel Makefile nomme sa cible par défaut et ses prérequis dans
  `.PHONY` et ne contient que des affectations simples, chaque variable définie une fois, et des règles simples ; un
  Makefile avec autre chose (une condition, un include) se compile avec make. TARGET vaut `native`, `aarch64` ou
  `armv7`, si bien qu’une seule compilation peut faire une app pour ce NAS et une pour ARM 64 bits. OTHERS dit ce qu’une
  app statique fait des recettes qui ne peuvent pas se lier en statique (`refuse` ou `bundle`). Une app créée ainsi
  prend aussi les recettes propres au projet dans `recipes/`, sauf si le nom du dossier n’est pas un nom de projet (un
  nom avec une espace, par exemple). Quand une app de ce nom existe déjà avec une autre cible, ou avec un autre projet
  alors que le dossier a ses propres recettes, Compiler reste désactivé et dit comment corriger. Avant la première
  compilation, l’étape nomme les apps qu’elle compile. Un projet Makefile dont le paquet est dans `qpkg/` fait le
  fichier de paquet avec `make qpkg` dans le terminal de l’éditeur ; tant qu’il manque, Déploiement de test, Publier et
  Publier sur le web disent « Demande un fichier de paquet ».
- **Recompiler** compile depuis zéro : `make clean`, puis `make` ; pour un paquet QPKG, les anciens `.qpkg` de `build/`
  partent d’abord. Un Makefile sans cible `clean` ne peut pas recompiler, et la page le dit. Une app App Builder n’a pas
  Recompiler, pas plus qu’un Makefile qui ne fait que créer et compiler des apps App Builder.
- **Exporter en QPKG** (admin) existe pour une app App Builder, comme sur la page de l’app. Publier attend alors un
  export, comme sur la page de l’app, et **Ouvrir l’app** dans l’en-tête ouvre cette page.
- **Déploiement de test** est pour les admins. Il installe le `.qpkg` le plus récent sur ce NAS avec `qpkg_cli`, comme
  App Center installe un fichier, et la tâche attend la confirmation d’App Center. Le paquet reste installé jusqu’à ce
  que vous le supprimiez dans App Center. Un paquet sans signature de code est refusé : le déploiement de test
  n’installe que des paquets signés et ne saute jamais le contrôle de signature d’App Center (dans App Center
  lui-même, le propriétaire d’un NAS peut autoriser les apps sans signature ; cette page ne le fait pas). Signez-le
  avec un certificat en fichiers (cas 1 de [Signer le code d’un QPKG](#signer-le-code-dun-qpkg)). Le paquet de Bucket
  & Code Server IDE lui-même est refusé, car son installation remplacerait le paquet en cours. Pour une app App
  Builder, le déploiement de test active sa compilation sur ce NAS.
- **Publier** met le `.qpkg` le plus récent dans le [dépôt de paquets](#dépôt-de-paquets), selon ses règles : la
  publication d’un admin devient la version listée, celle d’un développeur attend un admin, et un paquet sans signature
  est refusé sauf si le dépôt accepte les paquets non signés. Une ligne en dessous dit si la CI du projet publie aussi
  dans le dépôt App Center ; ce bouton publie dans tous les cas.
- **Publier sur le web** (admin) copie le `.qpkg` le plus récent, ou le dossier du site `public/` (sinon `dist/`), vers
  une [cible web](#votre-propre-serveur-web-cibles-web) et le relit par l’adresse publique de la cible. Pour un site web
  créé par l’assistant, l’étape attend la première compilation, qui écrit `public/`.

Un projet Makefile choisit une cible à côté de **Compiler** (la cible par défaut de make, sa première, sauf si vous en
choisissez une autre) ; un projet CMake se compile, ou se compile et lance ses tests. Un projet de module noyau se
compile dans l’espace de travail de l’atelier noyau pour le noyau en cours, et cette compilation revient à un admin. Une
étape en cours a **Annuler**, et la tâche s’arrête le plus souvent aussitôt. **Lancer le workflow** lance à la main le
workflow CI du projet quand il a un déclencheur `workflow_dispatch` ; sinon le bouton est désactivé et la page dit
pourquoi. Le workflow de chaque modèle a ce déclencheur. **Installé sur ce NAS** liste les paquets que les déploiements
de test du projet ont installés, avec **Supprimer de ce NAS** pour un admin. **Supprimer le projet**, dans le menu à
côté des boutons de l’en-tête ou dans le menu du projet sur la page Projets, est pour un admin ou pour le développeur
dont le compte Forgejo contient le dépôt. Vous tapez d’abord le nom du projet ; le dossier part, et le dépôt Forgejo,
une app App Builder de ce nom et les paquets installés restent. La page donne ensuite le lien vers le dépôt.

**Réglages** contient ce que bvsd garde pour le projet hors de son dossier. Pour un projet Makefile, CMake ou QPKG, il
montre l’environnement où s’exécutent Compiler et Recompiler, et le change. Pour un Makefile qui ne fait que créer et
compiler des apps App Builder, il liste chaque app avec l’environnement que nomme sa ligne du Makefile. Un projet qui
fait un paquet, ou dont le workflow lance `bvs repo publish`, y a l’interrupteur **Publier dans le dépôt App Center** :
activé, `bvs repo publish` dans la CI du projet ajoute ses paquets au dépôt ; désactivé, l’exécution CI saute cette
étape avec un avis. Un projet sans choix gardé (la copie d’un exemple, un clone) l’a activé. **Git** montre la
branche, son avance ou son retard sur le distant tels que le dernier fetch les a laissés (la page ne fait jamais de
fetch), le dernier commit et les chemins modifiés. **Fichiers** liste un dossier à la fois ; choisissez un fichier pour
le lire, un `.qpkg` se télécharge. L’édition reste dans l’éditeur, et le dossier `.git` est laissé de côté.

**Recettes**, pour un projet doté de son propre dossier `recipes/`, liste les recettes que prennent ses apps : chacune
avec sa version, la version de la bibliothèque qu’elle remplace quand elle porte le nom d’une recette de la
bibliothèque, et en dessous ce que la vérification des recettes a trouvé, avec le fichier et la ligne. Une recette que
bvsd laisse de côté a une ligne avec la raison ; au-delà de trois laissées de côté de la même façon, une ligne compte
le reste. Ce que la vérification a trouvé sur le dossier `recipes` dans son ensemble a sa propre ligne. Un clic ouvre
le dossier de la recette dans l’éditeur ; le menu copie aussi la commande qui lance la même vérification dans un
terminal du NAS : `bvs recipe lint` avec le dossier du projet. La carte apparaît aussi quand le `recipes` du projet
est un lien, un fichier, un dossier que bvsd ne peut pas lire ou un dossier qui ne contient que des liens, et dit
pourquoi aucune recette n’en est lue.

**Service et paquet** (un paquet QPKG, ou un projet Makefile dont le paquet est dans `qpkg/`, comme un paquet depuis
les sources) contient le script de démarrage et d'arrêt du paquet sous forme de blocs, les champs de son `qpkg.cfg`
(**Paquet**) et les fichiers écrits à partir d'eux (**Script**). **Enregistrer** montre les différences de chaque
fichier, puis en fait un commit dans le dépôt du projet ; si le projet a changé entre-temps, planifiez à nouveau.
**Modifier à la main** désactive les blocs, et vous modifiez le script dans l'éditeur ; **Rattacher les blocs** montre
ce qu'une nouvelle écriture remplacerait. **Mettre à jour la bibliothèque** apporte au paquet le `bvs-qpkg.sh` de ce
Bucket & Code Server IDE, avec ses différences. Un projet créé avant les blocs propose **Utiliser des blocs pour ce script**.

**Versions** liste ce vers quoi le projet peut passer, et **Passer à cette version** le fait :

| Version | Ce que fait le passage |
|---|---|
| Un `.qpkg` dans `build/`, ou un export de l’App Builder | un déploiement de test de ce fichier (admin) |
| La compilation de l’App Builder | l’active (admin) ; l’App Builder garde une compilation |
| Une version du dépôt de paquets | en fait la version listée (admin) |
| Un tag git | sort le tag dans le dossier |

Un clic choisit une version ; son menu la déploie aussi en test, la publie, la publie vers une cible web ou
télécharge un `.qpkg`. Un tag est sorti en HEAD détaché, et **Revenir à main** dans la carte Git revient à la
branche. Le passage est refusé tant que le dossier a des modifications non commitées ; le message les liste, et vous
les commitez ou les mettez de côté dans l’éditeur d’abord. Cochez deux versions et **Comparer les deux** liste les
fichiers que l’une a et l’autre n’a pas, et les fichiers modifiés. Deux tags, ou deux paquets, sont comparés par
contenu ; un paquet et un commit seulement par taille. Sur la page **Paquets**, un admin installe aussi une version
du dépôt sur ce NAS (**Installer sur ce NAS**), avec les contrôles du déploiement de test.

### Votre propre serveur web (cibles web)

Une cible web est l’endroit où un admin publie un paquet ou un site web sur son propre serveur web : un dossier de ce
NAS (le partage Web de QTS, que sert le Web Server de QTS, ou un autre dossier partagé) ou un distant rclone (SFTP,
FTP, WebDAV, S3, configuré dans le `rclone.conf` des sauvegardes), avec l’adresse publique qui le sert. Elles sont sur
la page **Publication**, sous **Cibles web** : **Ajouter une destination**, **Modifier**, **Supprimer** et **Tester**, qui y
copie un petit fichier, le récupère par l’adresse publique et le supprime ensuite.

Une cible dossier est un dossier à elle dans un partage (`/share/Web/monsite`, jamais un partage entier ni un
volume), hors du dossier de données, car une publication de site retire de la cible ce que le site ne contient plus.
L’adresse commence par `http://` ou `https://` et ne contient ni utilisateur ni mot de passe.

Chaque publication relit le fichier par l’adresse publique : le `.qpkg` d’un paquet avec son `.sha256`, comparé par
sha256, ou l’`index.html` d’un site, comparé octet par octet. Quand l’adresse ne sert pas ce qui a été copié, la tâche
échoue avec `web_check:` et les deux valeurs.

## Runners CI

Le module CI fait tourner Forgejo Actions : un fichier de workflow dans `.forgejo/workflows/` d’un dépôt s’exécute sur
un runner quand quelque chose est poussé. Il a besoin du module Git. Un admin l’active sous **Modules**, **CI**,
**Activer**. La tâche enregistre le runner propre du NAS auprès de Forgejo, redémarre Forgejo avec Actions activé et
démarre le runner ; elle se termine quand le runner a atteint Forgejo.

### Le runner du NAS

- **Ses étiquettes** sont `bucketvs`, `qnap-x86_64` (ou `qnap-aarch64`) et `self-hosted`, plus les étiquettes
  supplémentaires qu’un admin définit sur la page CI. Une tâche dont le `runs-on` nomme l’une d’elles s’exécute ici,
  par exemple `runs-on: bucketvs`.
- **Il exécute chaque tâche sur le NAS lui-même, en tant qu’admin**, pas dans un conteneur. Une tâche dispose des
  outils du paquet (git, curl, make et les autres), de Node pour les actions JavaScript comme `actions/checkout`, et
  de `bvs env run <env> -- <command>` pour une compilation dans un environnement. Quiconque peut pousser un workflow
  dans un dépôt peut donc exécuter des commandes en tant qu’admin sur le NAS. Un member ou un viewer ne peut ni
  créer, ni forker, ni importer de dépôts, ni créer d’organisations dans Forgejo ; les developers et les admins le
  peuvent. Un viewer ne pousse jamais. Tant que la CI est activée, le push d’un member vers un dépôt dont les Actions
  sont activées est refusé avec la raison, tout comme un push avec une clé de déploiement. Le push d’un member, ou
  avec une clé de déploiement, vers un dépôt qu’un compte de rôle inférieur à developer possède ou administre est
  refusé lui aussi, même quand la CI est désactivée : un tel compte pourrait activer lui-même les Actions. bvsd
  désactive les Actions en moins d’une minute dans chaque dépôt qu’un member ou un viewer possède ou dans lequel un
  tel compte peut écrire, et avant le démarrage du runner quand la CI est activée.
- **Ce qui reste** est l’acte propre d’un developer ou d’un admin : activer les Actions, ou lancer un workflow, dans
  un dépôt où un member a écrit plus tôt, ou donner à un tel compte l’accès admin à un dépôt où il a écrit plus tôt.
  Un developer qui fusionne ou compile les commits d’un member les exécute lui aussi en tant qu’admin.
- **Tâches simultanées** vaut `ci.capacity` (1 par défaut). Une tâche qu’aucun runner en ligne ne peut prendre attend
  dans Forgejo.
- **Les dépôts créés avant l’activation de CI** ont les Actions de Forgejo désactivées, donc un workflow poussé ne
  déclenche rien : activez-les dans les **Paramètres** du dépôt, **Unités** (Actions). Les dépôts créés pendant que CI
  est activé les ont.
- **Les actions** comme `actions/checkout@v4` sont téléchargées depuis `https://data.forgejo.org`, donc le NAS a
  besoin d’un accès Internet pour les workflows qui les utilisent. Le runner n’a pas de cache : `actions/cache` ne
  trouve rien et la tâche continue.
- **Les artefacts** (`actions/upload-artifact`) sont téléversés vers l’adresse propre de Forgejo (celle de la page
  Git), donc le NAS doit atteindre cette adresse, et faire confiance à son certificat quand elle est en HTTPS.
- **L’état du runner** est dans `<données>/runner` ; son journal est **runner** sous **Journaux**. Désactiver le
  module arrête le runner et désactive Actions dans Forgejo.

### Runners externes

Une machine de compilation ailleurs (un serveur, une VM) peut aussi prendre des tâches de ce Forgejo. Sur la page CI,
sous **Enregistrer un runner externe**, un admin lui donne un nom et obtient les commandes pour Linux x86_64 et
aarch64. Elles téléchargent forgejo-runner 13.2.0 depuis code.forgejo.org, vérifient son sha256, gardent le jeton du
runner dans `runner-token` (lisible par son propriétaire seulement) et démarrent le runner avec l’étiquette
`linux-amd64` (ou `linux-arm64`). Le runner apparaît aussitôt dans la liste, hors ligne jusqu’à ce que les commandes
s’exécutent. Le jeton n’est affiché qu’à ce moment-là. Un tel runner exécute ses tâches sur sa propre machine, en tant
que l’utilisateur qui l’a démarré, et atteint Forgejo à son adresse publique.

## SVN et Bucket

Le module SVN et Bucket sert les dépôts Subversion à `/svn/<nom>` et les buckets à `/bucket/<nom>`. Un bucket est un
lecteur réseau (WebDAV) dans lequel chaque fichier enregistré devient une révision, si bien que toute version
antérieure d’un fichier peut être lue ou remise en place. Un admin active le module sous **Modules** ; la page **SVN &
Bucket** crée, liste et supprime ensuite les deux types.

- **Les comptes** sont ceux du panneau. Les autorisations se règlent par dépôt : lecture et écriture, lecture, ou aucun,
  pour chaque compte, plus une autorisation par défaut pour chaque compte non listé. Les admins lisent et écrivent toujours ;
  les members et les developers écrivent là où ils ont lecture et écriture ; les viewers lisent au plus. Un nouveau
  dépôt ne donne aucun accès à personne d’autre que les admins tant qu’un admin ne l’a pas défini (l’icône des
  personnes sur sa ligne, **Autorisations pour `<name>`**).
  En dessous d’admin, la page montre à chacun son propre accès à chaque dépôt et chaque bucket.
- **Les mauvais mots de passe** comptent comme à la connexion du panneau : 5 mauvais mots de passe en 15 minutes
  bloquent ce compte depuis cette adresse, et l’adresse elle-même, pendant 15 minutes.
- **Authentification à deux facteurs.** Un compte avec l’authentification à deux facteurs n’utilise pas son mot de passe pour SVN ou
  WebDAV. Il crée un **mot de passe d’application** sur la page de son compte (**Mot de passe d’application pour
  WebDAV et SVN**) : 25 caractères aléatoires, affichés une fois. Un nouveau remplace l’ancien ; un changement de mot
  de passe ou la désactivation de l’authentification à deux facteurs le supprime.
- **Supprimer** un dépôt le déplace dans `<données>/trash`, où la tâche `trash` le supprime après 7 jours.

### Dépôts SVN

```bash
svn checkout "https://<adresse du NAS>:9951/svn/<nom>"
svn commit -m "…"
```

La page affiche la commande de checkout avec l’adresse du dépôt. Derrière un proxy, l’adresse suit l’URL publique, et
avec un nom d’hôte SVN elle est `/svn/<nom>` sur ce nom. L’**Historique** d’un dépôt liste ses révisions ; un fichier
peut être lu à n’importe quelle révision, comparé avec une autre, et remis en place comme nouvelle révision
(**Restaurer cette version**, pour les comptes en lecture et écriture).

### Buckets

- **Enregistrer, c’est committer.** Un client qui enregistre un fichier crée une révision avec le nom du compte et
  l’heure ; la révision est marquée comme faite via WebDAV. Beaucoup de clients écrivent d’abord un fichier vide puis
  les données, donc un enregistrement peut créer deux révisions.
- **Fichiers macOS.** Un bucket créé avec **Refuser les fichiers ._\* et .DS_Store de macOS** (par défaut) ne stocke
  pas les fichiers `._*` et `.DS_Store` que le Finder écrit à côté de ce qu’il copie. Le client ne reçoit aucune
  erreur, mais le fichier ne devient jamais une révision. Supprimer un tel fichier est permis.
- **Revenir en arrière.** L’historique du bucket affiche les versions de chaque fichier ; **Restaurer cette version**
  enregistre l’ancien contenu comme nouvelle révision, donc rien n’est perdu.

### Clients WebDAV et leurs limites

| Client | Comment se connecter | Remarques |
|---|---|---|
| Finder de macOS | **Aller**, **Se connecter au serveur**, `https://<adresse du NAS>:9951/bucket/<nom>` | le Finder verrouille un fichier pendant qu’il l’écrit ; le bucket répond aux verrous WebDAV |
| rclone | un remote `webdav` avec `vendor = other` et l’adresse du bucket | adapté aux copies par script |
| davfs2 (Linux) | `mount -t davfs https://<adresse du NAS>:9951/bucket/<nom> /mnt/<dossier>` | le nom d’utilisateur du panneau et le mot de passe ou le mot de passe d’application |
| Cyberduck, WinSCP | une connexion WebDAV (HTTPS) au port 9951, chemin `/bucket/<nom>` | |
| curl | `curl -T fichier -u <utilisateur> https://<adresse du NAS>:9951/bucket/<nom>/fichier` | un envoi, une révision (ou deux, voir plus haut) |
| Explorateur Windows | **Connecter un lecteur réseau** | le WebClient de Windows n’envoie un mot de passe qu’en HTTPS avec un certificat auquel Windows fait confiance : placez le bucket derrière un proxy avec un vrai certificat ([Derrière un proxy inverse](#derrière-un-proxy-inverse-plesk)) ; le certificat auto-signé ne suffit pas |

- Les gros fichiers ne sont pas coupés : nginx ne fixe aucune limite de taille du corps sur `/bucket/` et transmet
  l’envoi au fur et à mesure qu’il arrive.
- Un fichier que le bucket refuse de stocker (un fichier macOS ci-dessus) reçoit quand même `201` du serveur WebDAV,
  donc un client n’affiche aucune erreur ; le fichier n’est pas dans le bucket.

### Montrer un dépôt ou un bucket aux personnes sans compte

Un admin peut rendre public un dépôt SVN ou un bucket avec l’interrupteur de sa ligne sur la page **SVN & Bucket**
(**Sur le web**). Toute personne qui a l’adresse peut alors le lire dans un navigateur, sans compte :

| Adresse | Ce qu’elle affiche |
|---|---|
| `https://<adresse du NAS>:9951/svn-web/<nom>/` | un dépôt SVN |
| `https://<adresse du NAS>:9951/bucket-web/<nom>/` | un bucket |

Le lien **Voir sur le web** de la ligne ouvre cette page. Elle liste les fichiers et dossiers à la révision la plus
récente ou à une plus ancienne, affiche un fichier texte avec les numéros de ligne (jusqu’à 1 Mio) ou une image, et
propose chaque fichier au téléchargement. L’**Historique** liste les révisions avec leurs auteurs, messages et
fichiers modifiés. La page d’une révision affiche le diff de chaque fichier qu’elle a modifié (jusqu’à 20 fichiers
d’au plus 1 Mio ; les autres sont des liens) ; n’importe quel fichier peut être comparé entre deux révisions.

- **Lecture seule.** Les pages ne peuvent rien modifier. Les clients WebDAV et `svn` demandent toujours un compte,
  même pour un dépôt public.
- **Seulement ce qui est public.** Un dépôt qui n’est pas public et un nom qui n’existe pas répondent tous deux
  « There is no public repository at this address », donc les pages ne révèlent pas quels noms existent.
- **Limites.** Une adresse peut ouvrir 60 pages d’un coup puis 2 par seconde, et télécharger 2 fichiers à la fois ;
  au-delà, la page demande de réessayer dans un instant. Un diff est coupé à 2 Mio.
- **Les moteurs de recherche** sont priés de ne pas indexer les pages. Les pages sont en anglais. Elles ont l’apparence
  du panneau, sombre ou claire selon le réglage du navigateur du visiteur.
- Derrière un proxy ou avec un nom d’hôte SVN, l’adresse suit celle du dépôt lui-même (l’URL publique, ou le nom
  d’hôte SVN avec les mêmes chemins).

## Éditeur et outils d’IA

Le module Éditeur fait tourner VS Code dans le navigateur (code-server) pour chaque compte developer et admin. Un admin
l’active sous **Modules**. Chaque compte reçoit sa propre instance, démarrée quand le compte ouvre l’éditeur et arrêtée
après un temps sans trafic.

**Le terminal de l’éditeur est un shell root sur le NAS.** Sous QTS, les processus du paquet tournent en tant qu’admin
(uid 0), sauf Forgejo et httpd. Ne donnez le rôle developer qu’aux personnes à qui vous donneriez le mot de passe
admin du NAS.

### Ouvrir l’éditeur

- **Ouvrir l’éditeur** sur la page Réglages de l’éditeur (ou `/ide/` sur l’adresse du panneau, ou le nom d’hôte de
  l’Éditeur) ouvre l’espace de travail dans un nouvel onglet. La première requête démarre l’instance ; la connexion du
  panneau passe d’abord quand il n’y a pas de session.
- Par le nom d’hôte principal publié, tant que l’éditeur n’y est pas publié, la page Réglages de l’éditeur, l’onglet Éditeur,
  la Vue d’ensemble et **Ouvrir un shell** indiquent que l’éditeur ne s’ouvre que depuis le réseau local et donnent un
  lien vers son adresse sur le réseau local (`https://<adresse du NAS>:<port HTTPS>/ide/`) ; pour un admin, ils mènent
  aussi à l’interrupteur de la page **Publication**.
- L’éditeur ouvre `<données>/projects`, où les exemples de la catégorie Projets de l’éditeur placent leurs projets.
  Les réglages, les extensions et l’historique du terminal du compte sont gardés dans `<données>/ide/<compte>` et son
  dossier personnel dans `<données>/home/<compte>`.
- Le terminal a les outils du paquet en premier dans le `PATH` : git, svn, node, npm, rsync, curl, bash et les autres.
  `bvs env shell <nom>` entre dans un environnement des Toolchains et `eval "$(bvs use <id>)"` prépare une toolchain
  ou un runtime. **Ouvrir un shell** sur la page Toolchains ouvre l’éditeur avec un terminal déjà dans cet environnement.
- L’activité **Éditeur**, et la palette de commandes, ouvrent l’éditeur avec un shell dans un environnement ou avec le
  dossier d’une app. L’éditeur s’affiche dans l’onglet Éditeur du panneau ; sur un téléphone, il s’ouvre dans sa propre
  fenêtre de navigateur. Seules les pages de l’adresse du panneau lui-même peuvent afficher l’éditeur en leur sein : le
  paquet l’indique aux navigateurs dans chaque réponse de l’éditeur, donc un autre site ne peut pas placer le terminal
  root de l’éditeur sous une page à lui.
- **Délai d’inactivité** (30 minutes par défaut) arrête une instance sans trafic du navigateur, mais jamais tant qu’un
  programme lancé depuis ses terminaux, un agent IA lancé par une extension (le `claude` de Claude Code for VS Code,
  Codex, Gemini CLI) ou ce qui a été laissé en marche depuis ceux-ci tourne encore. 0 désactive l’arrêt pour
  inactivité. Un compte peut arrêter sa propre instance sur la page Réglages de l’éditeur ; un admin peut arrêter n’importe laquelle.
- **Mémoire et processus**, sur la page Réglages de l’éditeur, montre ce que fait tourner une instance et la mémoire qu’elle occupe face à
  celle du NAS, marque ce qui la retient, termine un processus avec **Terminer** et dit pourquoi le dernier arrêt est
  venu et ce qu’il a terminé. Un agent IA qui attend à son invite retient lui aussi l’instance : terminez-le là quand
  il ne sert plus.
- La vue **Ports** de VS Code atteint un serveur de développement sur le NAS à `/ide/proxy/<port>/` ; les ports
  loopback propres au paquet y sont refusés.
- **New recipe**, dans le menu du dossier `recipes` d’un projet, ou d’un dossier de recette qu’il contient, dans
  l’**Explorer**, demande un nom et y écrit une nouvelle recette : `recipe.json` et `build.sh` à partir de la recette de
  départ du paquet. Dans la palette de commandes, c’est **Bucket: New recipe**, qui fonctionne tant qu’un fichier d’un
  projet doté d’un dossier `recipes` est ouvert. À chaque enregistrement d’un fichier d’une recette, l’éditeur vérifie les
  recettes du projet avec `bvs recipe lint` et liste ce qu’il trouve dans la vue **Problems**. Il signale le sha256 de
  la recette de départ jusqu’à ce que vous y mettiez le vrai sha256 de votre source.

L’éditeur ne répond qu’à ses propres pages. Un WebSocket, un formulaire ou la requête d’un script qu’une autre page envoie
avec la session du compte, depuis un autre site ou depuis un autre port du NAS, reçoit 403 et ne démarre aucune
instance ; une autre page n’atteint donc pas non plus un serveur de développement derrière la vue **Ports**.

### Extensions

- Les extensions s’installent dans la vue Extensions de l’éditeur (Ctrl+Maj+X). Elles viennent d’Open VSX : le navigateur
  cherche sur open-vsx.org et affiche la page et l’icône de chaque extension depuis openvsx.eclipsecontent.org, et le
  NAS télécharge l’extension elle-même depuis open-vsx.org. Le navigateur et le NAS doivent tous deux atteindre
  internet pour cela.
- La première extension de chaque éditeur demande « Do you trust the publisher … ? ». **Trust Publisher & Install**
  l’installe ; jusque-là l’extension affiche « Installing ».
- Chaque compte garde ses extensions dans `<données>/ide/<compte>/extensions`, et une mise à jour du paquet les garde.
- Le paquet apporte un ensemble d’extensions et de formateurs, que l’éditeur installe pour chaque compte à son premier
  démarrage, puis de nouveau après une mise à jour du paquet qui apporte un nouvel ensemble. L’éditeur colore et met en
  forme (**Format Document** dans la palette de commandes) Markdown, les scripts shell, C, C++, Rust, Go, Pascal,
  Python, Perl, PHP, JSON, YAML, TOML, la configuration nginx, CSS, HTML, JavaScript et TypeScript. ShellCheck signale
  les problèmes des scripts shell, les fichiers d’unité systemd ont la coloration et la complétion, le `.editorconfig`
  d’un projet fixe l’indentation, les codes de couleur comme `#ff8800` ou `rgb(0, 128, 0)` s’affichent dans leur
  couleur dans tout fichier, et **Hex Editor: Open Active File in Hex Editor** dans la palette de commandes montre les
  octets d’un fichier.
- Rust est mis en forme dès que le runtime Rust est installé sur la page Toolchains et que l’éditeur du compte a redémarré
  ; avant cela, Format Document dans un fichier Rust signale que la requête a échoué.
- Sur un NAS ARM (le paquet aarch64), Pascal, Perl et PHP sont colorés mais pas mis en forme : le paquet n’a pour lui
  aucune compilation de Free Pascal, de Perl ou de Mago.
- Une extension de l’ensemble que le compte supprime dans la vue Extensions reste supprimée, même après une mise à
  jour du paquet, et celle qu’il met à jour garde la version plus récente. Un formateur par défaut choisi pour un
  langage (**Format Document With…**, puis **Configure Default Formatter…**) reste le choix du compte.
- Tout fichier `.conf` s’ouvre comme configuration nginx. Le nom du langage dans la barre d’état passe un fichier à un
  autre langage.
- Ruff, qui met en forme Python, signale une fois qu’aucune extension d’environnement Python n’est installée. Il met
  en forme sans elle.
- Dans le terminal de l’éditeur, `code-server --install-extension <éditeur.nom>` installe aussi une extension depuis
  Open VSX.
- Certaines extensions dessinent leurs propres vues, comme le panneau de Claude Code for VS Code, l’éditeur
  hexadécimal et l’aperçu Markdown. Le navigateur n’affiche ces vues que s’il fait confiance au certificat du panneau.
  Avec le certificat autosigné accepté seulement par la page d’avertissement du navigateur, elles restent vides, et
  l’onglet Éditeur et la page Réglages de l’éditeur le disent. Chacune de ces voies y remédie :
  - faire confiance au certificat du NAS sur l’appareil : **Télécharger le certificat** sur la page Réseau ou dans cet
    avis, puis l’ajouter aux certificats de confiance du système ou du navigateur ([Le certificat](#le-certificat)) ;
  - un admin ajoute un certificat pour le nom du NAS sur la page Réseau ;
  - ouvrir l’éditeur par le nom d’hôte principal publié, une fois qu’un admin l’y publie.
- Claude Code for VS Code (éditeur Anthropic) s’installe depuis la vue Extensions, et son panneau demande comment
  se connecter à Anthropic. **Claude Code: Open in Terminal** dans la palette de commandes lance Claude Code dans un
  terminal, ce qui fonctionne avec n’importe quel certificat. Les fonctions de chat propres à VS Code sont désactivées
  au départ (`chat.disableAIFeatures`) ; ce réglage ne concerne pas cette extension.

### Claude Code, Gemini CLI et Codex CLI

Sous **Outils IA en ligne de commande** sur la page Réglages de l’éditeur, chaque compte installe, met à jour et supprime ces outils
pour lui-même :

| Outil | Installé avec | Commande |
|---|---|---|
| Claude Code | l’installeur d’Anthropic (`claude.ai/install.sh`), npm s’il échoue | `claude` |
| Gemini CLI | `npm install -g @google/gemini-cli` | `gemini` |
| Codex CLI | `npm install -g @openai/codex` | `codex` |

- Ils vont dans le dossier personnel du compte (`~/.local/bin`, `~/.npm-global/bin`), qui est dans le `PATH` du
  terminal ; un autre compte ne les voit pas.
- Chaque installation est une tâche avec son journal sur la page Tâches. La connexion à chaque outil (un compte ou une
  clé d’API) se fait dans l’outil lui-même, dans le terminal de l’éditeur.

### Coder avec un assistant d’IA

Chaque projet créé par **Nouveau projet** contient un skill pour les assistants IA : `.claude/skills/qnap-dev/` et un
`AGENTS.md` qui y renvoie. Le skill, en anglais, réunit les consignes pour tout ce qui concerne un NAS QNAP : ce qu’il
est dans le shell (une racine sur disque RAM, BusyBox, pas de systemd, pas de gestionnaire de paquets, `getcfg` et
`setcfg`), le stockage et les chemins, comment construire, signer et publier avec ce paquet (`bvs env run`, `bvs ldd`,
`bvs qbuild`, l’App Builder, l’atelier du noyau, le dépôt de paquets), les ports, les sauvegardes et ce qu’il ne doit
pas suggérer, comme `apt` sur le NAS ou autoriser les apps non signées dans App Center. Il dit clairement que la
signature avec un jeton matériel n’est ni prise en charge ni testée.

- Lancez l’assistant dans le dossier du projet (`cd <données>/projects/<nom>`), pas dans `projects/` : chaque outil
  cherche dans le dossier où il démarre.
- Claude Code charge la skill depuis `.claude/skills/` quand une tâche en a besoin. Codex CLI lit `AGENTS.md`. Gemini
  CLI lit `AGENTS.md` dès que le fichier `.gemini/settings.json` du projet contient
  `{"context": {"fileName": ["AGENTS.md", "GEMINI.md"]}}`.
- Le paquet garde la skill actuelle dans `/usr/bin/bucketvs/share/skills/qnap-dev`. Un projet créé avant que le paquet
  l’ait, ou un projet copié depuis la galerie d’exemples, la reçoit quand vous copiez ce dossier dans le
  `.claude/skills/` du projet.
- La skill cite la base de connaissances de QNAP Club Poland (forum.qnap.net.pl) pour ce que le NAS a et ce qui lui
  manque.
- La construction du paquet écrit aussi le skill sous la forme `qnap-skill-<version>.zip`, les mêmes fichiers, pour un
  usage hors de l’éditeur : décompressez-le dans `.claude/skills/` d’un projet ou dans `~/.claude/skills/`. Le texte du
  skill est sous licence CC BY 4.0 (son `LICENSE`) : vous pouvez le partager et l’adapter avec la mention "QNAP
  guidelines skill by QNAP Club Poland / qnaputilities.com, CC BY 4.0".

## Toolchains et environnements

La page Toolchains télécharge ce qu’il faut pour compiler des logiciels sur le NAS :

| Type | Ce que c’est | Exemples |
|---|---|---|
| Système de fichiers racine | un système Linux à décompresser dans un environnement | Debian 12 et 13, Ubuntu 22.04 et 24.04, Alpine 3.22 |
| Toolchain croisée | un compilateur qui compile pour un autre processeur | toolchains Bootlin pour aarch64, x86-64 et armv7, avec glibc ou musl |
| Runtime | un langage ou un outil de compilation | Python, Go, Node.js, Zig, Rust, CMake, Ninja, Java (JDK) |
| QNAP | les outils propres de QNAP | QDK 2.5.0, pour [`bvs qbuild`](#compiler-un-qpkg-avec-bvs-qbuild) et Exporter en QPKG |
| Sources du noyau | une arborescence du noyau Linux pour l’[atelier du noyau](#atelier-du-noyau) | Linux 6.6.129 de kernel.org ; la publication GPL du noyau de QNAP pour QTS 5.2.3 |

Les entrées pour un autre processeur sont masquées ; « Afficher les entrées pour d’autres processeurs » les liste,
mais elles ne peuvent pas être installées. Les toolchains Bootlin tournent sur x86_64, donc un NAS ARM n’a pas de
toolchain croisée.

### Le catalogue

Le paquet contient un catalogue intégré. Le catalogue distant signé à
`https://pub.qnaputilities.com/bucketvs/catalog.json` peut ajouter des entrées sans nouveau paquet. bvsd ne l’utilise
que si sa signature ed25519 correspond à la clé intégrée au paquet et qu’il n’est pas plus ancien que le catalogue en
usage. « Actualiser le catalogue » le récupère aussitôt, et la tâche cron intégrée « Actualisation du catalogue des
toolchains » le récupère chaque jour à 04:15.

### Installer et supprimer

Installer est l’affaire d’un admin, affichée avec sa progression sur la page Tâches :

1. L’archive est téléchargée dans `toolchains/cache/` dans le dossier de données. Un téléchargement interrompu reprend
   quand l’installation est relancée.
2. Son sha256 est vérifié par rapport au catalogue. Une différence supprime le fichier.
3. Une toolchain croisée ou un runtime est décompressé dans `toolchains/<kind>/<id>` et son archive supprimée. Un
   système de fichiers racine ou des sources du noyau restent sous forme d’archive ; chaque environnement ou espace de
   travail du noyau décompresse sa propre copie. QNAP publie sa publication GPL du noyau en deux parties : les deux
   sont téléchargées, chacune vérifiée par son propre sha256, puis réunies.

Un volume plein fait échouer l’installation avec « no space left on » et le point de montage du volume.

« Supprimer » efface une entrée installée. Les environnements créés à partir d’un système de fichiers racine sont des
copies et restent.

### Utiliser une toolchain ou un runtime

`bvs use <id>` affiche les lignes `export` (comme `PATH`) qui rendent utilisable dans un shell une toolchain croisée
ou un runtime installé :

```bash
eval "$(bvs use bootlin-aarch64-glibc-stable)"
```

La page Toolchains affiche les mêmes lignes sous « bvs use » pour chaque entrée installée.

### Environnements

Un environnement est un système Linux décompressé à partir d’un système de fichiers racine dans `envs/<nom>` dans la
dossier de données. Il est modifiable : `apt install build-essential` fonctionne dans un environnement Debian ou
Ubuntu, et ce que vous installez y reste.

- « Nouvel environnement » (admin) télécharge d’abord le système de fichiers racine s’il n’est pas installé, puis le
  décompresse.
- Tant qu’il n’y a aucun environnement, la liste propose l’Ubuntu 24.04 builder, celui où se compilent les exemples
  d’apps, d’un seul bouton (admin) : sa tâche s’affiche dans la liste, et l’environnement apparaît quand la tâche est
  finie. Rien n’est créé tout seul.
- Un nom a de 1 à 64 caractères : lettres minuscules, chiffres, `.`, `_` ou `-`, commençant par une lettre ou un
  chiffre.
- Un modèle de la galerie d’exemples installe aussi des paquets avec apt-get avant que l’environnement apparaisse :
  le builder Ubuntu 24.04 (le builder par défaut) et le builder Debian 12, tous deux complets, avec les outils de
  compilation de l’App Builder et patchelf ; Clang/LLVM (LLVM 18), Fortran (gfortran 13), Kernel builder, SDK .NET 8
  et Kotlin (Kotlin 1.3.31), tous sur Ubuntu 24.04. La glibc de chaque modèle est au plus 2.39, donc ce qu’on y
  compile fonctionne sur tous les NAS où le paquet fonctionne
  ([Sur quels modèles de NAS une app fonctionne](#sur-quels-modèles-de-nas-une-app-fonctionne)).
  Quand apt-get échoue, aucun environnement ne reste. Depuis un shell :
  `bvs env create <nom> --from <rootfs-id> --package <nom> --package <nom>`.
- « Cloner » fait une copie complète sous un nouveau nom, par exemple pour essayer une modification sans toucher à
  l’original.
- « Exporter » emballe l’environnement dans `toolchains/exports/<nom>-<heure>.tar.zst`.
- « Supprimer » le retire. bvsd refuse tant qu’un shell ou un processus tourne à l’intérieur. Les fichiers de
  `projects/` ne font pas partie de l’environnement et restent.

À l’intérieur d’un environnement :

- `projects/` du dossier de données est visible en tant que `/projects`, et un shell y démarre ;
- `HOME` est `/root` de l’environnement, et `LANG` est `C.UTF-8` ;
- `/proc`, un `/sys` en lecture seule et un petit `/dev` à lui sont montés, et `resolv.conf` et `hosts` du NAS y sont
  copiés ;
- les montages appartiennent à l’espace de noms de montage propre du shell et disparaissent quand il se termine, donc
  rien ne reste monté sur le NAS.

La même chose depuis un shell (les exemples appellent `bvs` par son nom, comme peuvent le faire une tâche cron et une
session SSH sur le NAS tant que le paquet tourne) :

```bash
bvs env list
bvs env create deb12 --from debian-12-x86_64
bvs env shell deb12
bvs env run deb12 -- make -C /projects/hello
```

`bvs env shell` et `bvs env run` démarrent dans le dossier d’où vous les lancez. Un dossier sous `projects/` de la
dossier de données est le même dossier sous `/projects` dans l’environnement. Tout autre dossier (le checkout d’une
tâche CI, un dossier d’un partage) est monté sur `/work` et la commande y démarre. Depuis `/`, elles démarrent dans
`/projects`.

### Compiler un QPKG avec bvs qbuild

`bvs qbuild` compile un QPKG à partir du dossier où vous le lancez (un dossier avec `qpkg.cfg`, comme celui que crée
le modèle QPKG de Nouveau projet). Il exécute le `qbuild` de QNAP dans un environnement Debian ou Ubuntu. qbuild
appelle des programmes système par leur chemin complet, et dans l’environnement ces chemins mènent aux programmes de
l’environnement au lieu de ceux du NAS. Le paquet arrive dans `build/` du dossier.

```bash
bvs tc install qdk-2.5.0-x86_64      # une fois ; qdk-2.5.0-aarch64 sur un NAS ARM
cd /share/CACHEDEV1_DATA/BucketVS/projects/MyTool
bvs qbuild                           # --env <nom> choisit l’environnement
```

Il utilise l’environnement `ubuntu2404-builder` (l’exemple « Ubuntu 24.04 builder »), sinon `debian12-builder`, sinon
un autre environnement Debian ou Ubuntu, et y a besoin de `rsync`. Quand il manque quelque chose, il s’arrête et nomme
la commande qui l’ajoute, par exemple `bvs env run ubuntu2404-builder -- apt-get install -y rsync`. Les arguments
après `bvs qbuild` vont à qbuild. Avant que qbuild ne démarre, `bvs qbuild` indique quel [cas de signature de
code](#signer-le-code-dun-qpkg) s’applique au dossier.

Un environnement a ses propres fichiers et outils. Tout ce qu’il contient tourne en root, voit chaque processus du NAS
et utilise le réseau du NAS.

### Signer le code d’un QPKG

App Center vérifie la signature de code d’un paquet à son installation. Pour signer, il faut un certificat de
signature de code que QNAP vous a délivré. `bvs qbuild` nomme l’un des trois cas sur sa première ligne, par exemple
`bvs: code signing, case 1 (a certificate as files): …` :

| Cas | Le dossier contient | Ce qui se passe |
|---|---|---|
| 1, un certificat sous forme de fichiers | `certificate` et `private_key`, éventuellement `ca_certs` (la chaîne) | qbuild signe le paquet avec `openssl` ; l’environnement a besoin d’`openssl` et de `bsdextrautils` |
| 2, un certificat sur un jeton matériel | `certificate_hsm` | ni pris en charge ni testé : `bvs qbuild` s’arrête avant de construire quoi que ce soit |
| 3, pas de certificat | aucun de ces fichiers | le paquet est construit sans signature |

- **Cas 1.** Placez les trois fichiers à côté de `qpkg.cfg`. S’il manque un programme à l’environnement,
  `bvs qbuild` donne la commande, par exemple `bvs env run debian12-builder -- apt-get install -y openssl
  bsdextrautils`. Il refuse un dossier qui n’a qu’un seul des fichiers `certificate` et `private_key` (qbuild
  avertirait et construirait un paquet non signé) et un dossier où l’un d’eux est vide. Gardez ces fichiers hors de
  git : le `.gitignore` du modèle QPKG les liste. En CI, gardez-les comme secrets du dépôt et écrivez-les dans le
  dossier pour la seule durée de la construction.
- **Cas 2.** qbuild lirait l’emplacement de la clé sur le jeton dans des variables qui ne lui parviennent pas dans
  l’environnement, et le paquet n’a pas été testé avec un jeton. Construisez sans `certificate_hsm` et signez le
  paquet sur la machine qui détient le jeton avec les outils de QDK ; le skill QNAP pour les assistants IA
  (`references/signing.md`) décrit la voie de QDK, que ce paquet n’a jamais exécutée.
- **Cas 3.** Sur une unité en QTS 6.1, `qpkg_cli -m` d’un paquet non signé n’a rien installé, tandis que
  `sh <fichier>.qpkg` dans un dossier partagé l’a installé et démarré. Le [dépôt de paquets](#dépôt-de-paquets) ne l’accepte
  que tant qu’un admin autorise les paquets non signés, et App Center sur un autre NAS ne l’installe que si son
  propriétaire autorise les apps sans signature numérique valide, ce que ce paquet ne recommande jamais.
  **Exporter en QPKG** donne toujours ce cas.
- `qbuild --sign` ajoute une signature GnuPG, qui n’est pas la signature de code que lisent App Center et le dépôt.

## App Builder

L’App Builder compile des programmes choisis dans un seul dossier, le préfixe de l’app (par défaut
`/usr/app/my_app_prefix`), à l’intérieur d’un environnement.

L’onglet **Exemples** d’**Apps** contient des apps prêtes à l’emploi, des environnements, des workflows CI, des tâches cron et des
Projets de l’éditeur. **Voir le code** sur une carte montre ce que l’exemple crée avant de l’utiliser, pour
tout rôle : les fichiers d’un projet de l’éditeur, le fichier du workflow, la commande de la tâche cron, ou pour une app ses
recettes dans l’ordre de build avec le `build.sh` et le `recipe.json` de chacune. La vue est en lecture seule et
affiche les fichiers texte ; un environnement n’a pas de fichier, sa carte n’a donc pas ce bouton. Utiliser un exemple demande le rôle developer,
et un environnement le rôle admin : la carte d’un environnement n’offre pas **Utiliser cet exemple** à un developer.

### Recettes

Chaque programme est compilé à partir d’une recette. Une recette nomme sa source avec un sha256, les recettes dont
elle dépend, les outils de compilation dont l’environnement a besoin, et une vérification qui prouve que le résultat
fonctionne (comme `bin/bash --version`). Il existe des recettes pour des bibliothèques (zlib, openssl, ncurses,
readline et d’autres), des shells (bash, zsh, busybox), des outils (coreutils, curl, git, jq, rsync, tmux, htop, nano
et d’autres), des serveurs (nginx, redis, openssh, dropbear) et un langage (lua).

- Une recette n’est compilée qu’après les recettes dont elle dépend.
- Une recompilation réutilise le résultat d’une recette tant que ses entrées sont inchangées (la source, la recette,
  ses dépendances, le compilateur et la bibliothèque C de l’environnement, le préfixe, et le choix d’une édition de
  liens dynamique ou statique) et que chaque fichier qu’elle a installé est toujours dans le préfixe.
- Le mode de liaison décide comment les programmes trouvent leurs bibliothèques. **dynamique** donne à chaque
  programme un RUNPATH `<prefix>/lib`. **relocatable** l’écrit relativement au dossier du programme
  (`$ORIGIN/../lib`), donc l’arborescence trouve ses bibliothèques dans n’importe quel dossier ; les chemins compilés
  dans les programmes, comme le dossier terminfo, nomment toujours le préfixe. **bundle** est relocatable et copie en
  plus dans `lib` les bibliothèques que les programmes chargent depuis l’environnement de compilation (comme
  libstdc++), donc l’app n’a besoin que de la bibliothèque C du NAS. **statique** lie statiquement les recettes qui le
  permettent. Passer de dynamique à relocatable ou bundle, et inversement, ne recompile rien.
- Une app statique refuse les recettes qui ne peuvent pas être liées statiquement (openssh, dropbear et tmux), en
  nommant chacune avec la raison, sauf si l’app est réglée pour les compiler en bundle. La page Nouvelle app et
  **Modifier** proposent les quatre modes et, pour une app statique, ce choix.
- Un programme statique qui recherche des utilisateurs, des groupes ou des noms d’hôte ne les lit que dans
  `/etc/passwd`, `/etc/group`, `/etc/hosts` et le DNS du NAS lui-même, quoi que nomme `/etc/nsswitch.conf`, donc les
  comptes d’un domaine ou de LDAP lui sont inconnus. Compilez ces programmes en dynamique quand ces comptes comptent.
  Le panneau avertit quand une app statique en contient un.
- Après la dernière recette, la vérification de chaque recette s’exécute de nouveau sur le préfixe terminé.
- Les fichiers de configuration sous `etc/` du préfixe survivent à une recompilation : un fichier modifié reste, et la
  nouvelle version par défaut va à côté sous le nom `<file>.bvs-new`.

### Mises à jour des recettes

Les recettes nouvelles et mises à jour arrivent aussi sous forme de catalogue signé depuis pub.qnaputilities.com, et
atteignent ainsi le NAS avant la prochaine version de Bucket & Code Server IDE. bvsd cherche un catalogue plus récent une fois par
jour. Il ne l’utilise que si sa signature correspond à la clé intégrée à bvsd, que chaque fichier correspond à la
liste du catalogue et que ses recettes sont plus récentes que celles livrées avec le paquet ; un catalogue qui échoue
à un contrôle n’est jamais utilisé. Tant qu’un catalogue est utilisé, Nouvelle app marque les recettes qu’il apporte
comme **nouvelle dans le catalogue** ou **mise à jour par le catalogue**.

La carte **Bibliothèque de recettes** de la page Apps indique la bibliothèque qu’utilisent les builds
(**Bibliothèque du paquet** avec la version du paquet, ou **Catalogue** avec la version du catalogue) et le dernier
contrôle avec son résultat. Un admin y trouve deux boutons ; le second apparaît tant qu’un catalogue est utilisé :

- **Rechercher des mises à jour des recettes** contrôle tout de suite, sans attendre le contrôle quotidien.
- **Utiliser la bibliothèque du paquet** revient aux recettes livrées avec Bucket & Code Server IDE. Le catalogue reste mis de côté
  jusqu’à l’arrivée d’un plus récent ; les builds utilisent alors le plus récent.

Après un changement, une recompilation réutilise chaque recette dont les fichiers n’ont pas changé. Avant chaque
build, bvsd contrôle de nouveau les fichiers du catalogue. Si l’un d’eux a changé sur le NAS, le build s’arrête avant
que rien ne s’exécute, et les builds utilisent le catalogue précédent ou la bibliothèque du paquet.

### Compiler une app

**Apps**, **Nouvelle app** (rôle developer) demande un nom, le préfixe (`/usr/app/<nom>` sauf si vous en choisissez un
autre), l’environnement dans lequel l’app se compile (un environnement Debian ou Ubuntu ; la galerie d’exemples crée
le « Ubuntu 24.04 builder », que la page choisit en premier), la cible (l’architecture de ce NAS, ou aarch64 ou armv7
en compilation croisée), les modèles de NAS où elle fonctionne, le mode de liaison et les recettes. Les recettes dont elles dépendent sont ajoutées dans l’ordre de compilation.

Une app peut aussi prendre les recettes d’un projet qui a son propre dossier `recipes/`. Choisissez ce projet dans
**Recettes d’un projet**, à l’étape **Paquets**. Ses recettes viennent en tête de liste, sous le nom du projet et
marquées **projet**, puis viennent celles de la bibliothèque. Une recette du projet qui porte le nom d’une recette de
la bibliothèque la remplace dans cette app et indique quelle version de la bibliothèque elle remplace. Une recette que
le projet laisse de côté est nommée avec la raison. Une recette choisie qui manque à la liste du projet est nommée
sous la liste déroulante et laissée hors de l’app ; elle revient quand vous choisissez une liste qui l’a. La page de
l’app nomme le projet, et un développeur ouvre la vue Projet depuis ce nom. Un viewer voit les recettes du dernier
build de l’app dans l’ordre où son journal les nomme, chacune avec son état et son journal ; avant le premier build,
il n’y a pas d’ordre à montrer.

Chaque mode de liaison est une carte, avec une image de l’endroit où les programmes trouvent leurs bibliothèques, ce
que le mode coûte en taille, en mises à jour et en lieux où l’app tourne, et quelles recettes le permettent. Avant que
vous choisissiez des recettes, les cartes comptent toute la bibliothèque de recettes ; quand vous revenez à la
première étape, elles comptent les recettes de votre compilation. Pour une app statique, vous choisissez ce qui arrive
aux recettes qui ne peuvent pas être liées statiquement : les compiler en bundle, ou les laisser de côté.

- **Compiler** (ou **Recompiler**) lance une tâche : elle télécharge chaque source et vérifie son sha256, installe
  dans l’environnement les outils de compilation dont les recettes ont besoin, compile dans une copie de la
  compilation précédente de l’app, et vérifie chaque programme.
- Une compilation qui échoue, est annulée ou est interrompue laisse l’app telle qu’elle était : l’activation, le
  téléchargement et l’export continuent d’utiliser la compilation réussie la plus récente.
- Une seule compilation d’une app tourne à la fois, et une compilation attend tant que des programmes de l’app active
  tournent (arrêtez-les d’abord).
- La page de l’app affiche l’ordre de compilation, l’état et le journal de chaque recette, les fichiers sous le
  préfixe, et l’archive de la compilation réussie la plus récente (**Télécharger**, un `.tar.zst`).
- **Modifier** sur la page de l’app définit un autre environnement, les modèles de NAS où elle fonctionne, un autre
  mode de liaison, le projet dont elle prend des recettes ou d’autres recettes ; la compilation suivante l’applique.
- **Dépendances**, un onglet de la page de l’app, montre ce dont chaque fichier ELF de la compilation la plus récente
  a besoin et où le chargeur du NAS le trouve, lu sans rien exécuter. Une bibliothèque manquante, une version que la
  bibliothèque ne définit pas, ou un RUNPATH qui pointe hors de l’app figure comme constat avec sa solution. Un
  developer applique une solution avec son bouton : **Ajouter … et compiler**, **Passer en … et compiler**,
  **Recompiler**, **Corriger le RUNPATH**, **Définir l’interpréteur**, **Choisir un autre environnement** ou
  **Construire pour ce NAS seulement**. Une
  solution sans bouton (installer un paquet dans un environnement, compiler pour une autre machine) est une phrase à
  suivre. Sous les constats, chaque fichier et ce dont il a besoin sont dessinés en graphe (les programmes, les
  bibliothèques de l’app, celles du NAS et celles qui manquent, en colonnes ; une ligne en tirets est une version qui
  manque à la bibliothèque, une ligne en pointillés une bibliothèque introuvable) ou en arbre ; choisissez un fichier
  pour voir ses détails. Sur le NAS, `bvs deps <app>` affiche la même chose.
- **Inspecteur ELF** (Toolchains, rôle developer) vérifie un fichier : d’une compilation d’app, du dossier des projets
  ou d’un environnement, et pour un admin n’importe quel fichier du NAS, tel qu’il tournerait sur ce NAS ou dans un
  environnement. `bvs ldd <file>` fait de même sur le NAS (`--env <name>` pour un environnement).
- **Chaque compilation** exécute la même vérification sur son résultat avant de remplacer l’app, et échoue sur un
  problème qui arrête un programme sur ce NAS, ou sur un programme qui exige une glibc plus récente que ce que l’app
  autorise, avec le problème et sa solution dans le message de la tâche.
- **Outil de correctif** (l’onglet Dépendances) définit le RUNPATH (le `lib` de l’app, la forme `$ORIGIN`, ou un
  dossier de l’app) ou l’interpréteur de programme de fichiers choisis de la compilation la plus récente, avec le
  patchelf de l’environnement, sur une copie qui ne remplace la compilation qu’en cas de succès. **Aperçu** montre
  chaque fichier avant et après, et les bibliothèques que le nouveau RUNPATH ne trouve pas dans l’app ; **Appliquer le
  correctif** l’exécute. La compilation suivante réécrit les RUNPATH dans le mode de liaison de l’app.

Une compilation Qt exécute les outils de Qt lui-même (moc, rcc, et lrelease de Linguist pour les traductions). Dans
une compilation croisée, les recettes Qt (`qtbase`, `qttools`) compilent donc d’abord ces outils pour ce NAS, dans
`host/` sous le préfixe, puis Qt pour la cible ; les recettes compilées sur Qt exécutent les outils depuis cet
endroit. Rien sur la cible n’exécute `host/`. Il fait partie de la compilation de l’app, donc l’archive et
**Exporter en QPKG** l’emportent aussi.

### Vos propres recettes

Quand la bibliothèque de recettes n’a pas de recette pour un programme, un projet peut avoir la sienne. Les recettes
vont dans le dossier `recipes/` du projet, au format de la bibliothèque, et seules les apps qui nomment le projet les
compilent. Un paquet se fait ainsi en cinq étapes :

1. **Créer le projet.** Dans [Nouveau projet](#nouveau-projet), le modèle **Package from source** (paquet depuis les
   sources) crée un dossier avec une recette de départ qui porte le nom du projet, un Makefile pour deux apps (une
   pour le processeur de ce NAS, une pour aarch64, en compilation croisée sur un NAS x86_64) et le script de démarrage
   et d’arrêt du paquet issu de l’étape **Service**.
2. **Écrire la recette.** **New recipe** dans l’éditeur ajoute une autre recette à partir de la même recette de départ
   ([Ouvrir l’éditeur](#ouvrir-léditeur)). Mettez dans `recipe.json` la vraie adresse de la source du programme et son sha256
   (`curl -fsSL <adresse> | sha256sum` dans le terminal de l’éditeur affiche la somme), et ses étapes de compilation dans
   `build.sh`. Le dossier d’une recette contient `recipe.json`, `build.sh` et le fichier vendor que nomme
   `recipe.json`, rien d’autre ; un correctif va donc dans `build.sh` sous forme de here-document.
3. **La vérifier.** Chaque enregistrement d’un fichier de recette vérifie les recettes du projet et liste les résultats
   dans la vue **Problems** ; `make lint` dans le terminal et la carte **Recettes** de la [vue Projet](#la-vue-projet)
   donnent les mêmes résultats. La vérification lit `recipe.json` et analyse `build.sh` avec `bash -n`. Elle n’exécute
   jamais la recette.
4. **Compiler.** **Compiler** dans la vue Projet crée les deux apps quand elles manquent, puis les compile l’une après
   l’autre. Un build lit les recettes du projet une fois, à son début ; un fichier enregistré ensuite attend le build
   suivant. Une recette du projet qui porte le nom d’une recette de la bibliothèque la remplace dans les apps de ce
   projet seulement, et le journal du build le dit.
5. **Empaqueter et installer.** `make qpkg` dans le terminal de l’éditeur met le dernier build réussi de chaque app dans
   le paquet, avec le QDK des Toolchains, et laisse de côté une app qui n’en a pas. Avec les fichiers de signature du
   code dans `qpkg/` à côté de `qpkg.cfg`, ou dans un dossier nommé par `QPKG_CERT_DIR`, le paquet est signé ([Signer
   le code d’un QPKG](#signer-le-code-dun-qpkg)), et **Déploiement de test** dans la vue Projet (admin) l’installe sur
   ce NAS.

Quelques règles s’appliquent aux recettes d’un projet :

- Une recette que le projet laisse de côté (un fichier en trop dans son dossier, un lien, un fichier au-delà de sa
  taille limite) est nommée avec la raison sur la carte Recettes et par la vérification, et un build qui en a besoin
  est refusé.
- Quand le dossier du projet est renommé ou supprimé, ses apps ne se compilent plus : le build répond « Il n’y a pas de projet
  “…” » jusqu’à ce que **Modifier** sur la page de l’app choisisse le nouveau nom du projet ou **Aucun : la bibliothèque
  seule**.
- Les recettes d’un projet s’exécutent en root dans l’environnement de compilation, comme celles de la bibliothèque.
  Tout développeur peut les écrire et les compiler, comme tout développeur peut exécuter le Makefile d’un projet par
  Compiler. Un viewer voit les noms des recettes d’une app dans le journal de son build, jamais les fichiers des
  recettes.
- Une recette de projet reste dans son projet. Elle n’entre dans la bibliothèque de recettes, et dans le catalogue
  signé que reçoit chaque NAS, que par une modification relue de Bucket & Code Server IDE lui-même.
- Une recette dont le build a besoin de paquets npm, Go ou Cargo les liste dans un fichier vendor. `npm-vendor.py`,
  `go-vendor.py` et `cargo-vendor.py`, dans `/usr/bin/bucketvs/share/bucketvs/tools/`, écrivent cette liste ; ils ont
  besoin d’un runtime Python des [Toolchains](#toolchains-et-environnements).

### Sur quels modèles de NAS une app fonctionne

Un programme compilé contre la glibc ne démarre que sur un NAS dont la glibc est au moins aussi récente que la version
la plus récente que le programme demande. Le paquet compte sur une glibc 2.39 ou plus récente sur tous les NAS où il
fonctionne (QTS 6.0, QuTS hero 6.0 et suivants) : c’est son plancher glibc. 2.39 est la glibc d’Ubuntu 24.04 et de
QuTS hero 6.0.1, lue dans le propre `/lib/libc.so.6` de ce firmware ; QNAP ne publie aucune version de glibc, et ses notes de version ne citent aucun QTS 6.0. Les NAS de test sous QTS 6.1.0 et QuTS hero 6.1.0 ont 2.43.

**Fonctionne sur**, dans la nouvelle app et dans **Modifier**, choisit l’un des deux :

- **Tous les NAS où le paquet fonctionne** (par défaut) : aucun programme ne peut exiger une glibc au-dessus de 2.39.
  Compilez une telle app dans un environnement dont la glibc est 2.39 ou plus ancienne, comme le Ubuntu 24.04
  builder (2.39) ou le Debian 12 builder (2.36). C’est le choix pour **Exporter en QPKG** et le dépôt de paquets.
- **Ce NAS seulement** : les programmes peuvent exiger la glibc de ce NAS, pour un logiciel qui en exige une plus
  récente. Une telle app est compilée pour l’architecture de ce NAS et n’est jamais exportée en QPKG.

Une compilation croisée (aarch64 ou armv7) est toujours pour tous les NAS. La nouvelle app avertit quand la glibc de
l’environnement choisi dépasse ce que l’app autorise. Après chaque compilation, bvsd lit la version de glibc que
demande chaque programme et chaque bibliothèque, y compris pour une compilation croisée. Un fichier au-dessus de la
limite fait échouer la compilation, et les solutions sont nommées : un autre environnement (**Choisir un autre
environnement**), le mode de liaison static ou **Construire pour ce NAS seulement**. La page de l’app indique quelle
glibc exige sa compilation réussie la plus récente, et l’onglet **Dépendances** liste chaque fichier au-dessus de la
limite.

Sur le NAS, `bvs tree-deps <préfixe> --tree <dossier> --glibc-max 2.39` fait la même vérification sur n’importe quelle
arborescence d’app.

### Activer une app sur le NAS

**Activer** (admin) fait du préfixe un lien vers la compilation réussie la plus récente de l’app, donc ses programmes
tournent sur le NAS depuis `/usr/app/<nom>/bin`. L’activation ne remplace jamais rien qu’elle n’a pas créé : un
dossier, un fichier ou un autre lien à l’emplacement du préfixe est refusé avec la raison. Le disque système de QTS
est reconstruit à chaque démarrage, donc le paquet recrée les liens à chacun de ses démarrages. **Désactiver** (admin)
supprime le lien ; supprimer l’app la désactive d’abord et la déplace dans la corbeille pour 7 jours. Un developer ne
peut supprimer une app que tant qu’elle n’est pas active. Une app compilée pour une autre architecture ne peut pas
être activée ici ; téléchargez son archive pour un NAS de ce type.

### Exporter une app

**Exporter en QPKG** (admin) emballe la compilation réussie la plus récente dans un QPKG à part, qui installe l’app
sur un autre NAS sans ce paquet. Il refuse une app compilée pour ce NAS seulement, et s’arrête avant l’emballage quand
un programme de la compilation exige une glibc au-dessus de 2.39 (voir [Sur quels modèles de NAS une app
fonctionne](#sur-quels-modèles-de-nas-une-app-fonctionne)). Il faut QDK : installez « QNAP QDK 2.5.0 » sur la page
Toolchains, onglet QNAP. Tant que QDK n’est pas installé, le bouton n’apparaît pas et la page de l’app indique où
l’obtenir. L’export exécute qbuild dans l’environnement de l’app, de la même façon que [`bvs
qbuild`](#compiler-un-qpkg-avec-bvs-qbuild). Chaque compilation y installe `rsync` ; pour une app compilée la dernière
fois avec une version plus ancienne du paquet, le refus nomme la commande qui l’ajoute. Le paquet exporté crée le lien
de son préfixe au démarrage et le supprime à l’arrêt. Il n’est pas signé : le journal de la tâche indique le [cas
3](#signer-le-code-dun-qpkg), tout comme une ligne sous le bouton. Dans une session SSH, `/etc/init.d/<nom>.sh
disable` et `enable` règlent l’interrupteur du paquet exporté dans l’App Center et laissent le lien du préfixe tel
quel. À chaque démarrage, le paquet exporté ajoute aussi une ligne à `/etc/profile` et à `/root/.profile`, qui met
`bin` et `sbin` de l’app dans le `PATH` d’une connexion SSH, après les commandes propres du NAS, pour admin et pour tout
autre compte autorisé à se connecter ; une suppression retire cette ligne.

## Dépôt de paquets

Le paquet tient un dépôt App Center : les paquets QPKG qui y sont publiés peuvent être installés sur d’autres NAS
depuis l’App Center, comme les apps de n’importe quel dépôt.

- **L’adresse.** `bvs repo list` l’affiche (et `GET /api/v1/repo`) : `<base>/repo/repo.xml`, où `<base>` est l’hôte
  des paquets publié (sinon le nom d’hôte principal publié) tant que le paquet est publié sur Internet ([Publier sur
  Internet](#publier-sur-internet-cloudflare-tunnel)), sinon l’URL publique quand elle est définie, sinon
  `http://<adresse du NAS>:9950` (`https://…:9951` quand le HTTP simple ne fait que rediriger). Sur l’autre NAS : App
  Center, **Paramètres**, **Dépôt d’applications**, **Ajouter**, et cette adresse.
- **Publication.** `bvs repo publish <file.qpkg>` (sur le NAS, en root) ou `POST /api/v1/repo/packages` avec le
  fichier comme corps (rôle developer). L’architecture vient du nom de fichier QDK `<name>_<version>_<arch>.qpkg`
  (`x86_64`, `arm_64`, `arm-x41`), ou de `--arch`. Le nom, la version, la description et l’auteur viennent du paquet
  lui-même ; `PUT /api/v1/repo/packages/<name>/meta` et `…/icon` (un PNG, admin) changent ce qu’affiche l’App Center.
  Un paquet ne peut pas s’appeler `default` : c’est le nom de l’icône propre du dépôt.
- **Paquets signés seulement.** Un paquet sans signature de code est refusé, car l’App Center n’installe de tels
  paquets que sur un NAS réglé pour les autoriser. Le dépôt considère un paquet comme signé quand son bloc de
  signature de code est une signature PKCS#7 avec un certificat et un signataire. Il ne vérifie pas cette signature ni
  ses certificats par rapport à ceux de QNAP ; l’App Center le fait quand il installe le paquet. Un admin peut activer
  `repo.accept_unsigned` dans les réglages pour un réseau où c’est connu ; ces paquets sont marqués dans leur
  description. Les paquets que l’App Builder exporte ne sont pas signés, ils ont donc besoin de ce réglage.
- **Versions.** Chaque version reste téléchargeable, mais l’App Center voit une version par paquet, celle qu’un admin
  liste. La première version d’un paquet publiée par un admin est listée aussitôt ; une suivante quand l’admin la
  désigne (`POST /api/v1/repo/packages/<name>/latest`, ou `bvs repo publish --latest`). La version d’un developer, y
  compris la première d’un nouveau paquet, n’est listée nulle part tant qu’un admin n’a pas fait d’une version la plus
  récente : chaque NAS qui a ajouté le dépôt installe la version listée en root. Supprimer une version (admin) retire
  toutes ses architectures ; si c’était la version listée, aucune version n’est listée tant qu’un admin n’en choisit
  pas une autre.
- **Envois.** Deux envois sont copiés à la fois ; le dépôt refuse un envoi qui laisserait moins de 256 Mio libres sur
  le volume de données. Le dépôt ne fait pas partie d’une sauvegarde : après une restauration, publiez de nouveau les
  paquets.
- **Dans le panneau.** La page **Paquets** affiche l’adresse à copier, les paquets avec leurs versions et s’ils sont
  signés ; un développeur y envoie un QPKG, et un admin liste une version comme la plus récente et supprime une version.
  Sa carte **Signature de code** montre les [trois cas](#signer-le-code-dun-qpkg), combien de versions listées n’ont pas
  de signature, et l’interrupteur de l’admin pour les paquets non signés, qui demande confirmation avant de s’activer.
  La page d’une app affiche son pipeline (compiler, exporter en QPKG, publier) avec un bouton **Publier**.

## Atelier du noyau

L’atelier du noyau compile des modules pour le noyau que le NAS exécute, vérifie chacun par rapport à ce noyau, et le
charge. QNAP n’a pas publié les sources de ses noyaux actuels (ses publications GPL s’arrêtent à QTS 5.2.3), donc
l’atelier lit ce que le noyau en cours montre de lui-même et configure une arborescence du noyau pour lui
correspondre.

### Ce qu’il faut

- L’environnement Kernel builder de la galerie d’exemples (Ubuntu 24.04 avec GCC 13 et les outils de compilation du
  noyau). Un module doit être compilé avec la même version majeure de GCC que le noyau ; l’atelier les compare.
- Le rôle admin, et root dans un terminal : les commandes `bvs kernel` utilisent le socket CLI de bvsd ou chargent des
  modules.

### Le profil

`bvs kernel profile` montre ce qu’est le noyau en cours : sa version (`6.6.129-qnap`), le compilateur avec lequel QNAP
l’a compilé, le vermagic des modules propres de QNAP, la taille de `struct module` dans ceux-ci et l’emplacement de
init et exit, si les modules doivent être signés, et les réglages qu’une arborescence du noyau reçoit pour lui
correspondre.

### L’espace de travail

`bvs kernel prepare` crée l’espace de travail du noyau en cours dans `kernels/<release>` du dossier des données :

1. Il prend les sources du noyau que le catalogue a pour cette version (`bvs kernel sources`) : l’arborescence GPL
   propre de QNAP quand QNAP en a publié une, sinon l’arborescence de kernel.org, à laquelle manquent les
   modifications propres de QNAP. `--source` en choisit une autre.
2. Il configure l’arborescence : avec la configuration de QNAP pour ce modèle quand l’arborescence est celle de QNAP,
   sinon avec les réglages du profil. `--option NAME=m` ajoute un réglage, par exemple un pilote.
3. Il prépare l’arborescence pour la compilation de modules (`--full` compile plutôt le noyau entier, ce qui prend
   bien plus longtemps et permet à la vérification de voir les symboles exportés du noyau).
4. Il vérifie que l’arborescence compile exactement pour la version du noyau en cours.

Il s’exécute comme une tâche sur la page Tâches. Un prepare échoué garde l’arborescence du précédent.

### Compiler un module

- Votre propre module est un dossier dans `projects/` avec un fichier `Kbuild`. `bvs kernel make` dans ce dossier (ou
  `bvs kernel make --project <folder>`) le compile. L’exemple « Kernel module » est un tel projet.
- Un pilote du noyau que QNAP ne fournit pas se compile depuis son répertoire de l’arborescence du noyau :
  `bvs kernel make --dir drivers/usb/serial`. L’exemple « USB serial driver ch341 » compile ainsi le pilote ch341 ;
  son réglage vient d’abord avec `bvs kernel prepare --option USB_SERIAL=m --option USB_SERIAL_CH341=m`.

Les modules qu’une compilation produit sont gardés dans `kernels/<release>/modules/`. Un module que QNAP fournit pour
ce noyau n’est pas gardé : la copie propre de QNAP reste celle qui se charge. `bvs kernel make --build-only` n’y garde
rien : le module reste là où il a été compilé. Les workflows CI des modèles de noyau de Nouveau projet compilent
ainsi.

### La vérification et le chargement

`bvs kernel check <module>` compare un module au noyau en cours : le vermagic, la taille de `struct module` et
l’emplacement de init et exit dans celle-ci (par rapport aux modules propres de QNAP), si une signature est exigée,
les modules dont il dépend, et chaque symbole dont il a besoin. Chaque constat dit ce qui ne correspond pas et quoi
faire. Le code de sortie 3 signifie que le module ne doit pas être chargé.

`bvs kernel load <module>` refait la vérification et charge le module, en chargeant d’abord les modules dont il dépend
(ceux de QNAP depuis le NAS, ou les vôtres). Un module avec une erreur à la vérification n’est jamais chargé.
`bvs kernel unload <module>` le retire ; un module en cours d’utilisation reste chargé. Chaque chargement et
déchargement est écrit dans `log/kernel-modules.log`.

Le noyau de QNAP ne liste pas ses symboles de données, donc la vérification ne peut pas rechercher des symboles comme
`jiffies` ; elle les nomme en avertissements. Si l’un d’eux manque, le noyau refuse le module au chargement.

### Modules dans une app

Une app peut nommer des modules de l’espace de travail. Activer l’app les charge avant que son lien soit créé, et
chaque démarrage du paquet les charge de nouveau une fois le panneau disponible ; la page Noyaux affiche un chargement
encore en cours. La désactiver les décharge d’abord ; un module en cours d’utilisation garde l’app active.

- Si le NAS a redémarré pendant le chargement de modules, le démarrage suivant n’en charge aucun et affiche une
  notification : chargez-les à la main ou activez l’app de nouveau.
- Si le NAS s’est arrêté sans l’arrêt ordonné du paquet (un plantage, une coupure de courant ou une réinitialisation
  matérielle) pendant un démarrage où le paquet avait chargé des modules, le démarrage suivant ne charge pas ces
  modules, car l’un d’eux a pu causer le plantage. La notification et la page Noyaux les nomment. En charger un à la
  main, ou réactiver son app, permet au démarrage de le charger de nouveau. Un arrêt ou un redémarrage normal du NAS
  arrête le paquet dans l’ordre et ne change rien.
- Un module dont le démarrage ne se termine jamais (un pilote qui attend son matériel) laisse le panneau fonctionner.
  Une notification le nomme au bout d’une minute ; les autres chargements et déchargements l’attendent, et un
  redémarrage du NAS y met fin.

Après une mise à jour de QTS vers un autre noyau, les modules doivent être recompilés dans l’espace de travail du
nouveau noyau. Il en va de même après une restauration dans un autre dossier de données : `kernels/` ne fait pas
partie d’une sauvegarde.

### Dans le panneau

La page **Noyaux** affiche le profil du noyau en cours, les sources du noyau du catalogue, l’espace de travail et ses
modules. Un admin y prépare l’espace de travail (une source, l’environnement, des réglages supplémentaires, le noyau
entier ou non) et compile des modules depuis un dossier de projet ou un répertoire de l’arborescence ; les deux
s’exécutent comme des tâches. **Vérifier** (developer) affiche chaque constat avec sa solution ; **Charger** et
**Décharger** (admin) font ce que font `bvs kernel load` et `unload`, et un chargement refusé affiche la vérification
qui l’a arrêté. Un fichier `.ko` n’importe où sous le dossier des données peut être vérifié par son chemin.

## Tâches cron et opérations

La page Tâches a deux listes : les tâches planifiées (cron) et les opérations (les longues tâches que bvsd exécute,
comme les installations et les déplacements).

### Tâches planifiées

Un developer crée et modifie ses propres tâches ; un admin les modifie toutes. Une tâche a :

- Planification : cinq champs cron (`0 3 * * *`), six avec les secondes en premier, ou un descripteur (`@daily`,
  `@hourly`, `@every 6h`). Un préfixe `CRON_TZ=<zone>` choisit un autre fuseau horaire ; sans lui, la tâche s’exécute
  dans le fuseau horaire du NAS, affiché en haut de la page.
- Commande : exécutée en root par le `bash -lc` du paquet. Plusieurs lignes s’exécutent comme un seul script, et sa
  sortie devient le journal de l’exécution.
- Où elle s’exécute : sur le NAS, ou dans un environnement (avec le `bash` de l’environnement, ou `/bin/sh` s’il n’en
  a pas).
- Dossier de travail : relatif à `projects/` dans le dossier de données, ou un chemin complet. Dans un environnement,
  seul `projects/` est visible, en tant que `/projects`.
- Délai maximal : en secondes (0 : aucun). Une exécution encore en cours après ce délai est arrêtée et marquée comme
  ayant dépassé le délai.
- Pendant qu’une exécution est en cours : la suivante est sautée, ou elle attend (une seule au plus attend).
- En cas d’échec : une exécution échouée ou qui a dépassé le délai envoie une [notification](#notifications).

Chaque tâche garde ses 100 dernières exécutions avec la fin de leur sortie (4 Ko). Le journal complet d’une exécution
est gardé 30 jours.

Une tâche s’exécute en root pour le compte de son propriétaire, avec le dossier personnel de celui-ci comme `HOME` ;
elle ne s’exécute donc que tant que son propriétaire est un developer ou un admin actif. Tant que le propriétaire est
désactivé ou a le rôle member ou viewer, chacune des heures de la tâche est enregistrée comme une exécution sautée
qui dit pourquoi, par exemple « its owner ola is disabled », et **Exécuter maintenant** répond avec la même raison.
Aucune notification n’est envoyée. Une exécution en cours au moment où le propriétaire est désactivé ou reçoit un
rôle inférieur à developer est arrêtée. La tâche s’exécute de nouveau dès que le compte est réactivé, ou redevient
developer. Supprimer un compte supprime ses tâches. Les tâches intégrées s’exécutent quoi qu’il arrive à un compte.

Quand une commande se termine, les processus qu’elle a lancés en arrière-plan continuent de tourner. En cas de
dépassement du délai, de suppression de la tâche ou d’arrêt du paquet, tout le groupe de processus de l’exécution
reçoit SIGTERM, puis SIGKILL après 5 secondes.

Les nuits de changement d’heure, les tâches suivent la règle du cron de Debian. Une tâche avec `*` au début de son
champ des minutes ou des heures (aussi `*/N`), `@hourly` ou `@every` suit le temps écoulé : en automne, elle s’exécute
lors des deux passages de l’heure répétée, et au printemps l’heure sautée ne vient pas. Toute autre tâche exécute
chacune de ses heures une fois : une heure dans l’heure répétée s’exécute une fois, et une heure dans l’heure sautée
s’exécute juste après le changement.

Tout compte connecté, un `viewer` aussi, lit les commandes des tâches et la sortie de leurs exécutions. Ne mettez
ni mot de passe ni jeton dans une commande : faites-la les lire dans un fichier sous `<data>/etc` que seul root lit
(`chmod 600`).

### Tâches intégrées

| Tâche | Quand | Ce qu’elle fait |
|---|---|---|
| Maintenance | tous les jours à 03:30 | supprime de `tmp/` dans le dossier de données ce que rien n’a modifié depuis 10 jours ; fait la rotation de `log/audit.log` au-delà de 10 Mo |
| Vider la corbeille | tous les jours à 03:45 | supprime ce qui a été déplacé dans `trash/` dans le dossier de données il y a plus de 7 jours (comme le dossier personnel d’un compte supprimé) |
| Actualisation du catalogue des toolchains | tous les jours à 04:15 | récupère le catalogue signé des toolchains |
| Rotation des journaux de nginx | toutes les heures, à la minute 7 | renomme `nginx-access.log` ou `nginx-error.log` en `.1` dès qu’il dépasse 20 Mo (cinq anciens fichiers gardés) et demande à nginx de rouvrir ses journaux |

Un admin peut changer la planification, le délai maximal, la règle et la notification d’une tâche intégrée, et la
désactiver. Son nom et sa commande restent.

### Opérations

Les installations, les tâches d’environnement, les déplacements de données et l’activation d’un module s’exécutent
comme des opérations. La liste affiche les 50 dernières, les plus récentes d’abord, avec leur progression et leur
journal. Les opérations et leurs journaux sont gardés 30 jours. Une opération qui tournait quand le paquet s’est
arrêté apparaît comme `interrupted`. Un developer annule ses propres opérations ; un admin annule n’importe laquelle.

## Sauvegardes

La page **Sauvegardes** (admin) définit ce qui est sauvegardé, quand, où vont les copies et combien sont gardées.
**Sauvegarder maintenant** lance une sauvegarde à tout moment. Sur une nouvelle installation, les sauvegardes
planifiées sont désactivées ; une fois **Sauvegardes planifiées** activé, une sauvegarde s’exécute tous les jours par
défaut (la tâche cron intégrée `backup`). Elle garde l’exécution la plus récente de chacun des 7 derniers jours, des 4
dernières semaines et des 6 derniers mois, et toujours la dernière exécution réussie. Une seule sauvegarde,
vérification, restauration ou suppression s’exécute à la fois.

Deux exécutions le même jour comptent pour un seul jour : quand la plus récente se termine, l’exécution antérieure de
ce jour est supprimée. Un **Sauvegarder maintenant** l’après-midi remplace donc l’exécution planifiée du matin ;
copiez d’abord une exécution ailleurs quand l’état de ce matin-là compte.

### Ce que contient une sauvegarde

Une exécution est un dossier `<données>/backups/<id>/` avec un `manifest.json` qui liste chaque fichier avec son
sha256 :

| Partie | Contenu |
|---|---|
| réglages | `etc/` : réglages, comptes, secrets TOTP, la clé TLS, les clés de sauvegarde |
| Git | le dump de Forgejo : dépôts, wikis, objets LFS, pièces jointes et sa base de données |
| SVN, buckets | une copie de chaque dépôt, faite de façon incrémentale : une révision déjà copiée ne l’est pas de nouveau |
| réglages VS Code | les réglages VS Code de chaque compte, ses raccourcis clavier, ses snippets et la liste de ses extensions (pas les fichiers des extensions : la vue Extensions les réinstalle) |
| environnements | les environnements des Toolchains (désactivé par défaut : ils peuvent être recréés) |
| projets | chaque dossier de projet avec son historique Git et les fichiers pas encore validés, sans `node_modules` |
| dossiers HOME | le dossier HOME de chaque compte, sans les caches ni les installations propres des outils d’IA (leur bouton Installer les remet) |
| App Builder | les réglages de chaque app et l’historique de ses compilations ; une compilation refait les fichiers de l’app |
| dépôt de paquets | les paquets publiés, leurs icônes et l’index que lit App Center |

Ces quatre parties sont dans chaque sauvegarde, quoi que vous cochiez sur la page. Un dossier qui n’a pas changé
depuis la sauvegarde précédente est lié à la copie de celle-ci et n’occupe aucune place.

L’exécution ne devient visible que lorsque chaque partie est écrite et vérifiée ; un échec ou un volume plein supprime
le dossier inachevé et laisse les exécutions précédentes telles qu’elles étaient. **Vérifier** relit entièrement une
exécution : le sha256 de chaque archive, `svnadmin verify` de chaque révision, `git fsck` de chaque dépôt et un
contrôle d’intégrité de la base de données de Forgejo.

### Destinations

Chaque exécution est copiée vers chaque destination de la page :

- **Dossier local** sur ce NAS : l’exécution telle quelle, avec les fichiers inchangés liés à la copie précédente,
  donc chaque copie ne coûte que ce qui a changé. Non chiffré. Sur les mêmes disques que les données, il protège
  contre les erreurs, pas contre un volume défaillant.
- **rsync** (`user@host:path`) via SSH. **Se connecter avec la clé de sauvegarde du paquet** : la page affiche la clé
  publique à ajouter au `authorized_keys` de l’autre machine.
- **rclone** (`remote:path`, un remote de `<données>/etc/rclone.conf`) : stockage cloud et tout ce que rclone atteint.

Une copie qui quitte le NAS (rsync, rclone) doit être chiffrée : activez **Chiffrer les copies qui quittent le NAS**
et **Créer la clé**. Les copies sont chiffrées avec age pour sa moitié publique ; **la moitié secrète n’est affichée
qu’une fois** et bvsd ne la garde pas. Sans elle, aucun NAS, celui-ci compris, ne peut restaurer une copie chiffrée :
conservez-la loin de ce NAS. Une nouvelle clé chiffre les exécutions à partir de ce moment ; les copies plus anciennes
demandent l’ancienne clé secrète.

Une destination qui échoue est nommée dans le résultat de l’exécution ; l’exécution elle-même reste bonne.

`rclone.conf` contient les identifiants des distants. Avant qu’une copie ou un **Test** ne l’utilise, bvsd le rend
(ainsi que la clé de sauvegarde) lisible par root seul (mode 600) s’il ne l’est pas, et le journal de la tâche le dit.

### Restaurer

**Restaurer** sur une exécution remet en place soit tout, soit un dépôt (un dépôt SVN, un bucket ou un dépôt Git),
soit un dossier de projet :

- Rien n’est remplacé avant d’être vérifié : chaque archive par rapport à son sha256, chaque copie SVN avec
  `svnadmin verify`. Une copie qui échoue arrête la restauration avant que quoi que ce soit ne change dans les données
  en service.
- Les données en service remplacées vont dans `<données>/trash` pour 7 jours, donc une restauration peut elle-même
  être annulée.
- **Un dépôt** ou **un dossier de projet** est remis en place pendant que les modules continuent de tourner. Un
  dossier de projet supprimé par erreur revient avec son historique et ses fichiers pas encore validés.
- **Tout** arrête les modules, échange les données et redémarre bvsd sur les réglages restaurés : chaque session se
  termine, et les comptes et mots de passe sont ceux de la sauvegarde. Un dossier de projet ou HOME créé après la
  sauvegarde reste.

## Notifications

bvsd écrit chaque notification dans les journaux système de QTS. Il peut aussi les envoyer à des services de
messagerie et de courrier par des URL shoutrrr (Telegram, Discord, Slack, e-mail, un webhook générique et d’autres).

| Notification | Quand | Interrupteur pour les URL |
|---|---|---|
| échec de tâche | une exécution cron d’une tâche avec notification en cas d’échec a échoué ou dépassé le délai ; une opération a échoué | `notify.on_job_failure` |
| sauvegarde | le résultat de chaque sauvegarde, vérification et restauration, réussies comprises ; une sauvegarde planifiée qui n’a pas pu démarrer | `notify.on_backup` |
| boucle de redémarrage | un processus a commencé à redémarrer en boucle | `notify.on_crashloop` |
| certificat | un certificat téléversé a 30 jours ou moins ; nginx est revenu au certificat auto-signé ; un nouveau certificat auto-signé a été créé | `notify.on_cert_expiry` |
| système | réglages refusés au démarrage, un déplacement de données inachevé trouvé, des processus restants arrêtés, la configuration n’a pas pu être écrite | toujours envoyée |

- Les interrupteurs ne décident que de ce qui va aux URL. Les journaux système reçoivent chaque notification.
- La même notification part au plus une fois toutes les 10 minutes. La suivante après ce délai dit combien ont été
  retenues.
- Chaque URL reçoit son propre `shoutrrr send` avec une limite de 10 secondes. Un échec est journalisé avec le seul
  schéma de l’URL, car le reste d’une URL contient son jeton.

Les URL et les interrupteurs se trouvent sous **Notifications** sur la page Système ; les interrupteurs sont activés
par défaut. Un admin les modifie là. Les autres comptes voient les interrupteurs et seulement le schéma de chaque URL,
car le reste d’une URL contient son jeton. L’admin du NAS peut aussi les définir par le socket CLI :

```bash
/usr/bin/bucketvs/bin/curl --unix-socket /usr/bin/bucketvs/var/run/bvsd-cli.sock \
  -X PUT -H 'Content-Type: application/json' \
  -d '{"notify":{"shoutrrr_urls":["telegram://<token>@telegram?chats=<chat id>"]}}' \
  http://bvsd/api/v1/settings
```

## Journaux et paquet de diagnostic

Chaque journal est `log/<name>.log` dans le dossier de données. Les journaux de bvsd et de chaque processus qu’il
surveille subissent une rotation à 20 Mo, et cinq anciens fichiers sont gardés. `nginx-access.log` et
`nginx-error.log` ont les mêmes limites, vérifiées à chaque démarrage de bvsd et toutes les heures par la tâche
« Rotation des journaux de nginx », donc l’un d’eux peut dépasser 20 Mo d’au plus une heure de trafic. La tâche
« Maintenance » fait la rotation de `audit.log` dès qu’il dépasse 10 Mo et garde trois anciens fichiers. La page
Journaux (admin) affiche les dernières lignes de n’importe quel journal, les filtre et suit les nouvelles lignes à
mesure qu’elles arrivent :

| Journal | Ce qui l’écrit |
|---|---|
| `bvsd` | bvsd lui-même : démarrages, arrêts, réglages appliqués, problèmes |
| `nginx` | la sortie propre de nginx |
| `nginx-access`, `nginx-error` | chaque requête, et les erreurs de nginx |
| `audit` | chaque requête sur le socket CLI |
| un par processus de module | le programme de chaque module |

La page Système affiche la version, la plateforme, le fuseau horaire et chaque processus que bvsd surveille.
« Télécharger le paquet de diagnostic » (admin) crée une archive pour un rapport de bug :

- les réglages avec le mot de passe SMTP et chaque URL shoutrrr masqués ;
- le `nginx.conf` généré, les détails du certificat sans sa clé, les volumes ;
- l’état de chaque module, processus et opération récente ;
- les 2000 dernières lignes de chaque journal, où chaque chaîne de requête est remplacée par `?(query removed)`.

Elle ne contient jamais de mots de passe, de sessions, de jetons ni de clés.

## Mises à jour

bvsd lit `https://pub.qnaputilities.com/bucketvs/latest.json` au démarrage et toutes les 24 heures. Quand il nomme une
version plus récente, la page Système la nomme, avec le lien que donne le flux. bvsd ne fait que signaler la nouvelle
version : il ne télécharge ni n’installe jamais de paquet. Une vérification échouée ne change rien dans le panneau.

### Installer une nouvelle version

Installez le nouveau paquet par-dessus l’ancien, depuis l’App Center ou avec **Installer manuellement**.
L’installation arrête bvsd, remplace les fichiers du programme et le redémarre ; le dossier de données, les réglages,
les comptes et chaque dépôt restent, tout comme les sessions du panneau. Les instances de l’éditeur s’arrêtent avec bvsd,
donc les terminaux ouverts des éditeurs se terminent ; une instance redémarre quand son compte ouvre l’éditeur.

**Depuis une version qui utilisait les ports 6050, 6051 et 6052.** Les versions précédentes écoutaient sur 6050
(HTTP), 6051 (HTTPS) et 6052 (Git via SSH). Le premier démarrage de la nouvelle version déplace chacun d’eux vers
9950, 9951 et 9952 et le dit dans une notification ; un port que vous avez choisi vous-même reste. Changez ce qui
nomme les anciens ports : favoris, un proxy Plesk vers 6050, et les remotes git comme `ssh://git@<NAS>:6052/…`
(`git remote set-url`).

### Supprimer, réinstaller et un paquet endommagé

Votre travail reste quand le paquet est supprimé, réinstallé ou endommagé. Tout ce que vous créez se trouve dans la
dossier de données : comptes et réglages, dépôts Git et SVN, buckets, dossiers de projet, dossiers HOME, réglages et
extensions de VS Code, apps de l’App Builder, environnements, dépôt de paquets et sauvegardes. Le dossier
d’installation (`<volume>/.qpkg/bucketvs`) ne contient que les programmes et un fichier qui désigne le dossier de
données.

- **Supprimer, puis réinstaller.** La suppression du paquet efface le dossier d’installation et garde le dossier de
  données et `/etc/config/bucketvs-data-root`, le fichier qui la désigne. L’installation suivante lit ce fichier et
  démarre sur le même dossier de données : les mêmes comptes et mots de passe, les mêmes projets et dépôts, sessions
  ouvertes comprises.
- **Installer par-dessus le paquet installé.** La même version ou une plus récente remplace les fichiers des
  programmes ; le dossier de données n’est pas touché.
- **Un paquet endommagé.** Quand le dossier d’installation est endommagé (un programme manque, le dossier a été
  supprimé à la main), réinstallez le paquet depuis App Center ou avec **Installer manuellement**. L’installation
  trouve le dossier de données de la même façon. Un bvsd qui tourne encore à partir de fichiers supprimés est arrêté
  au démarrage, et celui qui est installé démarre.
- **Ce qui efface les données.** Seule la suppression du dossier de données. Pour repartir de zéro
  volontairement, supprimez le paquet, supprimez le dossier de données, puis installez : le premier
  démarrage crée un nouveau dossier de données, comme le décrit [Où commence le dossier de
  données](#où-commence-le-dossier-de-données).

Une panne de disque est autre chose : le dossier de données est sur les disques du NAS lui-même. Gardez des
sauvegardes sur une autre machine ([Sauvegardes](#sauvegardes)) ; chaque sauvegarde contient vos dossiers de projet,
vos dossiers HOME, les réglages de l’App Builder et le dépôt de paquets.

## La commande bvs

`bvs` est la ligne de commande du paquet, dans `/usr/bin/bucketvs/bin`. Elle est dans le `PATH` de chaque processus
que bvsd démarre, tâches cron comprises. Tant que le paquet tourne, `/usr/bin/bvs` est un lien vers elle, donc une
session SSH sur le NAS l’appelle aussi par son nom ; l’arrêt du paquet retire ce lien.

Chaque commande qui parle à bvsd par son socket CLI demande root, car le socket n’appartient qu’à root : `user`,
`jobs` et, tant que bvsd tourne, `tc install`, `tc remove` et `env create`, `clone`, `export` et `delete`.
`env shell`, `env run` et `qbuild` demandent root aussi : elles démarrent `bvs-chroot`. En tant qu’autre utilisateur,
une commande qui a besoin du socket s’arrête avec « bvsd's CLI socket … is open to root only: run bvs as root ».

```text
bvs env list
bvs env create <name> --from <rootfs-id> [--package <name>]...
bvs env shell <name>
bvs env run <name> -- <command> [<arg>...]
bvs env clone <name> <new-name>
bvs env export <name>
bvs env delete <name>
bvs tc list
bvs tc install <id>
bvs tc remove <id>
bvs use <id>
bvs jobs
bvs user list
bvs user password <name>
bvs user totp-reset <name>
bvs guide [--path] [en|pl|de|fr|it|nl|sv|ja|zh-TW]
bvs qbuild [--env <name>] [<qbuild argument>...]
bvs kernel profile [--json]
bvs kernel sources
bvs kernel prepare [--source <id>] [--env <name>] [--option NAME=VALUE]... [--full]
bvs kernel make [--project <folder> | --dir <tree directory>] [--build-only] [<target>...]
bvs kernel check <file.ko | module>
bvs kernel load <module>
bvs kernel unload <module>
```

- Tant que bvsd tourne, les installations et les tâches d’environnement vont à bvsd par son socket CLI, donc le
  panneau les affiche, et `bvs` les suit jusqu’à leur fin. Sans bvsd, elles s’exécutent dans le processus `bvs`
  lui-même ; `--local` avant la commande le fait exprès.
- `bvs jobs` et les commandes `user` ont besoin de bvsd.
- `bvs guide` ouvre ce guide avec le `less` du paquet dans un terminal, et affiche sinon, ou avec `--path`, le chemin
  de son fichier. Sans langue, il suit la première variable définie parmi `LC_ALL`, `LC_MESSAGES` et `LANG` :
  `de_DE.UTF-8` ouvre le guide allemand, `zh_TW` et `zh_HK` le guide en chinois traditionnel, et une langue dans
  laquelle le guide n’est pas écrit (`C`, `zh_CN`) le guide anglais.

## Fichiers et dossiers

| Chemin | Contenu |
|---|---|
| `/usr/bin/bucketvs` | les programmes du paquet (un lien vers son dossier d’installation) |
| `/usr/bin/bucketvs/etc/data-root` | le chemin du dossier de données |
| `/usr/bin/bucketvs/var/run/bvsd.sock` | le socket d’API du panneau (nginx l’utilise) |
| `/usr/bin/bucketvs/var/run/bvsd-cli.sock` | le socket de la commande `bvs`, root seulement |
| `<dossier de données>/etc/` | réglages, comptes, sessions, jetons, les certificats, le `nginx.conf` généré (secrets avec le mode 600) |
| `<dossier de données>/log/` | les journaux |
| `<dossier de données>/tmp/` | fichiers temporaires de chaque processus |
| `<dossier de données>/home/<utilisateur>/` | le dossier personnel de chaque compte |
| `<dossier de données>/jobs/` | opérations, tâches cron et leurs exécutions, avec leurs journaux |
| `<dossier de données>/toolchains/` | le cache de téléchargement, les toolchains et runtimes installés, les exports |
| `<dossier de données>/envs/` | environnements |
| `<dossier de données>/projects/` | espaces de travail, visibles en tant que `/projects` dans les environnements |
| `<dossier de données>/trash/` | éléments supprimés, gardés 7 jours |

Les modules ajoutent leurs propres dossiers ; leurs sections les nomment.
