# Bucket & Code Server IDE: gebruikershandleiding

Bucket & Code Server IDE (QPKG `bucketvs`) bewaart broncode en bestanden op een QNAP-NAS en bouwt daar software. Het
pakket brengt een eigen webserver, eigen tools en eigen accounts mee.

Deze handleiding beschrijft het pakket zoals het op de NAS draait.

## Inhoud

- [Wat er op de NAS draait](#wat-er-op-de-nas-draait)
- [Het pakket installeren](#het-pakket-installeren)
- [Eerste aanmelding](#eerste-aanmelding)
- [Werken in het paneel](#werken-in-het-paneel)
- [Accounts](#accounts)
- [De datamap](#de-datamap)
- [Netwerk](#netwerk)
- [Achter een reverse proxy (Plesk)](#achter-een-reverse-proxy-plesk)
- [Hostnamen voor de modules](#hostnamen-voor-de-modules)
- [Publiceren op internet (Cloudflare Tunnel)](#publiceren-op-internet-cloudflare-tunnel)
- [Modules](#modules)
- [Git](#git)
- [CI-runners](#ci-runners)
- [SVN en Bucket](#svn-en-bucket)
- [Editor en AI-tools](#editor-en-ai-tools)
- [Toolchains en omgevingen](#toolchains-en-omgevingen)
- [App Builder](#app-builder)
- [Pakketrepository](#pakketrepository)
- [Kernelwerkplaats](#kernelwerkplaats)
- [Cron-taken en bewerkingen](#cron-taken-en-bewerkingen)
- [Back-ups](#back-ups)
- [Meldingen](#meldingen)
- [Logboeken en de supportbundel](#logboeken-en-de-supportbundel)
- [Updates](#updates)
- [Het commando bvs](#het-commando-bvs)
- [Bestanden en mappen](#bestanden-en-mappen)

## Wat er op de NAS draait

| Onderdeel | Wat het doet | Waar het luistert |
|---|---|---|
| nginx | de enige deur op het LAN: serveert het paneel en geeft elk pad door aan zijn module | 9950 (HTTP), 9951 (HTTPS) |
| bvsd | start en bewaakt alle andere processen, houdt de accounts en instellingen bij, voert taken en cron-taken uit, beantwoordt het paneel | twee Unix-sockets in `/usr/bin/bucketvs/var/run/` |
| Git (Forgejo) | Git-hosting met een webinterface | een Unix-socket in `/usr/bin/bucketvs/var/run/forgejo/`, en 9952 voor Git via SSH |
| SVN en Bucket (httpd) | Subversion-repository’s en WebDAV-mappen met geschiedenis | 127.0.0.1:9961, of de volgende vrije poort als een ander programma hem bezet |
| Editor (code-server) | VS Code in de browser, één instantie per account | een Unix-socket per account in `/usr/bin/bucketvs/var/run/ide/` |
| CI | de Forgejo Actions-runner | geen eigen poort; nginx luistert ervoor op 127.0.0.1:9960 zolang de module aan staat, of op de volgende vrije poort als een ander programma 9960 bezet |

De kern (nginx, bvsd, het paneel, cron-taken, toolchains) staat altijd aan. Git, CI, SVN en Bucket, en de Editor zijn
modules die een admin aan- en uitzet. Op elk adres van de NAS antwoorden de modules onder hun paden: `/git/`, `/svn/`,
`/bucket/`, `/ide/`. Het paneel staat op `/`, en deze handleiding op `/guide/`.

Zolang het pakket draait, staan zijn programma’s in `/usr/bin/bucketvs/bin`. Alles wat het pakket bewaart (accounts,
instellingen, repository’s, omgevingen, logboeken) staat in één map op een datavolume: de [datamap](#de-datamap).

## Het pakket installeren

Het pakket vereist QTS 6.0 of QuTS hero 6.0 of nieuwer, op een NAS met x86_64 of ARM 64-bit; er is één pakket per
architectuur (`bucketvs_<versie>_x86_64.qpkg`, `bucketvs_<versie>_arm_64.qpkg`). Het is ondertekend met een
codesigneercertificaat: installeer het vanuit App Center (een repository die het aanbiedt, of **Handmatig
installeren** met het gedownloade bestand). App Center controleert de handtekening; apps zonder geldige handtekening
hoeven niet toegestaan te worden.

De installatie maakt de servicegebruiker `bucketvs` aan (voor Forgejo en httpd), de koppeling `/usr/bin/bucketvs` naar
de installatiemap, en start bvsd. De systeemlogboeken noemen de datamap.

### Waar de datamap begint

Bij de eerste start is de datamap `BucketVS` op het volume waarop het pakket is geïnstalleerd,
bijvoorbeeld `/share/CACHEDEV1_DATA/BucketVS` (QTS) of `/share/ZFS2_DATA/BucketVS` (QuTS hero). Later kan hij vanuit
het paneel naar een ander volume worden verplaatst ([De datamap
verplaatsen](#de-datamap-verplaatsen)).

Het pakket onthoudt zijn datamap in `/etc/config/bucketvs-data-root`, een bestand dat het pakket overleeft.
Het verwijderen van het pakket laat de datamap en dit bestand staan, zodat een installatie na een
verwijdering start op dezelfde data en dezelfde accounts. Een start hangt nooit af van een map die er niet is. Is de
datamap weg (verwijderd, op een vervangen schijf, of op een volume dat op dat moment offline is), dan maakt
de start `BucketVS` op het volume van het pakket, een nieuwe map tenzij er al een map met die naam staat, en in een
nieuwe map stelt de [eerste aanmelding](#eerste-aanmelding) het wachtwoord van de beheerder in. Dat geldt na een
herinstallatie en voor een geïnstalleerd pakket, ook bij het opstarten. De systeemlogboeken bevatten daarover een
waarschuwing, en `.prev`-bestanden bewaren het oude pad: `/etc/config/bucketvs-data-root.prev` en bij een geïnstalleerd
pakket ook `etc/data-root.prev` in de installatiemap. Stond de oude map op een volume dat op dat moment alleen offline
was, breng het volume dan online, stop het pakket, kopieer dat bestand over `etc/data-root` in de installatiemap
(bijvoorbeeld `/share/CACHEDEV1_DATA/.qpkg/bucketvs/etc/data-root`) en start het pakket: het draait dan weer op de oude
data.

### Het paneel openen vanuit App Center

**Openen** bij het pakket in App Center opent het paneel op poort 9950 (HTTP) of 9951 (HTTPS). Als de poorten in het
paneel worden gewijzigd ([Poorten](#poorten)), geeft het pakket de nieuwe bij zijn volgende start door aan App Center.

### Automatisch starten vanuit een SSH-sessie

Zolang de schakelaar van het pakket in App Center aan staat, start de NAS het pakket bij het opstarten. In een
SSH-sessie op de NAS zet je als admin dezelfde schakelaar met het servicescript:

```sh
/etc/init.d/bucketvs.sh disable   # de volgende start wordt geweigerd, ook bij het opstarten
/etc/init.d/bucketvs.sh enable    # het pakket start weer bij het opstarten
```

Beide opdrachten schrijven alleen de schakelaar (`Enable` in `/etc/config/qpkg.conf`), tonen wat ze hebben ingesteld
en schrijven dezelfde regel in de systeemlogboeken. Een draaiend pakket blijft na `disable` draaien, en `enable` start
niets: dat doen `stop` en `start`, en `start` weigert zolang het pakket uit staat. `status` zegt of bvsd draait.

## Eerste aanmelding

Open het paneel op `https://<NAS-adres>:9951/` of `http://<NAS-adres>:9950/`. Het HTTPS-adres gebruikt eerst een
zelfondertekend certificaat, dus de browser waarschuwt totdat het apparaat het vertrouwt; de sectie [Het
certificaat](#het-certificaat) laat zien hoe je de vingerafdruk vergelijkt.

Een nieuwe installatie heeft geen account. Zolang de eerste beheerder geen wachtwoord heeft, opent het paneel op één
scherm, ‘Het beheerderswachtwoord instellen’, voor het account `administrator`. Typ een wachtwoord twee keer: minstens
10 tekens en hoogstens 72 bytes. Opslaan maakt het account aan en meldt je aan.

```mermaid
flowchart TD
    start["Het pakket start<br/>zonder account"] --> screen["Het beheerderswachtwoord instellen<br/>60 minuten, alleen lokaal netwerk"]
    screen -->|"opgeslagen"| panel["administrator aangemeld"]
    screen -->|"60 minuten voorbij"| restart["Het pakket in App Center herstarten:<br/>een nieuw venster van 60 minuten"]
    restart --> screen
```

- Het scherm neemt een wachtwoord aan tot 60 minuten na de start van het pakket. Daarna meldt het dat het
  instelvenster gesloten is; Bucket & Code Server IDE herstarten in App Center opent een nieuw venster van 60 minuten.
- Het neemt alleen een wachtwoord aan vanuit het lokale netwerk: vanaf de NAS zelf, of vanaf een adres in een
  privébereik (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `169.254.0.0/16`, `fc00::/7`, `fe80::/10`).
- Zolang het scherm open is, tonen de QTS-systeemlogboeken voor elk LAN-adres van de NAS een informatieregel zoals:

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

- Het weigert een verzoek dat via een proxy kwam (een verzoek met `X-Forwarded-For`, `X-Forwarded-Host`,
  `X-Forwarded-Proto`, `X-Real-IP` of `Forwarded`), ook van een proxy op het LAN: open het paneel rechtstreeks vanaf
  een computer in het lokale netwerk om het wachtwoord in te stellen.

Vanuit een SSH-sessie op de NAS maakt `/usr/bin/bucketvs/bin/bvs user password administrator` het account aan als er
nog geen is en toont een willekeurig wachtwoord; het paneel vraagt dan bij de eerste aanmelding om een eigen
wachtwoord ([Als een admin buitengesloten is](#als-een-admin-buitengesloten-is)).

Na de eerste aanmelding zegt het Overzicht dat alleen de kern draait. De tegel van elke module zegt wat de module
biedt en heeft een knop die haar aanzet, zoals **Git aanzetten**: zet de modules aan die je nodig hebt
([Modules](#modules)).

## Werken in het paneel

Het paneel werkt als een code-editor: elke pagina opent in een tabblad, en een opdrachtenpalet bereikt de pagina’s en
acties via hun naam.

- **Activiteitenbalk** (de pictogrammen links). Elk pictogram opent zijn pagina en toont zijn boom in de zijbalk: de
  modules, repository’s en buckets, apps, pakketten, omgevingen, taken en back-ups. Een stip op een pictogram markeert
  een module met problemen, een mislukte build of een mislukte back-up. Klikken op het pictogram van de voorste pagina
  verbergt de zijbalk; Ctrl+B doet hetzelfde.
- **Tabbladen.** Een pagina die met één klik is geopend, is een voorbeeld, in cursief: de volgende pagina opent op
  haar plaats. Bewerk de pagina of dubbelklik op haar tabblad om haar te houden. De tabbladen komen terug na herladen,
  voor elk account in elke browser. In een venster van minstens 1280 pixels breed toont **Het tabblad naar de andere
  kant verplaatsen** (`Ctrl+\`) twee pagina’s naast elkaar.
- **Objecten en hun menu’s.** Eén klik op een rij van de zijbalk, een tabelrij of een kaart opent dat object: de
  pagina komt naar voren, met het object in een groene rand of met de details open. Het adres noemt het object, dus
  terug, vooruit en een gekopieerde link brengen je er weer. Dubbelklikken houdt het tabblad. Rechtsklikken, Shift+F10
  of lang drukken op een aanraakscherm opent het menu van het object: de manieren om het te openen (in dit tabblad, in
  een gehouden tabblad, aan de andere kant, in een nieuw browsertabblad), wat de pagina ermee kan, en het kopiëren van
  de naam en het adres. Het menu van een tabblad sluit het, de andere tabbladen, de tabbladen rechts ervan of alle.
  Alleen een element met ↗ verlaat het paneel. Het pictogram **Beheer** opent de pagina Systeem.
- **Opdrachtenpalet.** Ctrl+Shift+P (of Ctrl+K, of F1) toont de opdrachten die jouw rol mag uitvoeren: een pagina
  openen, een app bouwen of publiceren, een back-up starten, een module herstarten, een shell in een omgeving openen.
  Ctrl+P gaat met een paar letters van de naam naar een pagina, een app, een repository, een omgeving of een pakket.
- **Onderpaneel** (Ctrl+J). **Taken** toont de lopende en recente taken met hun voortgang en logboek. **Problemen**
  somt op wat de modules melden: mislukte builds, uitgeschakelde back-updoelen, modules met problemen, een certificaat
  dat bijna verloopt, en de bevindingen van de ELF-inspecteur voor de voorste app. **Uitvoer** volgt de taak die je het
  laatst hebt gestart.
- **Verplaatsen en de grootte wijzigen.** Sleep een rand om de grootte te wijzigen van wat eraan grenst: de zijbalk,
  het onderpaneel, twee pagina’s naast elkaar, of twee delen van een pagina naast elkaar, zoals een wizard en zijn
  samenvatting of de bestandenlijst van een project en het bestand. Een dubbelklik op een rand zet hem terug. Een
  dialoogvenster verplaats je aan zijn titel en de grootte wijzig je aan een rand of aan de hoek rechtsonder; een
  dubbelklik op de titel zet het terug in het midden. Om het onderpaneel naast de pagina’s te zetten, sleep je zijn
  kop naar de rechterrand van de editor of klik je op de knop naast zijn ×; sleep het naar de onderrand, of klik nog
  eens op de knop, om het terug te zetten. Een tabblad kun je naar een andere plek in de tabbladbalk of naar de andere
  kant slepen. Het paneel houdt elke grootte en plaats bij voor je account in deze browser, en **Indeling
  terugzetten** (in het opdrachtenpalet, of onder de knop van je account onder in de activiteitenbalk) zet ze allemaal
  terug. Met het toetsenbord verplaatsen de pijltjestoetsen een gekozen rand (met Shift in grotere stappen, Home zet
  hem terug), Ctrl+Shift+Links en Rechts verplaatsen een gekozen tabblad, en een dialoogvenster heeft bovenaan een
  balk en rechtsonder een greep waarmee de pijltjestoetsen het verplaatsen en de grootte wijzigen. Op een telefoon
  blijven dialoogvensters en het onderpaneel waar ze zijn.
- **Statusbalk.** Modulestatussen, lopende taken, problemen, vrije ruimte op het datavolume, het certificaat, het
  aangemelde account, de taal (de editor neemt hem over bij de volgende start, in het Engels voor Zweeds en Nederlands; Git volgt hem, tenzij je daar een andere kiest), het thema (donker, licht, zoals het systeem, diepblauw, grafiet of papier: het hoort bij je account, en de editor en Git volgen het, tenzij je daar een ander kiest) en de versie. Elk item opent de plek om
  beter te kijken.
- **Toetsenbord.** Ctrl+/ of ? toont de sneltoetsen. Een browsertabblad houdt Ctrl+W en Ctrl+Tab voor zichzelf, daarom
  sluit het paneel een tabblad met Alt+W en wisselt het van tabblad met Alt+PageDown en Alt+PageUp.
- **Op een telefoon** staan de eerste activiteiten onderaan, opent **Meer** de zijbalk met de rest, en zijn de open
  pagina’s een lijst bovenaan.

## Accounts

Het paneel heeft eigen accounts. QTS-accounts melden zich er niet bij aan.

### Rollen

| Rol | Wat hij mag |
|---|---|
| viewer | leest het paneel; de pagina’s Back-ups, Opslag, Gebruikers en Logboeken zijn voor admins. In Forgejo maakt hij geen repository aan, forkt of importeert hij er geen, maakt hij geen organisatie aan en pusht hij nooit; leest de SVN-repository’s en buckets waarop hij rechten heeft en schrijft in geen ervan |
| member | leest wat een viewer leest, en schrijft: pusht naar de Git-repository’s waarvoor hij schrijftoegang krijgt, schrijft in de SVN-repository’s en buckets waarop hij lezen en schrijven heeft, en zet daar oudere versies van bestanden terug. In Forgejo maakt hij geen repository aan, forkt of importeert hij er geen en maakt hij geen organisatie aan, en hij kan niet pushen naar een repository waarvan een account onder developer eigenaar of beheerder is, en, zolang de CI-module aan staat, ook niet naar een repository waarvan de Actions aan staan. Hij krijgt geen editor, cron-taken of builds |
| developer | opent daarnaast de editor, leest de `bvs use`-regels van geïnstalleerde toolchains, maakt eigen cron-taken aan, wijzigt en start ze, maakt en bouwt apps, publiceert pakketten in de repository (een admin zet ze in de lijst), controleert kernelmodules, en maakt Git-repository’s aan waarvan de workflows op de NAS draaien zolang de CI-module aan staat |
| admin | alles: accounts, instellingen, opslag, netwerk, modules, toolchains, omgevingen, elke cron-taak, apps activeren, kernelmodules laden, pakketten in de repository in de lijst zetten |

De rol developer heeft root-rechten op de NAS: cron-opdrachten draaien als root, net als de terminal van de editor en, zolang de
CI-module aan staat, de workflows van de repository’s waarin hij mag pushen. Geef deze rol alleen aan mensen aan wie
je ook het admin-wachtwoord van de NAS zou geven. Wie alleen in repository’s schrijft, heeft genoeg aan de rol member.

Onder **Jouw account** ziet ieder zijn eigen rol en wat die toestaat, met een link naar deze sectie. Een pagina boven
iemands rol zegt welke rol ze vereist, en wat alleen een beheerder wijzigt, staat daar ook zo vermeld. Netwerk, Git en
CI tonen hun instellingen aan alle anderen als waarden, met „Niet ingesteld” waar er een leeg is.

Workflows draaien als root op de NAS, dus houdt bvsd de Actions uit in elke Git-repository waarvan een member of
viewer eigenaar is of waarin zo’n account kan schrijven: zo’n account schrijftoegang tot een repository geven zet de
Actions ervan binnen een minuut uit, en de Git-pagina toont die repository’s aan admins. Om daar Actions te
gebruiken, haal je eerst die schrijftoegang in Forgejo weg, of draag je een repository waarvan zo’n account eigenaar
is over aan het account van een developer.

### Accounts aanmaken en wijzigen

Een admin beheert de accounts op de pagina Gebruikers.

- Een nieuw account begint met de rol viewer. Developer en admin kies je bewust: de rol developer is root op de NAS
  (hierboven).
- Een nieuw account begint zonder cron-taken, ook als een account met dezelfde naam er had.
- Een naam heeft 2 tot 32 tekens: eerst een kleine letter, dan kleine letters, cijfers, `.`, `_` of `-`. Twee van `.`,
  `_`, `-` achter elkaar, of één aan het eind, worden geweigerd. Net als de namen die Forgejo reserveert (`admin`,
  `api`, `user` en andere), `bucketvs` (de systeemgebruiker van het pakket), `cli` en `system` (bvsd gebruikt die
  allebei zelf).
- ‘Moet bij de eerste aanmelding een eigen wachtwoord kiezen’ laat het account zijn eigen wachtwoord instellen voordat
  het iets anders kan doen.
- Een account uitschakelen beëindigt meteen zijn sessies en stopt zijn API-tokens. Zijn data blijven. Zijn
  [cron-taken](#geplande-taken) blijven ook, maar draaien niet zolang het uitgeschakeld is, en een uitvoering van zo’n
  taak die loopt, wordt gestopt.
- Een account de rol member of viewer geven doet hetzelfde met zijn cron-taken: ze blijven, draaien niet zolang zijn
  rol onder developer ligt, en een uitvoering die loopt, wordt gestopt.
- Een viewer pusht nooit naar Git, ook niet waar hij in Forgejo schrijftoegang heeft gekregen. Een account dat moet
  pushen, krijgt de rol member.
- Een account verwijderen haalt het weg met zijn sessies en API-tokens. Zijn thuismap gaat naar
  `trash/home/<naam>-<tijd>` in de datamap, waar de taak ‘De prullenbak legen’ hem na 7 dagen verwijdert,
  en zijn map `tmp/tmux-<naam>` wordt verwijderd. Zijn cron-taken worden verwijderd met hun uitvoeringen en
  logboeken.
- De laatste ingeschakelde admin kan niet worden gedegradeerd, uitgeschakeld of verwijderd.
- ‘Een nieuw wachtwoord instellen’ beëindigt de sessies van het account en trekt zijn API-tokens in.

### Wachtwoorden en sessies

- Een wachtwoord heeft minstens 10 tekens en hoogstens 72 bytes.
- Je eigen wachtwoord wijzigen (onder Jouw account) beëindigt je andere sessies en trekt je API-tokens in. De sessie
  waarin je het hebt gewijzigd blijft aangemeld.
- Een sessie eindigt 12 uur na haar laatste verzoek, en in elk geval 7 dagen na de aanmelding. Een account houdt
  hoogstens 32 sessies; nog een keer aanmelden beëindigt de sessie die het langst niet is gebruikt.
- Het sessiecookie is `HttpOnly` en `SameSite=Lax`. Het wordt als `Secure` gemarkeerd als de browser de NAS via HTTPS
  bereikte, rechtstreeks of via een [vertrouwde proxy](#vertrouwde-proxys).

### Verkeerde wachtwoorden

- Vijf mislukte aanmeldingen binnen 15 minuten vanaf één adres blokkeren dat adres 15 minuten, zelfs met het juiste
  wachtwoord. Een IPv6-client telt als zijn /64-netwerk.
- Vijf mislukkingen voor één account vanaf één adres blokkeren dat account vanaf dat adres 15 minuten.
- Mislukkingen voor één account vanaf veel adressen vertragen de volgende aanmeldingen: 125 ms na één mislukking,
  verdubbelend tot 2 seconden. Vanaf het lokale netwerk blokkeren ze het nooit.
- Vanaf internet (elk adres buiten het lokale netwerk) vergrendelen 20 mislukte aanmeldingen voor één account sinds
  zijn laatste geslaagde het daar, van welke adressen ze ook kwamen; dan wordt daar ook het juiste wachtwoord
  geweigerd. Vanaf het lokale netwerk meldt het account zich nog steeds aan. Een beheerder ontgrendelt het op de
  pagina Gebruikers (**Ontgrendelen**) of met `bvs user unlock <naam>`, of geeft het een nieuw wachtwoord. Het aantal
  stelt u in op de pagina Gebruikers onder **Aanmeldingen vanaf internet**; 0 zet deze vergrendeling uit.
- De tellers staan in het geheugen, dus een herstart van bvsd wist ze; een vergrendeling blijft tot ze wordt
  opgeheven.

Achter een proxy telt het adres dat de proxy doorgeeft, maar alleen als de proxy een [vertrouwde
proxy](#vertrouwde-proxys) is. Anders telt elke client als het eigen adres van de proxy, en vijf verkeerde
wachtwoorden van wie dan ook sluiten iedereen 15 minuten buiten.

### Tweefactorauthenticatie

Elk account zet deze voor zichzelf aan onder Jouw account: scan de QR-code met een authenticator-app (of typ de
sleutel in) en voer dan de code van 6 cijfers in die de app toont. Daarna vraagt de aanmelding na het wachtwoord om
een code.

- Codes volgen RFC 6238: 6 cijfers, elke 30 seconden een nieuwe code. Een code die 30 seconden te vroeg of te laat is,
  wordt aangenomen. Dezelfde code wordt nooit twee keer aangenomen.
- Als een verse code faalt, loopt de klok van de telefoon waarschijnlijk verkeerd; zet hem op automatische tijd.
- Uitzetten vereist het wachtwoord van het account.
- Bij een verloren telefoon zet een admin haar uit op de pagina Gebruikers. Het wachtwoord alleen meldt dan aan.

### API-tokens

Scripts en CI gebruiken API-tokens in plaats van een wachtwoord. Onder Jouw account maakt ‘Nieuw token’ er een:

- Het ziet eruit als `bvs_` gevolgd door 40 hexadecimale cijfers, en het wordt één keer getoond. bvsd bewaart alleen
  de hash.
- Een script stuurt het als `Authorization: Bearer bvs_...`.
- De bereiken zijn `read`, `write` en `admin`, die staan voor de rollen viewer, developer en admin. Een token handelt
    met zijn hoogste bereik, nooit boven de huidige rol van zijn eigenaar, dus een account degraderen degradeert ook
  zijn tokens. Geen bereik staat voor member: een member maakt `read`-tokens.
- Een nieuw wachtwoord trekt alle tokens van de eigenaar in. De tokens van een uitgeschakelde eigenaar werken niet
  meer.
- Een token kan geen wachtwoorden of tweefactorauthenticatie wijzigen, en kan geen tokens maken of verwijderen:
  daarvoor is een aangemelde sessie nodig. Een uitgelekt token kan zijn eigenaar dus niet buitensluiten.

### Als een admin buitengesloten is

De NAS-admin kan elk account resetten vanuit een SSH-sessie op de NAS. Het commando `bvs` praat met bvsd via een
socket die alleen root kan openen (`/usr/bin/bucketvs/var/run/bvsd-cli.sock`, modus 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 <naam>` stelt een nieuw willekeurig wachtwoord van 20 tekens in en toont het. Het account moet
  bij de volgende aanmelding een eigen wachtwoord kiezen. Zijn sessies eindigen en zijn API-tokens worden ingetrokken.
  Op een nieuwe installatie zonder account maakt `bvs user password administrator` het account aan.
- `bvs user totp-reset <naam>` zet de tweefactorauthenticatie voor het account uit.
- `bvs user unlock <naam>` heft de vergrendeling van het account op na te veel verkeerde wachtwoorden vanaf internet.

Elk verzoek op die socket handelt als de ingebouwde admin `cli` en wordt geschreven naar `log/audit.log` in de
datamap. Voor deze commando’s moet bvsd draaien.

## De datamap

De datamap is de map die alles bevat wat het pakket bewaart. Het pad is de ene regel in
`/usr/bin/bucketvs/etc/data-root`. De pagina Opslag toont hem, met het volume waarop hij staat en de vrije ruimte
daar.

bvsd start alleen op een datamap die een map op een echt volume is. De start van het pakket controleert dat
eerst: een ontbrekende map, of een map op tmpfs of ramfs, geeft een nieuwe datamap op het
installatievolume, zoals [Waar de datamap begint](#waar-de-datamap-begint) beschrijft. Een
volume dat offline is, laat zijn koppelpunt achter als een lege map in het RAM van de NAS, en instellingen die daar
worden geschreven zouden na de volgende herstart weg zijn. bvsd zelf weigert zo'n map alleen als het met de hand wordt
gestart, en de reden gaat dan naar de QTS-systeemlogboeken.

### Wat bvsd elk proces meegeeft

QTS houdt `/tmp` in een kleine RAM-schijf en `/root` op een RAM-schijf die een herstart leegmaakt. Daarom krijgt elk
proces dat bvsd start (moduleprogramma’s, cron-taken, installaties) zijn tijdelijke bestanden en zijn thuismap in de
datamap:

| Variabele | Waarde |
|---|---|
| `HOME` | `<datamap>/home/<gebruiker>` |
| `TMPDIR` | `<datamap>/tmp` |
| `XDG_CACHE_HOME`, `XDG_CONFIG_HOME` | `.cache` en `.config` in die thuismap |
| `npm_config_cache` | `.npm` in die thuismap |
| `PATH` | `~/.local/bin`, `~/.npm-global/bin`, dan `bin`, `sbin` en `node/bin` van het pakket, dan de mappen van het systeem |
| `SSL_CERT_FILE`, `GIT_SSL_CAINFO`, `CURL_CA_BUNDLE` | de CA-bundel van het pakket, `/usr/bin/bucketvs/etc/ssl/cacert.pem` |
| `TZ` | de tijdzone van de NAS, zoals ingesteld in QTS |
| `SHELL`, `EDITOR` | `bash` en `nano` van het pakket |
| `LANG`, `LESSCHARSET` | `C.UTF-8` en `utf-8`, zodat bestandsnamen met letters als é en ë verschijnen zoals ze zijn |

### De datamap verplaatsen

De pagina Opslag verplaatst de hele datamap naar een andere map onder `/share`. Kies een gedeelde map of
een volume; de data gaan naar een nieuwe map met de naam `BucketVS` daarin. Om een map zelf te gebruiken, open je een
lege map met de naam `BucketVS`. Gedeelde mappen maak je aan in QTS (Configuratiescherm, Gedeelde mappen); binnen een
gedeelde map of een volume kan de pagina een nieuwe map aanmaken. De kiezer opent waar de data nu staat en zegt dat;
open van daaruit de gedeelde map of het volume waar ze naartoe moet.

De pagina controleert het doel voordat de verplaatsing begint en somt elke reden op waarom ze er een weigert:

- het pad moet onder `/share` liggen, en niet `/share` zelf zijn;
- geen `:`, `&`, `|` of stuurteken in enig deel van het pad, ook niet in het pad waar een koppeling naartoe leidt;
- niet de huidige datamap, geen map daarin, geen map die hem bevat;
- een nieuwe map, of een lege;
- een bestandssysteem dat Unix-eigenaars en -rechten bewaart (FAT, exFAT en NTFS worden geweigerd: Forgejo en SVN
  hebben eigenaars en modi nodig);
- een volume dat is gekoppeld met `exec`, `dev` en `suid`, wat build-omgevingen nodig hebben;
- beschrijfbaar, met vrije ruimte voor de data plus 10 %.

```mermaid
flowchart TD
    check["Het doel opnieuw controleren<br/>vrije ruimte: de data + 10 %"] --> stop["De modules stoppen<br/>het paneel blijft aan"]
    stop --> copy["Kopiëren met<br/>rsync -aHAX --numeric-ids"]
    copy --> verify["De kopie controleren<br/>bestanden, bytes, eigenaars, modi, tijden,<br/>sha256 van maximaal 1000 bestanden"]
    verify --> switch["De hoofdmap omschakelen<br/>in /usr/bin/bucketvs/etc/data-root"]
    switch --> start["bvsd start opnieuw op de nieuwe map<br/>en start de modules"]
    start --> old["De oude kopie blijft, gemarkeerd MOVED-TO,<br/>totdat je hem verwijdert"]
    copy -->|mislukt| failed["De oude map blijft in gebruik,<br/>de modules starten weer"]
    verify -->|een verschil| failed
```

Terwijl de verplaatsing loopt:

- Git, CI, SVN en Bucket, en de Editor antwoorden niet. Het paneel blijft aan en toont de voortgang.
- Er start geen nieuwe cron-uitvoering. Een uitvoering die al liep, gaat door.
- Een module aan- of uitzetten wordt geweigerd tot de verplaatsing klaar is.

Na het omschakelen stopt bvsd en start het zichzelf opnieuw op de nieuwe map, dus het paneel is een paar seconden weg.

Als de verplaatsing mislukt, blijft de oude datamap in gebruik en starten de modules weer. De fout van de
taak zegt wat er misging en wat er van de kopie is geworden:

- Een vol volume, of annuleren, verwijdert wat de verplaatsing heeft aangemaakt.
- Elke andere fout (het kopiëren, de controle, schrijven naar schijf) houdt de kopie, gemarkeerd met een bestand met
  de naam `INCOMPLETE`, en zet haar op de pagina Opslag onder ‘Achtergebleven kopieën van mislukte verplaatsingen’.
- Een stop van het pakket tijdens de verplaatsing houdt de oude map als datamap. Bij de volgende start
  meldt bvsd de onvoltooide verplaatsing in de systeemlogboeken en zet de kopie in de lijst.

### De oude kopie en achtergebleven kopieën

Na een geslaagde verplaatsing blijft de oude datamap waar hij was, met een bestand met de naam `MOVED-TO`
erin. Niets gebruikt hem. ‘De oude kopie verwijderen’ op de pagina Opslag verwijdert hem, en ‘Deze kopie verwijderen’
verwijdert een achtergebleven kopie van een mislukte verplaatsing. Beide verwijderingen blijven op één bestandssysteem
en volgen nooit een koppeling. Een map die een koppelpunt is of de eigen map van een gedeelde map, wordt geleegd en
behouden.

## Netwerk

De pagina Netwerk bevat de poorten, de modus voor gewoon HTTP, de hostnamen van de modules, de vertrouwde proxy’s, de
openbare URL en het certificaat. Opslaan herstart wat de wijziging nodig heeft en toont dat.

### Poorten

| Instelling | Standaard | Wat het is |
|---|---|---|
| HTTPS | 9951 | het paneel en de modules via TLS |
| HTTP | 9950 | het paneel en de modules via gewoon HTTP |
| Git via SSH | 9952 | Git via SSH |
| SSH-poort in kloon-URL’s | 0 | de poort die SSH-kloonadressen tonen; 0 betekent de SSH-poort hierboven. Stel hem in als een router een andere poort naar de NAS doorstuurt. |
| SSH-host in kloon-URL’s | leeg | de naam of het adres dat SSH-kloonadressen tonen. Leeg betekent de Git-hostnaam, anders de host van de openbare URL, anders het LAN-adres van de NAS; voor een naam die via de Cloudflare Tunnel is gepubliceerd, het LAN-adres en de poort voor Git via SSH. Stel hem in op een naam die alleen in DNS staat of op het adres van de router als de router de SSH-poort doorstuurt. |

Poorten liggen tussen 1024 en 65535 en verschillen van elkaar. De moduleporten op 127.0.0.1 (9960 en 9961) worden
geweigerd, net als de poort die httpd of de server van de CI-runner gebruikt als een ander programma 9960 of 9961
heeft ingenomen (de foutmelding noemt hem). Als opslaan nginx op zijn oude poorten laat omdat het een nieuwe niet kon
gebruiken, zegt een melding waarom. nginx luistert ook op IPv6 als de NAS dat heeft.

### Gewoon HTTP

- ‘Serveren’ (de standaard): het paneel en de modules antwoorden ook op gewoon HTTP. Houd dit aan als een proxy op het
  LAN gewoon HTTP naar poort 9950 praat.
- ‘Doorsturen naar HTTPS’: een verzoek via gewoon HTTP krijgt een omleiding (307) naar de HTTPS-poort. Een verzoek van
  een vertrouwde proxy die meldt dat zijn client HTTPS gebruikte, wordt via gewoon HTTP bediend, zodat zo’n proxy niet
  in een lus raakt.

De aanmeldpagina waarschuwt als ze via gewoon HTTP binnenkwam: het wachtwoord gaat dan leesbaar over het netwerk.

### Het certificaat

Bij de eerste start maakt bvsd een zelfondertekend certificaat (ECDSA P-256) voor:

- de hostnaam van de NAS, de hostnamen van de modules en `localhost`;
- de LAN-adressen van de NAS en `127.0.0.1`.

Het is 825 dagen geldig, het maximum dat Apple-apparaten aannemen, ook voor een certificaat dat met de hand is
vertrouwd. bvsd controleert het bij de start en eenmaal per dag, en maakt een nieuw als er 30 dagen of minder over
zijn, als de verzameling namen verandert, of als de sleutel niet past. Een nieuw certificaat stuurt een
[melding](#meldingen). De pagina Netwerk toont de namen, de geldigheid en de SHA-256-vingerafdruk: vergelijk de
vingerafdruk voordat een apparaat het vertrouwt.

‘Een eigen certificaat toevoegen’ neemt een certificaatketen en de bijbehorende privésleutel in PEM-vorm:

- eerst het servercertificaat, dan de tussencertificaten;
- een sleutel zonder wachtwoordzin (nginx start zonder toezicht);
- de sleutel moet bij het eerste certificaat horen, elk certificaat moet door het volgende zijn ondertekend, geen
  enkel mag verlopen zijn, en het eerste moet zijn hosts als subject alternative names noemen.

Het paar wordt opgeslagen in `etc/tls/` in de datamap met modus 600. Als een geüpload certificaat
verloopt of deze controles niet meer doorstaat, serveert nginx in plaats daarvan het zelfondertekende en zegt een
melding waarom. Een geüpload certificaat met 30 dagen of minder stuurt ook een melding. ‘Nieuw zelfondertekend
certificaat’ maakt een nieuw zelfondertekend paar en schakelt daar weer op over.

Clients die via een proxy komen, zien het certificaat van de proxy, niet dit.

**Het certificaat downloaden** op de pagina Netwerk bewaart het certificaat dat het paneel serveert, zonder sleutel,
als `<naam>.crt`. Voeg het toe aan de vertrouwde certificaten van een apparaat, dan vertrouwen de browsers van dat
apparaat het paneel; de extensieweergaven van de editor hebben dat nodig ([Extensies](#extensies)). Een nieuw
zelfondertekend certificaat moet je opnieuw vertrouwen.

### Vertrouwde proxy’s

Een lijst van IP-adressen of CIDR-bereiken, zoals `192.168.1.5`, het adres van de proxy vóór de NAS. Alleen voor een
verzoek waarvan de TCP-verbinding van een daarvan komt, neemt nginx over:

- het adres van de client uit `X-Forwarded-For`;
- het schema dat de client gebruikte uit `X-Forwarded-Proto` (alleen `http` of `https` tellen).

Een verzoek van elk ander adres telt als rechtstreeks afkomstig van zijn client, en nginx vervangt de
`X-Forwarded-*`-headers door eigen waarden. Een bereik maakt elke host erin vertrouwd: elk daarvan kan dan het
clientadres en het schema instellen, dus vul het eigen adres van de proxy in. Een bereik van alle adressen
(`0.0.0.0/0`, `::/0`) wordt geweigerd.

### Openbare URL

Alleen het adres, zoals `https://git.example.com`, zonder pad. De secties van de modules zeggen welke koppelingen
ermee beginnen.

## Achter een reverse proxy (Plesk)

De gebruikelijke opzet legt TLS op een proxy met een openbaar certificaat en laat die gewoon HTTP naar poort 9950 op
het LAN praten. De voorbeelden hieronder gebruiken `bvs.example.com` als openbare naam, `192.168.1.20` voor de NAS en
`192.168.1.5` voor de Plesk-server; vul je eigen waarden in.

```mermaid
flowchart LR
    client["Browser, git, WebDAV-client"] -->|"HTTPS 443"| plesk["nginx van Plesk<br/>bvs.example.com<br/>openbaar certificaat"]
    plesk -->|"HTTP 9950<br/>Host, X-Forwarded-For,<br/>X-Forwarded-Proto: https,<br/>Upgrade voor WebSocket"| nas["nginx op de NAS<br/>192.168.1.20"]
    nas --> panel["/ paneel, /api/ bvsd"]
    nas --> mods["/git/, /svn/, /bucket/, /ide/"]
    sshc["git via SSH"] -->|"TCP 9952, niet via de proxy"| nas
```

### Op de NAS

1. Voeg op de pagina Netwerk het adres van de Plesk-server (zoals de NAS het ziet) toe aan de vertrouwde proxy’s.
2. Gewoon HTTP kan op ‘Serveren’ blijven. ‘Doorsturen naar HTTPS’ werkt ook zodra de Plesk-server een vertrouwde proxy
   is.

### In Plesk

Zet deze directieven in de Apache- en nginx-instellingen van het domein, in het veld ‘Extra nginx-directieven’ (in de
Engelse interface ‘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: de editor en zijn terminal
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $http_connection;

    # grote pushes en uploads: geen groottelimiet, gestreamd, een uur stilte toegestaan
    client_max_body_size 0;
    proxy_request_buffering off;
    proxy_buffering off;
    proxy_read_timeout 3600s;
    proxy_send_timeout 3600s;
}
```

`location ~ ^/` is een location met een reguliere expressie. nginx verkiest een passende reguliere expressie boven een
gewone `location /` (Plesk schrijft er zelf een), dus dit blok bedient elk pad van de site, en `proxy_pass` zonder pad
geeft het pad van het verzoek ongewijzigd door. Zet op dezelfde instellingenpagina ‘Statische bestanden rechtstreeks
via nginx serveren’ uit: als dat aan staat, beantwoordt Plesk verzoeken om `.js`-, `.css`- en afbeeldingsbestanden uit
de eigen map van het domein, en laadt het paneel niet.

Zet in de hostinginstellingen van het domein de permanente, SEO-veilige 301-omleiding van HTTP naar HTTPS aan (in de
Engelse interface ‘Permanent SEO-safe 301 redirect from HTTP to HTTPS’). De extra nginx-directieven gelden ook voor de
site van het domein op gewoon HTTP op poort 80; zonder de omleiding antwoordt de site ook daar, en gaan wachtwoorden
onversleuteld over het internet.

Waarom elke regel telt:

| Directief | Wat er zonder niet werkt |
|---|---|
| `Host $http_host` | nginx op de NAS kiest het serverblok op basis van `Host`, dus een module-hostnaam past nooit. bvsd vergelijkt de `Origin` van de pagina met `Host`, inclusief poort, dus elke wijziging in het paneel mislukt met `403 csrf`; `$host` zou een andere openbare poort dan 443 weglaten en op dezelfde manier mislukken. |
| `X-Forwarded-For` | elke client telt als de Plesk-server: vijf verkeerde wachtwoorden van één persoon sluiten iedereen 15 minuten buiten |
| `X-Forwarded-Proto $scheme` | het sessiecookie wordt niet als `Secure` gemarkeerd, en in de modus ‘Doorsturen naar HTTPS’ wordt elk verzoek naar poort 9951 van de openbare naam gestuurd |
| `Upgrade`, `Connection` | de pagina van de editor opent, maar de editor en zijn terminal maken nooit verbinding |
| `client_max_body_size 0`, `proxy_request_buffering off`, de time-outs | voor grote pushes en uploads gelden de eigen groottelimiet en time-outs van de proxy |

`X-Forwarded-For` en `X-Forwarded-Proto` tellen alleen als de Plesk-server bij de vertrouwde proxy’s staat. Als dat
niet zo is, negeert nginx op de NAS ze.

### Git via SSH

Een HTTP-proxy vervoert geen SSH. Git via SSH gaat rechtstreeks naar poort 9952 van de NAS, of via een
TCP-poortdoorsturing op de router. ‘SSH-poort in kloon-URL’s’ op de pagina Netwerk stelt de poort in die de
kloonadressen tonen, en ‘SSH-host in kloon-URL’s’ de naam of het adres, als de openbare naam alleen de proxy
bereikt.

### De opzet controleren

- Het paneel opent op `https://bvs.example.com/`, en aanmelden en een instelling opslaan werken.
- De ontwikkelaarstools van de browser tonen het sessiecookie `bvs_session` met de vlag `Secure`.
- De pagina Logboeken toont `nginx-access`: het eerste veld van elke regel is het eigen adres van de client, niet dat
  van de Plesk-server.

## Hostnamen voor de modules

Een module kan een eigen hostnaam hebben (pagina Netwerk, Hostnamen). nginx serveert die module dan alleen, in de root
van de naam. De naam moet naar de NAS wijzen, of naar de proxy ervoor. Op een proxy heeft elke hostnaam een eigen site
nodig, met dezelfde directieven als hierboven.

| Module | In de root van zijn hostnaam | Daar ook |
|---|---|---|
| Git | Forgejo | `/git/` op elke andere naam antwoordt met 302 naar de Git-hostnaam, met hetzelfde pad, dezelfde query en dezelfde poort |
| Editor | code-server | de aanmeldpagina van het paneel en `/api/`, zodat een gebruiker zich op die naam kan aanmelden |
| SVN en Bucket | httpd | repository’s blijven op `/svn/<naam>` en buckets op `/bucket/<naam>` |

Een module die uit staat, antwoordt met `503` en een zin die dat zegt, op zijn paden en op zijn hostnaam.

### Eén aanmelding voor alle hostnamen

Het sessiecookie hoort bij de naam waarop het werd gezet, tenzij de instelling `network.cookie_domain` een
bovenliggend domein van alle module-hostnamen noemt. Met `example.com` bereikt een aanmelding op `bvs.example.com` ook
`git.example.com` en `ide.example.com`. bvsd weigert een openbaar achtervoegsel (`com`, `com.pl`, `co.uk`,
`myqnapcloud.com`), een domein dat niet boven alle module-hostnamen ligt, en elk domein zolang er geen module-hostnaam
is ingesteld. Elke site onder dat domein krijgt het cookie, dus stel het nauwste gemeenschappelijke bovenliggende
domein in.

De pagina Netwerk toont deze instelling niet. De NAS-admin stelt haar in via de CLI-socket met de `curl` van het
pakket:

```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
```

Een lege waarde (`""`) houdt het cookie weer op elke naam apart.

## Publiceren op internet (Cloudflare Tunnel)

De pagina **Publiceren** (Beheer, alleen admins) zet het pakket op internet onder namen van een domein waarvan de DNS
bij Cloudflare staat. `cloudflared`, dat het pakket meelevert, maakt vanaf de NAS verbinding met Cloudflare: er wordt
geen routerpoort geopend, en Cloudflare beheert de certificaten van de namen. Elk verzoek gaat door de nginx van het
pakket: het paneel en de API vragen om aanmelding, en zonder account opent alleen wat u openbaar hebt gemaakt
(openbare repository’s, hun webweergave, de pakketrepository).

Er zijn twee typen namen, van elk type hoogstens één:

| Type | Voorbeeld | Wat hij serveert |
|---|---|---|
| Hoofdhostnaam | `code.example.com` | het paneel en de API, Forgejo op `/git/`, de webweergave op `/svn-web/` en `/bucket-web/`, de pakketrepository op `/repo/` (alleen lezen) en de handleiding. `/svn/` en `/bucket/` antwoorden daar met 404, en `/ide/` ook zolang u de editor niet publiceert (hieronder) |
| Hostnaam voor pakketten | `qpkg.example.com` | alleen GET en HEAD van `/repo/`, voor App Center op andere NAS-apparaten; al het andere antwoordt met 404 |

### Wat het token nodig heeft

Maak op dash.cloudflare.com (My Profile, API Tokens) een API-token met deze rechten, voor de zone van de namen:

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

Het pakket bewaart het token op de NAS in een bestand dat alleen root leest, en toont het nooit meer.

Als het token Client IP Address Filtering heeft, zet er dan beide openbare adressen van de NAS in, IPv4 en IPv6: de
NAS maakt via IPv6 verbinding met Cloudflare zodra hij een IPv6-adres heeft. Anders weigert de pagina Publiceren met
`token_ip_filter` en noemt het adres dat Cloudflare zag.

### Publiceren

Typ de namen en het token, en dan **Publiceren**. Het pakket controleert eerst alles bij Cloudflare en verandert daar
niets als een controle mislukt; de melding zegt wat je moet oplossen:

- een recht dat het token mist, met de naam ervan;
- een naam in een zone die het token niet ziet;
- een DNS-record dat de naam al heeft (het type en waar het naartoe wijst): verwijder het bij Cloudflare of kies een
  andere naam;
- een hoofdhostnaam terwijl **Openbaar bladeren** (pagina Git) uit staat, want de bezoekers ervan hebben geen
  account;
- namen in zones van twee Cloudflare-accounts.

Daarna maakt het de tunnel (met de naam `bucketvs-<NAS-naam>` tenzij je een naam opgeeft; een tunnel met die naam die
Cloudflare nog heeft, wordt overgenomen), laat elke naam er met een geproxied CNAME-record naar wijzen, voegt de
cacheregels toe en start cloudflared. **Opnieuw publiceren** met dezelfde waarden verandert niets. Stel de openbare
URL (Netwerk) in op het `https://`-adres van de hoofdhostnaam, zodat de koppelingen en HTTPS-kloonadressen van Forgejo die
noemen; de pagina herinnert je eraan zolang ze verschillen.

Git via SSH gaat niet door de tunnel: Cloudflare vervoert naar een gepubliceerde naam alleen HTTP. Het SSH-kloonadres
is daarom het LAN-adres van de NAS met zijn poort voor Git via SSH (9952), in het paneel en in het eigen kloonvak van
Forgejo, en de pagina Git en het Overzicht zeggen ‘Klonen via SSH werkt op het lokale netwerk; vanaf internet kloon je
via HTTPS.’ Voor SSH vanaf internet stuur je de SSH-poort door op de router en stel je **SSH-host in kloon-URL’s**
(Netwerk) in op het openbare adres van de router of op een naam die alleen in DNS staat en ernaar wijst, met
**SSH-poort in kloon-URL’s** als de router een andere poort gebruikt.

De hoofdhostnaam antwoordt op `/ide/` met 404 tot een beheerder **De editor publiceren op de hoofdhostnaam** op de pagina **Publiceren**
aanzet. De terminal van de editor is een root-shell op de NAS, en wie zich daar vanaf internet als developer of
beheerder aanmeldt, krijgt hem; zet dus eerst tweefactorauthenticatie aan voor die accounts. Met de schakelaar
aan gaat de editor door dezelfde aanmelding als op het LAN, blijft zijn poortproxy (`/ide/proxy/<port>/`) daar uit en
houdt Cloudflare zijn pagina’s nooit in de cache. Zolang hij aan staat, toont het Overzicht **Editor op internet**.

Zolang de CI-module aan staat, draait een workflow die naar een willekeurige repository van deze Forgejo wordt gepusht
als root op de NAS via de eigen runner van de NAS, of hij nu vanaf internet of vanaf het LAN is gepusht: dezelfde
macht als de terminal van de editor. De pagina **Publiceren** zegt dat onder **Wat internet bereikt**. Geef pushtoegang
alleen aan mensen aan wie u het beheerderswachtwoord van de NAS zou geven.

### De cache

Met **Cache-voorinstellingen** aan bewaart Cloudflare pakketten (`*.qpkg`) een jaar, pictogrammen een dag, `repo.xml`
5 minuten en de bestanden van het paneel een jaar; de API, de pagina’s van Forgejo, de aanmeldpagina’s en de
webweergave bewaart het nooit. Een publicatie in de pakketrepository haalt `repo.xml` (en een gewijzigd pictogram of
de bestanden van een verwijderde versie) meteen uit de cache van Cloudflare. Het pakket voegt zijn eigen regels toe
aan het einde van de cacheregels van de zone en wijzigt de andere nooit.

### Aanmelden vanaf internet

Het eerste wachtwoord kan nooit via een gepubliceerde naam worden ingesteld: de eerste configuratie neemt alleen een
lokale client aan. Verkeerde wachtwoorden tellen per internetclient, zoals op het LAN. Een wachtwoord dat met git naar
Forgejo wordt gestuurd, telt ook; een Forgejo-toegangstoken werkt zoals altijd. Cloudflare neemt verzoeken tot 100 MB
aan, dus een grotere push of upload gaat via het LAN-adres.

### Stoppen en verwijderen

**Stoppen** beëindigt cloudflared; de namen tonen dan de eigen foutpagina van Cloudflare, en bij Cloudflare verandert
niets. **Van Cloudflare verwijderen** verwijdert de DNS-records, de cacheregels en de tunnel die het pakket heeft
gemaakt (een overgenomen tunnel blijft) en vergeet het token. Zolang er namen zijn gepubliceerd, toont het Overzicht
een label **Internet** met de toestand van de tunnel.

## Modules

Het Overzicht toont elke module met haar processen, geheugen en CPU, en zet haar aan en uit (alleen admins). Een
module die uit staat zegt wat ze biedt; een admin zet haar aan met haar knop (**Git aanzetten**, **Editor aanzetten**
enzovoort) op haar tegel, op haar eigen pagina of, voor de Editor, in het tabblad Editor.

- Een module aanzetten voert een taak uit: de module bereidt zich voor, de schakelaar wordt opgeslagen, en haar
  processen starten. De taak wacht tot 30 seconden op ze.
- CI heeft Git nodig: Git kan niet uit zolang CI aan staat.
  Zolang Git uit staat, heet de knop van CI **Git en CI aanzetten** en zet die eerst Git aan.
- Een module die uit staat, draait geen proces en gebruikt geen geheugen.
- De kern staat altijd aan.

| Status | Betekenis |
|---|---|
| actief | elk proces van de module draait |
| start | een proces is minder dan een seconde geleden gestart |
| verminderd | een proces wacht op een herstart, de bestanden van de module konden niet worden geschreven, of de gezondheidscontrole mislukt |
| herstart steeds opnieuw | een proces is binnen 10 minuten 5 keer gestopt; bvsd start het elke 15 minuten opnieuw, en er ging één melding uit |
| gestopt | de processen van de module draaien niet (een verplaatsing van de data heeft ze gestopt, of ze konden niet starten) |
| uit | de module staat uit |

Een proces dat stopt, wordt na 1 seconde opnieuw gestart, dan na 2, 4 enzovoort tot 60 seconden. Na 10 minuten draaien
begint de wachttijd weer bij 1 seconde. Een proces dat steeds opnieuw herstart, start meteen weer als zijn module uit-
en weer aangezet wordt.

## Git

De Git-module draait Forgejo, een webinterface voor git-repository’s met issues, pull requests, releases, wiki’s, LFS,
mirrors en webhooks. Een admin zet haar aan met **Git aanzetten** in het Overzicht of op de Git-pagina. De taak
bereidt de mappen en de database van Forgejo voor in `<data>/forgejo`, voegt elk account van het paneel aan Forgejo
toe, en start het. Forgejo opent dan op `/git/` op het adres van het paneel, of op `/` van de Git-hostnaam als die is
ingesteld (zie [Hostnamen voor de modules](#hostnamen-voor-de-modules)).

**Nieuwe repository** op de Git-pagina, en zolang er nog geen is ook op de Git-tegel van het Overzicht, opent het
formulier van Forgejo voor een repository.
Het paneel biedt hem, en **Een repository migreren of spiegelen**, vanaf de rol developer aan: Forgejo laat een
viewer of een member geen repository aanmaken.

### Repository’s en accounts

- **Eén aanmelding.** Forgejo heeft geen eigen aanmelding: `/git/user/login` gaat naar de aanmelding van het paneel,
  met de authenticatiecode als het account die heeft, en daarna terug naar Forgejo.
- **Accounts komen uit het paneel.** Elk account van het paneel heeft een Forgejo-account met dezelfde naam en
  hetzelfde wachtwoord. De rol `admin` is sitebeheerder in Forgejo. Een uitgeschakeld account, of een account dat
  eerst zijn wachtwoord moet wijzigen, kan zich ook niet bij Forgejo aanmelden. Een wijziging in het paneel bereikt
  Forgejo binnen enkele seconden. De pagina’s Gebruikers en Git tonen de status van elk account in Forgejo, en **Nu
  alle accounts synchroniseren** op de pagina Git voert de synchronisatie opnieuw uit.
- **Een account verwijderen** in het paneel zet zijn Forgejo-account buiten gebruik: het kan zich niet meer aanmelden,
  en zijn tokens en SSH-sleutels worden verwijderd, maar zijn repository’s blijven. De naam kan niet aan een nieuw
  paneelaccount worden gegeven totdat een admin het oude Forgejo-account hernoemt of verwijdert in het sitebeheer van
  Forgejo (**Gebruikersaccounts**).
- **Een account dat in Forgejo zelf is gemaakt**, wijzigt het paneel nooit. Een paneelaccount met dezelfde naam toont
  de reden en wacht tot een van beide een andere naam krijgt.
- **Tweefactorauthenticatie en git.** Forgejo neemt geen wachtwoord aan voor een account met tweefactorauthenticatie.
  git via HTTP of HTTPS heeft dan een Forgejo-toegangstoken nodig (in Forgejo **Instellingen**,
  **Applicaties**) in plaats van het wachtwoord. SSH-sleutels werken zoals voorheen.
- **Repository’s** worden aangemaakt, geïmporteerd en gespiegeld op de eigen pagina’s van Forgejo. Importeren vanaf
  adressen op het LAN is standaard toegestaan (instelling: `git.allow_local_networks`), omdat een NAS meestal vanuit
  zijn eigen netwerk spiegelt.

### Klonen via HTTPS en SSH

Op de pagina Git schrijft **Een repository klonen** beide commando’s voor een eigenaar en een repository:

| Manier | Adres | Aanmelding |
|---|---|---|
| HTTPS (of HTTP) | `https://<NAS-adres>:9951/git/<eigenaar>/<repo>.git` | gebruikersnaam en wachtwoord van het paneel, of een toegangstoken |
| SSH | `ssh://git@<NAS-adres>:9952/<eigenaar>/<repo>.git` | een SSH-sleutel die in Forgejo is toegevoegd onder **Instellingen**, **SSH- / GPG-sleutels** |

- Met een openbare URL (achter een proxy, [Openbare URL](#openbare-url)) gebruikt het HTTPS-adres die; met een
  Git-hostnaam is het pad `/<eigenaar>/<repo>.git` op die naam.
- De SSH-poort is `network.ssh_port` (9952), of `network.public_ssh_port` als een router een andere poort ernaartoe
  doorstuurt. De proxy vervoert geen SSH: stuur de poort door op de router (zie [Git via SSH](#git-via-ssh)).
- Een naam die via de Cloudflare Tunnel is gepubliceerd draagt geen SSH, dus het SSH-kloonadres noemt dan het
  LAN-adres van de NAS, tenzij `network.public_ssh_host` (Netwerk, ‘SSH-host in kloon-URL’s’) een naam die alleen in
  DNS staat of het adres van de router noemt.
- Grote pushes worden niet afgekapt: nginx stelt op `/git/` geen groottelimiet voor de body in.

### Repository’s tonen aan mensen zonder account

**Openbaar bladeren** op de pagina Git laat iedereen die Forgejo bereikt, de openbare repository’s zonder account
lezen. In Forgejo is elke repository openbaar of privé (de **Instellingen** ervan, **Zichtbaarheid**). Met de
schakelaar aan kan een bezoeker zonder account:

- een openbare repository openen, de mappen en bestanden ervan, en een bestand downloaden zoals het is;
- de geschiedenis lezen, een commit met zijn diff openen (ook als `.diff`- of `.patch`-bestand) en twee commits
  vergelijken;
- de releases zien, een archief downloaden, en de repository via HTTP klonen
  (`git clone https://<NAS-adres>:9951/git/<eigenaar>/<repo>.git`);
- openbare repository’s vinden onder **Verkennen** en op het profiel van hun eigenaar.

Een privérepository blijft verborgen: het adres antwoordt ‘niet gevonden’. Zolang de schakelaar aan staat, toont
Forgejo ook geen accounts (het tabblad **Gebruikers** van Verkennen en het zoeken naar gebruikers staan uit, ook voor
aangemelde gebruikers), zodat een bezoeker de accountnamen niet kan verzamelen. Pushen, issues, pull requests en
instellingen vereisen nog steeds een account. Met de schakelaar uit vraagt elke pagina de bezoeker eerst om aan te
melden.

Om een repository te delen, maak je hem openbaar in Forgejo, zet je **Openbaar bladeren** aan en stuur je het adres.
Op een Git-hostnaam is het adres `https://<Git-hostnaam>/<eigenaar>/<repo>`.

### Nieuw project

**Nieuw project** is een pagina die een project stap voor stap van niets naar een gebouwde en gepubliceerde eerste
versie brengt. Ze opent in een eigen tabblad vanuit het opdrachtenpalet (Ctrl+Shift+P, **Nieuw project**), boven in de
zijbalk van de Editor, in de kop van de Git-zijbalk, op de Git-pagina en op de pagina Projecten; een voorbeeld onder **Editor-projecten** op
de pagina Voorbeelden heeft **Nieuw project hieruit**. Ze vraagt de rol developer, de modules Git en Editor en je
account in Forgejo (de Git-pagina toont de synchronisatie). Zolang Git of de Editor uitstaat, zegt de pagina dat boven de
stappen, en een beheerder zet ze daar met één knop aan.

1. **Sjabloon.** De sjablonen zijn gegroepeerd: Programma’s (C, C++ met CMake, Go, Rust, Node, Python, Java), Pakketten
   voor QTS (een QPKG-pakket, een App Builder-app, een pakket uit broncode), Kernel (een kernelmodule, het
   USB-serieel-stuurprogramma ch341), Websites (een statische website: pagina’s in `src/`, een Makefile die `public/`
   bouwt, een linkcontrole als `make test`) en Voorbeelden. Elke kaart zegt wat een build maakt en markeert wat deze
   NAS nog mist (QDK, een bouwomgeving, een module die uit staat), met een link naar de pagina die het toevoegt.
2. **Naam en zichtbaarheid.** De naam is die van de repository en de map `projects/<naam>`; de pagina controleert hem
   terwijl je typt. Dan de eigenaar (je account, of een organisatie waarin Forgejo je repository’s laat maken), privé
   of openbaar, en een beschrijving als je wilt. Voor een App Builder-app de voorinstelling waarmee ze begint en het
   doel.
3. **Dienst**, voor een QPKG-pakket en een pakket uit broncode: hoe het pakket start en stopt, opgebouwd uit blokken.
   Kies een voorinstelling (**Opdrachtregelprogramma** koppelt het programma in `/usr/bin`; **Achtergronddienst**
   voegt een account toe, een datamap op het volume, het programma gestart als dat account, wachten op zijn poort
   en een stop met een time-out; **Webapp** voegt de knop Openen van App Center toe; **Leeg** heeft alleen de
   werkwoorden) en wijzig dan de blokken. De kaart van elk blok toont al tijdens het typen de regels die het in het
   script schrijft, en een geweigerde waarde staat gemarkeerd onder zijn veld. Een start zet Enable van App Center
   nooit aan, en een stop raakt het nooit aan. De projectweergave wijzigt de blokken later.
4. **Bouwen.** De omgeving waarin het project bouwt, voor het project bewaard (de projectweergave wijzigt die later),
   of geen, en dan kiest bvsd er bij elke build een. De stap biedt eerst de omgeving die bvsd zelf zou kiezen en
   waarin `bvs qbuild` en CI bouwen: de Ubuntu 24.04 builder, anders de Debian 12 builder, anders een andere Debian-
   of Ubuntu-omgeving. Geeft de Makefile van de sjabloon elke app haar omgeving op een ENV-regel, dan zegt de stap dat en
   vraagt hij er geen, en bewaart het project er geen. Voor een pakket uit broncode zegt de stap ook dat het bronadres
   en de sha256 van het startrecept tijdelijk zijn: de eerste build mislukt tot het recept de echte bron van je
   programma en de sha256 van dat bestand noemt. De pagina toont de workflow die CI bij elke push draait. **Direct na
   het aanmaken bouwen** staat aan tot je het uitzet; bij een pakket uit broncode staat het eerst uit, en je kunt het
   aanzetten. Zonder Debian- of Ubuntu-omgeving voor de NAS zegt de stap dat: een beheerder maakt daar de bouwomgeving
   aan met **Deze omgeving aanmaken**, en daarna biedt de stap die aan; zet anders **Direct na het aanmaken bouwen**
   uit en bouw het project later.
5. **Publiceren**, voor een QPKG-pakket, een App Builder-app of een website: **Testuitrol op deze NAS** (admin, alleen
   ondertekende pakketten; voor een app **Activeren op deze NAS**), **Publiceren in de App Center-repository van deze
   NAS** en **Publiceren op mijn webserver** (admin) op een van de [webdoelen](#je-eigen-webserver-webdoelen); **Een
   doel toevoegen** bewaart er een nieuw. Elk is optioneel. De keuze om te publiceren wordt ook de schakelaar
   **Publiceren in de App Center-repository** van het project (projectweergave, Instellingen): zolang die aan staat,
   publiceert de CI van het project daar bij elke run.
6. **Controle** toont elk antwoord en de stappen die zullen draaien. **Project aanmaken** verstuurt ze.
7. **Voortgang** volgt de taak van elke stap: de repository en de eerste commit, de eerste build, dan de testuitrol,
   de publicatie in App Center en die op het web, de een na de ander. Een rij toont haar status en het einde van haar
   logboek; een klik toont het logboek. Een mislukte stap zegt waarom en biedt **Opnieuw proberen**, en de stappen
   erna wachten. Aan het eind **Project openen**, **Openen in de editor** en **Openen in Forgejo**. De pagina
   herladen of terugkeren naar haar tabblad leest de taken opnieuw en verstuurt geen stap twee keer. Wie de pagina
   verlaat, stopt de keten; de projectweergave heeft dezelfde stappen.

Als bvsd het project weigert (een naam die een map, een app of een repository al heeft, een eigenaar waarin je niets
mag maken), gaat de pagina terug naar de stap met dat antwoord, toont de reden onder het veld en houdt alle andere
antwoorden. Een module die uit staat (een admin zet haar aan vanaf de pagina), geen Forgejo-account en geen Debian- of
Ubuntu-omgeving voor een App Builder-app worden uitgelegd in Controle.

Wat de eerste taak doet:

- Ze maakt de repository als jij, privé tenzij je openbaar koos.
- Ze maakt de map `projects/<naam>` in de datamap met de eerste commit: elk bestand van de sjabloon en de workflow
  `.forgejo/workflows/build.yml`, op `main`. De commit is van jou (je naam en adres staan als auteur in de map), en de
  scripts van de sjabloon zijn erin uitvoerbaar. Ze pusht die commit als jij. `origin` is het HTTPS-adres van de
  repository, dus je volgende push gaat daarheen. De commit bevat ook de [skill voor
  AI-assistenten](#programmeren-met-een-ai-assistent), `.claude/skills/qnap-dev/` en `AGENTS.md`. De map staat in de
  gedeelde map van de editor: de editor van elke developer opent `projects/`, of de repository nu privé of openbaar
  is.
- Ze bewaart de gekozen omgeving voor het project, tenzij de Makefile van de sjabloon elke app haar omgeving geeft. Voor
  een App Builder-app maakt ze de app met de naam van het project en de recepten van de voorinstelling en koppelt die
  aan het project, zodat de app ook recepten kan bouwen die je in de map `recipes/` van het project zet. Ze bewaart ook
  je publicatiekeuze als de schakelaar Publiceren in de App Center-repository van het project, voordat de eerste run van
  de workflow die leest.

De workflow draait bij elke push op de runner van de NAS zodra de CI-module aan staat, in de omgeving die de workflow
noemt. De QPKG-workflow publiceert het pakket in de [pakketrepository](#pakketrepository), die alleen code-ondertekende
pakketten aanneemt. Als de eerste commit of de push mislukt nadat de repository is gemaakt, verwijdert de taak de map,
verwijdert de repository weer en zegt waarom. Zolang de schakelaar **Publiceren in de App Center-repository** van het
project uit staat, toont `bvs repo publish` in de workflow een melding in plaats van te publiceren, en de run gaat door.

### De projectweergave

Elke map van `projects/` in de datamap heeft een eigen pagina: een project uit de wizard, de kopie van een voorbeeld uit Editor-projecten
of een repository die je daar gekloond hebt. Ze opent vanuit de tak **Projecten** van de zijbalk van de Editor, vanaf de
pagina **Projecten**, met **Project openen** aan het eind van Nieuw project en vanuit het opdrachtenpalet (**Project …
openen**, of Ctrl+P en de naam). Ze vraagt de rol developer. Het menu van een project op de pagina Projecten heeft
ook Bouwen, Opnieuw bouwen, Publiceren en de publicatie op de webdoelen. Hetzelfde menu zet publiceren door CI in App
Center aan of uit.

De pagina leest de map en wijzigt er uit zichzelf niets in. Het type van het project volgt uit de bestanden: een App
Builder-app met de naam van de map, een `qpkg.cfg` bovenaan (een QPKG-pakket), een `Makefile` bovenaan, of niets
daarvan, wanneer de CI-workflow van het project het bouwt. Heeft een App Builder-app de naam van de map, dan houdt een
project met een `qpkg.cfg` bovenaan, of met een Makefile en een `qpkg.cfg` in `qpkg/`, zijn eigen build, en nemen
Testuitrol en Publiceren nog steeds zijn QPKG.

De strook bovenaan bevat de stappen. Een stap die niet kan draaien houdt haar knop, uitgeschakeld, en zegt ernaast en
onder de strook waarom. Een stap die het project helemaal niet kan nemen, verdwijnt uit de strook, en één zin eronder
zegt wat het project maakt en waar het resultaat van de build heen gaat: de map van het project bij een programma, de
pagina Kernels bij een kernelmodule, de pagina van elke app bij een Makefile die alleen App Builder-apps bouwt.

- **Bouwen** draait als taak in het onderpaneel. Een QPKG-pakket bouwt met `qbuild` van QDK, een Makefile-project met
  `make`, beide in de bouwomgeving van het project. Het logboek noemt elke `.qpkg` die de build in `build/` schreef en
  of die een codehandtekening heeft. Een App Builder-app bouwt zoals op haar eigen pagina. Voert het standaarddoel van
  een Makefile alleen regels `./bvsapp.sh create` en `./bvsapp.sh build` uit, dan doet Bouwen hetzelfde via de App
  Builder: het maakt elke app van een `create`-regel die er nog niet is, en bouwt de apps daarna een voor een. Een
  `create`-regel is `./bvsapp.sh create NAAM RECEPTENBESTAND [ENV] [LINK] [TARGET] [OTHERS]`. Zo'n Makefile noemt zijn
  standaarddoel en de vereisten daarvan in `.PHONY` en bevat alleen eenvoudige toewijzingen, elke variabele één keer
  gezet, en eenvoudige regels; een Makefile met iets anders (een voorwaarde, een include) bouwt met make. TARGET is
  `native`, `aarch64` of `armv7`, zodat één keer Bouwen een app voor deze NAS en een voor ARM 64-bit kan maken. OTHERS
  zegt wat een statische app doet met recepten die niet statisch kunnen linken (`refuse` of `bundle`). Een app die zo
  gemaakt is, neemt ook de eigen recepten van het project uit `recipes/`, tenzij de naam van de map geen projectnaam is
  (bijvoorbeeld een naam met een spatie). Staat er al een app met die naam met een ander doel, of met een ander project
  terwijl de map eigen recepten heeft, dan blijft Bouwen uit en zegt het hoe je dat oplost. Voor de eerste build noemt
  de stap de apps die hij bouwt. Een Makefile-project met zijn pakket in `qpkg/` maakt het pakketbestand met `make qpkg`
  in de terminal van de editor; zolang het er niet is, zeggen Testuitrol, Publiceren en Publiceren op het web „Heeft een
  pakketbestand nodig”.
- **Opnieuw bouwen** bouwt vanaf nul: `make clean`, dan `make`; bij een QPKG-pakket gaan eerst de oude `.qpkg`-bestanden
  van `build/` weg. Een Makefile zonder doel `clean` kan niet opnieuw bouwen, en de pagina zegt dat. Een App Builder-app
  heeft geen Opnieuw bouwen, en een Makefile die alleen App Builder-apps maakt en bouwt evenmin.
- **Exporteren als QPKG** (admin) is er voor een App Builder-app, zoals op de pagina van de app. Publiceren wacht dan op
  een export, zoals op de pagina van de app, en **De app openen** in de kop opent die pagina.
- **Testuitrol** is voor admins. Ze installeert de nieuwste `.qpkg` op deze NAS met `qpkg_cli`, zoals App Center een
  bestand installeert, en de taak wacht tot App Center het bevestigt. Het pakket blijft geïnstalleerd tot je het in
  App Center verwijdert. Een pakket zonder codehandtekening wordt geweigerd: de testuitrol installeert alleen
  ondertekende pakketten en slaat de handtekeningcontrole van App Center nooit over (in App Center zelf kan de
  eigenaar van een NAS apps zonder handtekening toestaan; deze pagina doet dat niet). Onderteken het met een
  certificaat als bestanden (geval 1 in [Een QPKG code-ondertekenen](#een-qpkg-code-ondertekenen)). Het pakket van
  Bucket & Code Server IDE zelf wordt geweigerd, omdat de installatie het draaiende pakket zou vervangen. Voor een App
  Builder-app activeert de testuitrol haar build op deze NAS.
- **Publiceren** zet de nieuwste `.qpkg` in de [pakketrepository](#pakketrepository), met haar regels: de publicatie van
  een admin wordt de vermelde versie, die van een developer wacht op een admin, en een pakket zonder handtekening
  wordt geweigerd tenzij de repository niet-ondertekende pakketten aanneemt. Een regel eronder zegt of de CI van het
  project ook in de App Center-repository publiceert; deze knop publiceert in elk geval.
- **Publiceren op het web** (admin) kopieert de nieuwste `.qpkg`, of de websitemap `public/` (anders `dist/`), naar een
  [webdoel](#je-eigen-webserver-webdoelen) en leest die terug via het openbare adres van het doel. Bij een website die
  de wizard maakte, wacht de stap op de eerste build, die `public/` schrijft.

Een Makefile-project kiest een doel naast **Bouwen** (het standaarddoel van make, het eerste, tenzij je een ander
kiest); een CMake-project bouwt, of bouwt en draait zijn tests. Een kernelmoduleproject bouwt in de werkruimte van de
kernelwerkplaats voor de draaiende kernel, en die build is aan een admin. Een lopende stap heeft **Annuleren**, en de
taak stopt meestal meteen. **De workflow uitvoeren** draait de CI-workflow van het project met de hand als de workflow
een `workflow_dispatch`-trigger heeft; anders staat de knop uit en zegt de pagina waarom. De workflow van elk sjabloon
heeft die trigger. **Geïnstalleerd op deze NAS** toont de pakketten die de testuitrollen van het project hebben
geïnstalleerd, met **Verwijderen van deze NAS** voor een admin. **Project verwijderen**, in het menu naast de knoppen
van de kop of in het menu van het project op de pagina Projecten, is voor een admin of voor de developer wiens
Forgejo-account de repository bevat. Je typt eerst de naam van het project; de map verdwijnt, en de Forgejo-repository,
een App Builder-app met die naam en geïnstalleerde pakketten blijven. De pagina linkt daarna naar de repository.

**Instellingen** bevat wat bvsd buiten de map voor het project bewaart. Voor een Makefile-, CMake- of QPKG-project toont
het de omgeving waarin Bouwen en Opnieuw bouwen draaien, en wijzigt die. Voor een Makefile die alleen App Builder-apps
maakt en bouwt, noemt het elke app met de omgeving die haar regel in de Makefile noemt. Een project dat een pakket
maakt, of waarvan de workflow `bvs repo publish` uitvoert, heeft daar de schakelaar **Publiceren in de App
Center-repository**: staat die aan, dan voegt `bvs repo publish` in de CI van het project zijn pakketten toe aan de
repository; staat die uit, dan slaat de CI-run die stap over met een melding. Een project zonder bewaarde keuze (de
kopie van een voorbeeld, een kloon) heeft hem aan. **Git** toont de branch, hoever die voor of achter de remote ligt
zoals de laatste fetch het liet (de pagina fetcht nooit), de laatste commit en de gewijzigde paden. **Bestanden** toont
één map tegelijk; kies een bestand om het te lezen, een `.qpkg` wordt gedownload. Bewerken gebeurt in de editor, en de
map `.git` blijft buiten beeld.

**Recepten** toont voor een project met een eigen map `recipes/` de recepten die zijn apps nemen: elk met zijn versie,
de versie uit de bibliotheek die het vervangt als het de naam van een bibliotheekrecept heeft, en daaronder wat de
receptcontrole vond, met bestand en regel. Een recept dat bvsd weglaat, heeft een rij met de reden; zijn er meer dan
drie op dezelfde manier weggelaten, dan telt één rij de rest. Wat de controle over de map `recipes` als geheel vond,
heeft een eigen rij. Een klik opent de map van het recept in de editor; het menu kopieert ook de opdracht die dezelfde
controle in een terminal van de NAS draait: `bvs recipe lint` met de map van het project. De kaart verschijnt ook als
`recipes` van het project een link, een bestand, een map die bvsd niet kan lezen of een map met alleen links is, en
zegt waarom er geen recept uit wordt gelezen.

**Dienst en pakket** (een QPKG-pakket, of een Makefile-project met zijn pakket in `qpkg/`, zoals een pakket uit
broncode) bevat het start- en stopscript van het pakket als blokken, de velden van zijn `qpkg.cfg` (**Pakket**) en de
bestanden die daaruit worden geschreven (**Script**). **Opslaan** toont de verschillen van elk bestand en commit ze
dan in de repository van het project; is het project intussen gewijzigd, plan dan opnieuw. **Met de hand bewerken**
zet de blokken uit, zodat je het script in de editor bewerkt; **Blokken weer koppelen** toont wat opnieuw schrijven
zou vervangen. **Bibliotheek bijwerken** brengt de `bvs-qpkg.sh` van deze Bucket & Code Server IDE in het pakket, met de verschillen.
Een project van vóór de blokken biedt **Blokken gebruiken voor dit script**.

**Versies** toont waarnaar het project kan overschakelen, en **Naar deze versie overschakelen** doet het:

| Versie | Wat overschakelen doet |
|---|---|
| Een `.qpkg` in `build/`, of een export van de App Builder | een testuitrol van dat bestand (admin) |
| De build van de App Builder | activeert die (admin); de App Builder bewaart één build |
| Een versie in de pakketrepository | maakt die de vermelde versie (admin) |
| Een git-tag | checkt de tag uit in de map |

Een klik kiest een versie; haar menu doet ook een testuitrol, publiceert, publiceert op een webdoel of downloadt een
`.qpkg`. Een tag wordt uitgecheckt als losse HEAD, en **Terug naar main** in de Git-kaart keert terug naar de branch.
Overschakelen wordt geweigerd zolang de map niet-gecommitte wijzigingen heeft; de melding noemt ze, en je commit of
stasht ze eerst in de editor. Vink twee versies aan en **De twee vergelijken** toont de bestanden die de ene heeft en
de andere mist, en de gewijzigde bestanden. Twee tags, of twee pakketten, worden op inhoud vergeleken; een pakket met
een commit alleen op grootte. Op de pagina **Pakketten** installeert een admin ook een versie uit de repository op
deze NAS (**Installeren op deze NAS**), met de controles van de testuitrol.

### Je eigen webserver (webdoelen)

Een webdoel is waar een admin een pakket of een website op de eigen webserver publiceert: een map van deze NAS (de
Web-share van QTS, die de Web Server van QTS serveert, of een andere gedeelde map) of een rclone-remote (SFTP, FTP,
WebDAV, S3, ingesteld in de `rclone.conf` van de back-ups), met het openbare adres dat het serveert. Ze staan op de
pagina **Publiceren**, onder **Webdoelen**: **Een doel toevoegen**, **Wijzigen**, **Verwijderen** en **Testen**, dat er
een klein bestand naartoe kopieert, het via het openbare adres ophaalt en weer verwijdert.

Een mapdoel is een eigen map in een share (`/share/Web/mijnsite`, nooit een hele share of een volume), buiten de
datamap, omdat een websitepublicatie van het doel verwijdert wat de site niet meer bevat. Het adres begint met
`http://` of `https://` en bevat geen gebruiker of wachtwoord.

Elke publicatie leest het bestand terug via het openbare adres: de `.qpkg` van een pakket met de `.sha256`, op sha256
vergeleken, of de `index.html` van een website, byte voor byte vergeleken. Als het adres niet serveert wat er is
gekopieerd, mislukt de taak met `web_check:` en beide waarden.

## CI-runners

De CI-module draait Forgejo Actions: een workflowbestand in `.forgejo/workflows/` van een repository draait op een
runner als er iets wordt gepusht. Ze heeft de Git-module nodig. Een admin zet haar aan onder **Modules**, **CI**,
**Inschakelen**. De taak registreert de eigen runner van de NAS bij Forgejo, herstart Forgejo met Actions aan, en
start de runner; ze eindigt als de runner Forgejo heeft bereikt.

### De runner op de NAS

- **Zijn labels** zijn `bucketvs`, `qnap-x86_64` (of `qnap-aarch64`) en `self-hosted`, plus de extra labels die een
  admin op de pagina CI instelt. Een taak waarvan `runs-on` er een noemt, draait hier, bijvoorbeeld
  `runs-on: bucketvs`.
- **Hij voert elke taak uit op de NAS zelf, als admin**, niet in een container. Een taak heeft de tools van het
  pakket (git, curl, make en de rest), Node voor JavaScript-actions zoals `actions/checkout`, en `bvs env run <env>
  -- <command>` voor een build in een omgeving. Wie een workflow in een repository kan pushen, kan dus commando’s als
  admin op de NAS uitvoeren. Een member of viewer kan in Forgejo geen repository’s aanmaken, forken of importeren, en
  geen organisaties aanmaken; developers en admins kunnen dat wel. Een viewer pusht nooit. Zolang CI aan staat, wordt
  de push van een member naar een repository waarvan de Actions aan staan geweigerd met de reden, net als een push
  met een deploy key. De push van een member, of met een deploy key, naar een repository waarvan een account onder
  developer eigenaar of beheerder is, wordt ook geweigerd, ook als CI uit staat: zo’n account zou de Actions zelf
  kunnen aanzetten. bvsd zet de Actions binnen een minuut uit in elke repository waarvan een member of viewer
  eigenaar is of waarin zo’n account kan schrijven, en voor de start van de runner wanneer CI aangezet wordt.
- **Wat overblijft,** is de eigen handeling van een developer of admin: Actions aanzetten, of een workflow starten,
  in een repository waarin een member eerder schreef, of zo’n account beheerderstoegang geven tot een repository
  waarin het eerder schreef. Een developer die commits van een member samenvoegt of bouwt, voert ze ook als admin
  uit.
- **Taken tegelijk** is `ci.capacity` (standaard 1). Een taak die geen online runner kan oppakken, wacht in Forgejo.
- **Repository’s die zijn aangemaakt voordat CI aan stond**, hebben de Actions van Forgejo uit staan, dus een gepushte
  workflow start niets: zet ze aan in de **Instellingen** van de repository, **Eenheden** (Actions). Repository’s die
  zijn aangemaakt terwijl CI aan staat, hebben ze wel.
- **Actions** zoals `actions/checkout@v4` worden gedownload van `https://data.forgejo.org`, dus de NAS heeft
  internettoegang nodig voor workflows die ze gebruiken. De runner heeft geen cache: `actions/cache` vindt niets en de
  taak gaat door.
- **Artefacten** (`actions/upload-artifact`) worden geüpload naar het eigen adres van Forgejo (dat op de pagina Git),
  dus de NAS moet dat adres bereiken, en het certificaat ervan vertrouwen als het HTTPS is.
- **De status van de runner** staat in `<data>/runner`; zijn logboek is **runner** onder **Logboeken**. De module
  uitzetten stopt de runner en zet Actions uit in Forgejo.

### Externe runners

Een buildmachine elders (een server, een VM) kan ook taken van deze Forgejo oppakken. Op de pagina CI, onder **Een
externe runner registreren**, geeft een admin haar een naam en krijgt de commando’s voor Linux x86_64 en aarch64. Die
downloaden forgejo-runner 13.2.0 van code.forgejo.org, controleren de sha256, bewaren het token van de runner in
`runner-token` (alleen leesbaar voor de eigenaar) en starten de runner met het label `linux-amd64` (of `linux-arm64`).
De runner verschijnt meteen in de lijst, offline totdat de commando’s draaien. Het token wordt alleen dan getoond.
Zo’n runner voert zijn taken uit op zijn eigen machine, als de gebruiker die hem startte, en bereikt Forgejo op het
openbare adres.

## SVN en Bucket

De module SVN en Bucket serveert Subversion-repository’s op `/svn/<naam>` en buckets op `/bucket/<naam>`. Een bucket
is een netwerkstation (WebDAV) waarin elk opgeslagen bestand een revisie wordt, zodat elke eerdere versie van een
bestand gelezen of teruggezet kan worden. Een admin zet de module aan onder **Modules**; de pagina **SVN & Bucket**
maakt dan beide typen aan, toont ze en verwijdert ze.

- **De accounts** zijn die van het paneel. Machtigingen worden per repository ingesteld: lezen en schrijven, lezen, of
  geen, voor elk account, plus een standaard voor elk account dat niet in de lijst staat. Admins lezen en schrijven
  altijd; members en developers schrijven waar ze lezen en schrijven hebben; viewers lezen hooguit. Een nieuwe
  repository geeft niemand behalve de admins toegang totdat een admin die instelt (het personenpictogram op de rij,
  **Machtigingen voor `<name>`**).
  Onder admin toont de pagina ieder zijn eigen toegang tot elke repository en bucket.
- **Verkeerde wachtwoorden** tellen zoals bij de aanmelding van het paneel: 5 verkeerde wachtwoorden binnen 15 minuten
  blokkeren dat account vanaf dat adres, en het adres zelf, 15 minuten.
- **Tweefactorauthenticatie.** Een account met tweefactorauthenticatie gebruikt zijn wachtwoord niet voor SVN of
  WebDAV. Het maakt één **app-wachtwoord** op zijn accountpagina (**App-wachtwoord voor WebDAV en SVN**): 25
  willekeurige tekens, één keer getoond. Een nieuw vervangt het oude; een wachtwoordwijziging of het uitzetten van de
  tweefactorauthenticatie verwijdert het.
- **Verwijderen** van een repository verplaatst hem naar `<data>/trash`, waar de taak `trash` hem na 7 dagen
  verwijdert.

### SVN-repository’s

```bash
svn checkout "https://<NAS-adres>:9951/svn/<naam>"
svn commit -m "…"
```

De pagina toont het checkout-commando met het adres van de repository. Achter een proxy volgt het adres de openbare
URL, en met een SVN-hostnaam is het `/svn/<naam>` op die naam. De **Geschiedenis** van een repository toont de
revisies; een bestand kan in elke revisie worden gelezen, met een andere worden vergeleken, en als nieuwe revisie
worden teruggezet (**Deze versie herstellen**, voor accounts met lezen en schrijven).

### Buckets

- **Opslaan is committen.** Een client die een bestand opslaat, maakt een revisie met de naam van het account en de
  tijd; de revisie is gemarkeerd als gemaakt via WebDAV. Veel clients schrijven eerst een leeg bestand en daarna de
  data, dus één keer opslaan kan twee revisies maken.
- **macOS-bestanden.** Een bucket die is aangemaakt met **De macOS-bestanden ._\* en .DS_Store weigeren** (de
  standaard) bewaart de bestanden `._*` en `.DS_Store` niet die de Finder naast wat hij kopieert schrijft. De client
  krijgt geen fout, maar het bestand wordt nooit een revisie. Zo’n bestand verwijderen mag wel.
- **Teruggaan.** De geschiedenis van de bucket toont de versies van elk bestand; **Deze versie herstellen** slaat de
  oude inhoud op als nieuwe revisie, dus er gaat niets verloren.

### WebDAV-clients en hun grenzen

| Client | Hoe verbinden | Opmerkingen |
|---|---|---|
| Finder van macOS | **Ga**, **Verbind met server**, `https://<NAS-adres>:9951/bucket/<naam>` | de Finder vergrendelt een bestand terwijl hij het schrijft; de bucket beantwoordt WebDAV-vergrendelingen |
| rclone | een `webdav`-remote met `vendor = other` en het adres van de bucket | geschikt voor kopieën via scripts |
| davfs2 (Linux) | `mount -t davfs https://<NAS-adres>:9951/bucket/<naam> /mnt/<map>` | de gebruikersnaam van het paneel en het wachtwoord of app-wachtwoord |
| Cyberduck, WinSCP | een WebDAV-verbinding (HTTPS) met poort 9951, pad `/bucket/<naam>` | |
| curl | `curl -T bestand -u <gebruiker> https://<NAS-adres>:9951/bucket/<naam>/bestand` | één upload, één revisie (of twee, zie hierboven) |
| Windows Verkenner | **Netwerkverbinding maken** | de WebClient van Windows stuurt een wachtwoord alleen via HTTPS met een certificaat dat Windows vertrouwt: zet de bucket achter een proxy met een echt certificaat ([Achter een reverse proxy](#achter-een-reverse-proxy-plesk)); het zelfondertekende certificaat is niet genoeg |

- Grote bestanden worden niet afgekapt: nginx stelt op `/bucket/` geen groottelimiet voor de body in en geeft de
  upload door terwijl die binnenkomt.
- Een bestand dat de bucket weigert op te slaan (een macOS-bestand hierboven), beantwoordt de WebDAV-server toch met
  `201`, dus een client toont geen fout; het bestand staat niet in de bucket.

### Een repository of bucket tonen aan mensen zonder account

Een admin kan een SVN-repository of een bucket openbaar maken met de schakelaar in de rij op de pagina **SVN &
Bucket** (**Op het web**). Iedereen die het adres heeft, kan hem dan zonder account in een browser lezen:

| Adres | Wat het toont |
|---|---|
| `https://<NAS-adres>:9951/svn-web/<naam>/` | een SVN-repository |
| `https://<NAS-adres>:9951/bucket-web/<naam>/` | een bucket |

De koppeling **Bekijken op het web** van de rij opent die pagina. Ze toont de bestanden en mappen in de nieuwste
revisie of een oudere, toont een tekstbestand met regelnummers (tot 1 MiB) of een afbeelding, en biedt elk bestand als
download aan. De **Geschiedenis** toont de revisies met hun auteurs, berichten en gewijzigde bestanden. De pagina van
een revisie toont de diff van elk bestand dat ze wijzigde (tot 20 bestanden van hoogstens 1 MiB; de andere zijn
koppelingen); elk bestand kan tussen twee revisies worden vergeleken.

- **Alleen lezen.** De pagina’s kunnen niets wijzigen. WebDAV-clients en `svn` hebben nog steeds een account nodig,
  ook voor een openbare repository.
- **Alleen wat openbaar is.** Een repository die niet openbaar is, en een naam die niet bestaat, antwoorden allebei
  ‘There is no public repository at this address’, dus de pagina’s verraden niet welke namen bestaan.
- **Grenzen.** Eén adres mag 60 pagina’s tegelijk openen en daarna 2 per seconde, en 2 bestanden tegelijk downloaden;
  daarboven vraagt de pagina het zo meteen opnieuw te proberen. Een diff wordt bij 2 MiB afgekapt.
- **Zoekmachines** wordt gevraagd de pagina’s niet te indexeren. De pagina’s zijn in het Engels. Ze zien eruit als het
  paneel, donker of licht, zoals de browser van de bezoeker is ingesteld.
- Achter een proxy of met een SVN-hostnaam volgt het adres dat van de repository zelf (de openbare URL, of de
  SVN-hostnaam met dezelfde paden).

## Editor en AI-tools

De Editor-module draait VS Code in de browser (code-server) voor elk account met de rol developer of admin. Een admin zet
haar aan onder **Modules**. Elk account krijgt een eigen instantie, die start als het account de editor opent en stopt na
een tijd zonder verkeer.

**De terminal van de editor is een root-shell op de NAS.** Op QTS draaien de processen van het pakket als admin (uid 0),
behalve Forgejo en httpd. Geef de rol developer alleen aan mensen aan wie je ook het admin-wachtwoord van de NAS zou
geven.

### De editor openen

- **De editor openen** op de pagina Editor-instellingen (of `/ide/` op het adres van het paneel, of de Editor-hostnaam) opent de workbench
  in een nieuw tabblad. Het eerste verzoek start de instantie; zonder sessie komt eerst de aanmelding van het paneel.
- Via de gepubliceerde hoofdhostnaam, zolang de editor daar niet gepubliceerd is, zeggen de pagina Editor-instellingen, het
  Editor-tabblad, het Overzicht en **Een shell openen** dat de editor alleen vanuit het lokale netwerk opent, en linken ze naar
  het LAN-adres (`https://<NAS-adres>:<HTTPS-poort>/ide/`); voor een beheerder linken ze ook naar de schakelaar op de
  pagina **Publiceren**.
- De editor opent `<data>/projects`, waar de voorbeelden onder Editor-projecten hun projecten neerzetten. De instellingen, extensies en
  terminalgeschiedenis van het account staan in `<data>/ide/<account>` en de thuismap in `<data>/home/<account>`.
- De terminal heeft de tools van het pakket voorop in `PATH`: git, svn, node, npm, rsync, curl, bash en de rest.
  `bvs env shell <naam>` stapt in een omgeving uit Toolchains en `eval "$(bvs use <id>)"` zet een toolchain of runtime
  klaar. **Een shell openen** op de pagina Toolchains opent de editor met een terminal die al in die omgeving zit.
- De activiteit **Editor**, en het opdrachtenpalet, openen de editor met een shell in een omgeving of met de map van een
  app. De editor verschijnt in het tabblad Editor van het paneel; op een telefoon opent hij in een eigen browservenster.
  Alleen pagina’s van het eigen adres van het paneel mogen de editor in zich tonen: het pakket zegt dat de browsers bij
  elk antwoord van de editor, zodat een andere site de root-terminal van de editor niet onder een eigen pagina kan leggen.
- **Inactieve tijd** (standaard 30 minuten) stopt een instantie zonder browserverkeer, maar nooit zolang een programma
  dat vanuit haar terminals is gestart, een AI-agent die een extensie startte (de `claude` van Claude Code for VS Code,
  Codex, Gemini CLI) of iets wat daarvandaan bleef draaien nog draait. 0 zet het stoppen bij inactiviteit uit. Een
  account kan zijn eigen instantie op de pagina Editor-instellingen stoppen; een admin kan elke instantie stoppen.
- **Geheugen en processen** op de pagina Editor-instellingen toont wat een instantie draait en hoeveel geheugen ze gebruikt naast dat
  van de NAS, markeert wat haar draaiende houdt, beëindigt één proces met **Beëindigen** en zegt waarom de laatste
  stop kwam en wat die beëindigde. Ook een AI-agent die bij zijn prompt wacht, houdt de instantie draaiende: beëindig
  hem daar als hij niet meer nodig is.
- De weergave **Ports** van VS Code bereikt een ontwikkelserver op de NAS op `/ide/proxy/<port>/`; de eigen
  loopback-poorten van het pakket worden daar geweigerd.
- **New recipe**, in het menu van de map `recipes` van een project, of van een receptmap daarin, in de **Explorer**,
  vraagt om een naam en schrijft daar een nieuw recept: `recipe.json` en `build.sh` uit het startrecept van het pakket.
  In het opdrachtenpalet heet het **Bucket: New recipe**, en het werkt zolang een bestand van een project met een map
  `recipes` open is. Elke keer dat je een bestand van een recept opslaat, controleert de editor de recepten van het project
  met `bvs recipe lint` en toont de bevindingen in de weergave **Problems**. De editor waarschuwt voor de sha256 van het
  startrecept tot je de echte sha256 van je bron invult.

De editor antwoordt alleen zijn eigen pagina's. Een WebSocket, een formulier of het verzoek van een script dat een
andere pagina met de sessie van het account stuurt, vanaf een andere site of vanaf een andere poort van de NAS, krijgt
403 en start geen instantie; zo bereikt een andere pagina ook geen ontwikkelserver achter de weergave **Ports**.

### Extensies

- Extensies installeer je in de Extensions-weergave van de editor (Ctrl+Shift+X). Ze komen van Open VSX: de browser zoekt
  op open-vsx.org en toont de pagina en het pictogram van elke extensie van openvsx.eclipsecontent.org, en de NAS
  downloadt de extensie zelf van open-vsx.org. Browser en NAS moeten daarvoor allebei het internet bereiken.
- De eerste extensie van elke uitgever vraagt ‘Do you trust the publisher …?’. **Trust Publisher & Install**
  installeert hem; tot dan toont de extensie ‘Installing’.
- Elk account bewaart zijn extensies in `<data>/ide/<account>/extensions`, en een pakketupdate houdt ze.
- Het pakket brengt een set extensies en formatters mee, die de editor voor elk account installeert bij de eerste start,
  en opnieuw na een pakketupdate met een nieuwe set. Daarmee kleurt en formatteert de editor (**Format Document** in het
  opdrachtenpalet) Markdown, shellscripts, C, C++, Rust, Go, Pascal, Python, Perl, PHP, JSON, YAML, TOML,
  nginx-configuratie, CSS, HTML, JavaScript en TypeScript. ShellCheck markeert problemen in shellscripts,
  systemd-unitbestanden krijgen kleuring en aanvulling, de `.editorconfig` van een project bepaalt het inspringen,
  kleurcodes zoals `#ff8800` of `rgb(0, 128, 0)` verschijnen in elk bestand in hun kleur, en **Hex Editor: Open Active
  File in Hex Editor** in het opdrachtenpalet toont de bytes van een bestand.
- Rust wordt geformatteerd zodra de Rust-runtime op de pagina Toolchains is geïnstalleerd en de editor van het account
  opnieuw is gestart; tot dan meldt Format Document in een Rust-bestand dat het verzoek is mislukt.
- Op een ARM-NAS (het aarch64-pakket) worden Pascal, Perl en PHP gekleurd maar niet geformatteerd: het pakket heeft
  daarvoor geen build van Free Pascal, Perl of Mago.
- Een extensie uit de set die het account in de Extensions-weergave verwijdert, blijft verwijderd, ook na een
  pakketupdate, en een die het bijwerkt, houdt de nieuwere versie. Een standaardformatter die voor een taal is gekozen
  (**Format Document With…**, dan **Configure Default Formatter…**) blijft de keuze van het account.
- Elk `.conf`-bestand opent als nginx-configuratie. De taalnaam in de statusbalk zet een bestand op een andere taal.
- Ruff, dat Python formatteert, meldt één keer dat er geen extensie voor Python-omgevingen is geïnstalleerd. Het
  formatteert ook zonder.
- In de terminal van de editor installeert `code-server --install-extension <uitgever.naam>` ook een extensie van
  Open VSX.
- Sommige extensies tekenen hun eigen weergaven, zoals het paneel van Claude Code for VS Code, de hex-editor en de
  Markdown-voorbeeldweergave. De browser toont die weergaven alleen als hij het certificaat van het paneel vertrouwt.
  Met het zelfondertekende certificaat dat alleen via de waarschuwingspagina van de browser is aanvaard, blijven ze
  leeg, en het tabblad Editor en de pagina Editor-instellingen zeggen dat. Elk van deze wegen lost het op:
  - vertrouw het certificaat van de NAS op het apparaat: **Het certificaat downloaden** op de pagina Netwerk of in die
    melding, en voeg het toe aan de vertrouwde certificaten van het systeem of de browser
    ([Het certificaat](#het-certificaat));
  - een admin voegt op de pagina Netwerk een certificaat voor de naam van de NAS toe;
  - open de editor via de gepubliceerde hoofdhostnaam, zodra een admin hem daar publiceert.
- Claude Code for VS Code (uitgever Anthropic) installeer je vanuit de Extensions-weergave, en het paneel vraagt
  hoe je je aanmeldt bij Anthropic. **Claude Code: Open in Terminal** in het opdrachtenpalet start Claude Code in een
  terminal, en dat werkt met elk certificaat. De eigen chatfuncties van VS Code staan bij de start uit
  (`chat.disableAIFeatures`); die instelling raakt deze extensie niet.

### Claude Code, Gemini CLI en Codex CLI

Onder **AI-opdrachtregeltools** op de pagina Editor-instellingen installeert, bijwerkt en verwijdert elk account deze tools voor
zichzelf:

| Tool | Geïnstalleerd met | Commando |
|---|---|---|
| Claude Code | het installatieprogramma van Anthropic (`claude.ai/install.sh`), npm als dat mislukt | `claude` |
| Gemini CLI | `npm install -g @google/gemini-cli` | `gemini` |
| Codex CLI | `npm install -g @openai/codex` | `codex` |

- Ze komen in de thuismap van het account (`~/.local/bin`, `~/.npm-global/bin`), die in de `PATH` van de terminal
  staat; een ander account ziet ze niet.
- Elke installatie is een taak met haar logboek op de pagina Taken. Aanmelden bij elke tool (een account of een
  API-sleutel) gebeurt in de tool zelf, in de terminal van de editor.

### Programmeren met een AI-assistent

Elk project dat **Nieuw project** maakt, bevat een skill voor AI-assistenten: `.claude/skills/qnap-dev/` en een
`AGENTS.md` die ernaar verwijst. De skill, in het Engels, bevat richtlijnen voor alles rond een QNAP-NAS: wat het in de
shell is (een root op een RAM-schijf, BusyBox, geen systemd, geen pakketbeheerder, `getcfg` en `setcfg`), opslag en
paden, hoe je met dit pakket bouwt, ondertekent en publiceert (`bvs env run`, `bvs ldd`, `bvs qbuild`, de App Builder,
de kernelwerkplaats, de pakketrepository), poorten, back-ups en wat hij niet mag voorstellen, zoals `apt` op de NAS of
niet-ondertekende apps toestaan in App Center. Hij zegt duidelijk dat ondertekenen met een hardwaretoken niet wordt
ondersteund en niet is getest.

- Start de assistent in de map van het project (`cd <data>/projects/<naam>`), niet in `projects/`: elke tool zoekt in
  de map waarin hij start.
- Claude Code laadt de skill uit `.claude/skills/` als een taak hem nodig heeft. Codex CLI leest `AGENTS.md`. Gemini
  CLI leest `AGENTS.md` zodra de `.gemini/settings.json` van het project
  `{"context": {"fileName": ["AGENTS.md", "GEMINI.md"]}}` bevat.
- Het pakket bewaart de huidige skill in `/usr/bin/bucketvs/share/skills/qnap-dev`. Een project dat is gemaakt voordat
  het pakket hem had, of een project dat uit de galerij Voorbeelden is gekopieerd, krijgt hem door die map naar
  `.claude/skills/` van het project te kopiëren.
- De skill citeert de kennisbank van QNAP Club Poland (forum.qnap.net.pl) voor wat de NAS heeft en wat hij mist.
- De build van het pakket schrijft de skill ook als `qnap-skill-<versie>.zip`, dezelfde bestanden, voor gebruik buiten
  de editor: pak hem uit in `.claude/skills/` van een project of in `~/.claude/skills/`. De tekst van de skill valt onder
  CC BY 4.0 (zijn `LICENSE`): je mag hem delen en bewerken met de vermelding "QNAP guidelines skill by QNAP Club Poland
  / qnaputilities.com, CC BY 4.0".

## Toolchains en omgevingen

De pagina Toolchains downloadt wat nodig is om software op de NAS te bouwen:

| Type | Wat het is | Voorbeelden |
|---|---|---|
| Root-bestandssysteem | een Linux-systeem om in een omgeving uit te pakken | Debian 12 en 13, Ubuntu 22.04 en 24.04, Alpine 3.22 |
| Cross-toolchain | een compiler die voor een andere processor bouwt | Bootlin-toolchains voor aarch64, x86-64 en armv7, met glibc of musl |
| Runtime | een taal of een buildtool | Python, Go, Node.js, Zig, Rust, CMake, Ninja, Java (JDK) |
| QNAP | de eigen tools van QNAP | QDK 2.5.0, voor [`bvs qbuild`](#een-qpkg-bouwen-met-bvs-qbuild) en Exporteren als QPKG |
| Kernelbroncode | een Linux-kernelboom voor de [kernelwerkplaats](#kernelwerkplaats) | Linux 6.6.129 van kernel.org; de GPL-kernelrelease van QNAP voor QTS 5.2.3 |

Items voor een andere processor zijn verborgen; ‘Items voor andere processors tonen’ toont ze, maar ze kunnen niet
worden geïnstalleerd. De Bootlin-toolchains draaien op x86_64, dus een ARM-NAS heeft geen cross-toolchain.

### De catalogus

Het pakket bevat een ingebouwde catalogus. De ondertekende externe catalogus op
`https://pub.qnaputilities.com/bucketvs/catalog.json` kan items toevoegen zonder nieuw pakket. bvsd gebruikt hem
alleen als de ed25519-handtekening past bij de sleutel die in het pakket is ingebouwd en hij niet ouder is dan de
catalogus in gebruik. ‘De catalogus vernieuwen’ haalt hem meteen op, en de ingebouwde cron-taak ‘Vernieuwen van de
toolchaincatalogus’ haalt hem elke dag om 04:15 op.

### Installeren en verwijderen

Installeren is werk voor een admin, getoond met de voortgang op de pagina Taken:

1. Het archief wordt gedownload naar `toolchains/cache/` in de datamap. Een onderbroken download gaat
   verder als de installatie opnieuw wordt gestart.
2. De sha256 wordt vergeleken met de catalogus. Bij een verschil wordt het bestand verwijderd.
3. Een cross-toolchain of een runtime wordt uitgepakt naar `toolchains/<kind>/<id>` en het archief wordt verwijderd.
   Een root-bestandssysteem of kernelbroncode blijft een archief; elke omgeving of kernelwerkruimte pakt een eigen
   kopie uit. QNAP publiceert zijn GPL-kernelrelease in twee delen: beide worden gedownload, elk gecontroleerd met een
   eigen sha256, en samengevoegd.

Een vol volume laat de installatie mislukken met ‘no space left on’ en het koppelpunt van het volume.

‘Verwijderen’ verwijdert een geïnstalleerd item. Omgevingen die van een root-bestandssysteem zijn gemaakt, zijn
kopieën en blijven.

### Een toolchain of runtime gebruiken

`bvs use <id>` toont de `export`-regels (zoals `PATH`) die een geïnstalleerde cross-toolchain of runtime in een shell
bruikbaar maken:

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

De pagina Toolchains toont dezelfde regels onder ‘bvs use’ voor elk geïnstalleerd item.

### Omgevingen

Een omgeving is een Linux-systeem dat van een root-bestandssysteem is uitgepakt naar `envs/<naam>` in de
datamap. Ze is wijzigbaar: `apt install build-essential` werkt in een Debian- of Ubuntu-omgeving, en wat je
installeert blijft daar.

- ‘Nieuwe omgeving’ (admin) downloadt eerst het root-bestandssysteem als het niet is geïnstalleerd, en pakt het dan
  uit.
- Zolang er geen omgeving is, biedt de lijst de Ubuntu 24.04 builder aan, waarin de app-voorbeelden bouwen, met één
  knop (admin): de taak staat in de lijst, en de omgeving verschijnt als de taak klaar is. Er wordt niets vanzelf
  aangemaakt.
- Een naam heeft 1 tot 64 tekens: kleine letters, cijfers, `.`, `_` of `-`, beginnend met een letter of een cijfer.
- Een voorinstelling uit de galerij Voorbeelden installeert ook pakketten met apt-get voordat de omgeving verschijnt:
  de Ubuntu 24.04-builder (de standaardbuilder) en de Debian 12-builder, allebei volledig, met de buildtools van de
  App Builder en patchelf; Clang/LLVM (LLVM 18), Fortran (gfortran 13), Kernel builder, .NET 8 SDK en Kotlin (Kotlin
  1.3.31), allemaal op Ubuntu 24.04. De glibc van elke voorinstelling is hoogstens 2.39, dus wat je erin bouwt, draait
  op elke NAS waarop het pakket draait ([Op welke NAS-modellen een app draait](#op-welke-nas-modellen-een-app-draait)). Als apt-get
  mislukt, blijft er geen omgeving achter. Vanuit een shell:
  `bvs env create <naam> --from <rootfs-id> --package <naam> --package <naam>`.
- ‘Klonen’ maakt een volledige kopie onder een nieuwe naam, bijvoorbeeld om een wijziging te proberen zonder het
  origineel aan te raken.
- ‘Exporteren’ pakt de omgeving in als `toolchains/exports/<naam>-<tijd>.tar.zst`.
- ‘Verwijderen’ haalt haar weg. bvsd weigert dat zolang er een shell of een proces in draait. Bestanden in `projects/`
  horen niet bij de omgeving en blijven.

In een omgeving:

- is `projects/` van de datamap zichtbaar als `/projects`, en start een shell daar;
- is `HOME` de `/root` van de omgeving, en `LANG` is `C.UTF-8`;
- zijn `/proc`, een alleen-lezen `/sys` en een kleine eigen `/dev` gekoppeld, en worden `resolv.conf` en `hosts` van
  de NAS erin gekopieerd;
- horen de koppelingen bij de eigen mount-namespace van de shell en verdwijnen ze als die eindigt, dus blijft er op de
  NAS niets gekoppeld.

Hetzelfde vanuit een shell (de voorbeelden roepen `bvs` bij naam aan, zoals een cron-taak en een SSH-sessie op de NAS
dat kunnen zolang het pakket draait):

```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` en `bvs env run` starten in de map van waaruit je ze uitvoert. Een map onder `projects/` van de
datamap is dezelfde map onder `/projects` in de omgeving. Elke andere map (de checkout van een CI-taak,
een map in een share) wordt gekoppeld op `/work` en het commando start daar. Vanuit `/` starten ze in `/projects`.

### Een QPKG bouwen met bvs qbuild

`bvs qbuild` bouwt een QPKG uit de map waarin je het uitvoert (een map met `qpkg.cfg`, zoals de QPKG-sjabloon van
Nieuw project die maakt). Het draait `qbuild` van QNAP in een Debian- of Ubuntu-omgeving. qbuild roept
systeemprogramma’s aan via hun volledige paden, en in de omgeving leiden die paden naar de programma’s van de omgeving
in plaats van die van de NAS. Het pakket komt in `build/` van de map.

```bash
bvs tc install qdk-2.5.0-x86_64      # eenmalig; qdk-2.5.0-aarch64 op een ARM-NAS
cd /share/CACHEDEV1_DATA/BucketVS/projects/MyTool
bvs qbuild                           # --env <naam> kiest de omgeving
```

Het gebruikt de omgeving `ubuntu2404-builder` (het voorbeeld ‘Ubuntu 24.04 builder’), anders `debian12-builder`,
anders een andere Debian- of Ubuntu-omgeving, en heeft daarin `rsync` nodig. Als er iets ontbreekt, stopt het en noemt
het de opdracht die het toevoegt, bijvoorbeeld `bvs env run ubuntu2404-builder -- apt-get install -y rsync`.
Argumenten na `bvs qbuild` gaan naar qbuild. Voordat qbuild draait, zegt `bvs qbuild` welk [geval van
code-ondertekening](#een-qpkg-code-ondertekenen) voor de map geldt.

Een omgeving heeft eigen bestanden en tools. Alles erin draait als root, ziet elk proces op de NAS en gebruikt het
netwerk van de NAS.

### Een QPKG code-ondertekenen

App Center controleert de codehandtekening van een pakket bij de installatie. Om te ondertekenen is een
code-ondertekeningscertificaat nodig dat QNAP je heeft uitgegeven. `bvs qbuild` noemt op zijn eerste regel een van
drie gevallen, bijvoorbeeld `bvs: code signing, case 1 (a certificate as files): …`:

| Geval | In de map staat | Wat er gebeurt |
|---|---|---|
| 1, een certificaat als bestanden | `certificate` en `private_key`, optioneel `ca_certs` (de keten) | qbuild ondertekent het pakket met `openssl`; de omgeving heeft `openssl` en `bsdextrautils` nodig |
| 2, een certificaat op een hardwaretoken | `certificate_hsm` | niet ondersteund en niet getest: `bvs qbuild` stopt voordat er iets gebouwd wordt |
| 3, geen certificaat | geen van die bestanden | het pakket wordt ongetekend gebouwd |

- **Geval 1.** Zet de drie bestanden naast `qpkg.cfg`. Mist de omgeving een programma, dan noemt `bvs qbuild` de
  opdracht, bijvoorbeeld `bvs env run ubuntu2404-builder -- apt-get install -y openssl bsdextrautils`. Het weigert
  een map met maar één van de bestanden `certificate` en `private_key` (qbuild zou waarschuwen en een ongetekend
  pakket bouwen) en een map waarin een van beide leeg is. Houd de bestanden buiten git: de `.gitignore` van het
  QPKG-sjabloon noemt ze. Bewaar ze in CI als secrets van de repository en schrijf ze alleen voor de build in de
  map.
- **Geval 2.** qbuild zou de plek van de sleutel op het token lezen uit variabelen die het in de omgeving niet
  bereiken, en het pakket is niet met een token getest. Bouw zonder `certificate_hsm` en onderteken het pakket op de
  machine met het token met de eigen hulpmiddelen van QDK; de QNAP-skill voor AI-assistenten
  (`references/signing.md`) beschrijft de route van QDK, die dit pakket nooit heeft uitgevoerd.
- **Geval 3.** Op een apparaat met QTS 6.1 installeerde `qpkg_cli -m` van een ongetekend pakket niets, terwijl
  `sh <bestand>.qpkg` in een gedeelde map het installeerde en startte. De [pakketrepository](#pakketrepository) neemt het
  alleen aan zolang een admin ongetekende pakketten toestaat, en App Center op een andere NAS installeert het alleen
  als de eigenaar apps zonder geldige digitale handtekening toestaat, wat dit pakket nooit aanraadt. **Exporteren als QPKG** geeft
  altijd dit geval.
- `qbuild --sign` voegt een GnuPG-handtekening toe; dat is niet de codehandtekening die App Center en de repository
  lezen.

## App Builder

De App Builder compileert gekozen programma’s in één map, het prefix van de app (standaard `/usr/app/my_app_prefix`),
in een omgeving.

Het tabblad **Voorbeelden** onder **Apps** bevat kant-en-klare apps, omgevingen, CI-workflows, cronjobs en
Editor-projecten. **Code bekijken** op een kaart toont vóór gebruik wat het voorbeeld maakt, voor elke rol: de
bestanden van een Editor-project, het workflowbestand, de opdracht van de cronjob, of bij een app de recepten in
build-volgorde met `build.sh` en `recipe.json` van elk recept. De weergave is alleen-lezen en toont tekstbestanden;
een omgeving heeft geen bestand, dus haar kaart heeft deze knop niet. Een voorbeeld gebruiken vraagt de rol
developer, een omgeving de rol admin: de kaart van een omgeving biedt een developer geen **Dit voorbeeld gebruiken**.

### Recepten

Elk programma wordt gebouwd vanuit een recept. Een recept noemt zijn bron met een sha256, de recepten waarvan het
afhangt, de buildtools die de omgeving nodig heeft, en een controle die bewijst dat het resultaat werkt (zoals
`bin/bash --version`). Er zijn recepten voor bibliotheken (zlib, openssl, ncurses, readline en andere), shells (bash,
zsh, busybox), tools (coreutils, curl, git, jq, rsync, tmux, htop, nano en andere), servers (nginx, redis, openssh,
dropbear) en een taal (lua).

- Een recept wordt pas gebouwd na de recepten waarvan het afhangt.
- Een nieuwe build hergebruikt het resultaat van een recept zolang de invoer ongewijzigd is (de bron, het recept, de
  afhankelijkheden, de compiler en C-bibliotheek van de omgeving, het prefix, en of er dynamisch of statisch wordt
  gelinkt) en elk bestand dat het installeerde nog in het prefix staat.
- De linkwijze bepaalt hoe programma’s hun bibliotheken vinden. **dynamisch** geeft elk programma een RUNPATH van
  `<prefix>/lib`. **relocatable** schrijft het relatief aan de eigen map van het programma (`$ORIGIN/../lib`), zodat
  de boom zijn bibliotheken in elke map vindt; paden die in programma’s zijn gecompileerd, zoals de terminfo-map,
  noemen nog steeds het prefix. **bundle** is relocatable en kopieert daarnaast de bibliotheken naar `lib` die de
  programma’s uit de build-omgeving laden (zoals libstdc++), zodat de app alleen de C-bibliotheek van de NAS nodig
  heeft. **statisch** linkt de recepten die dat ondersteunen statisch. Wisselen tussen dynamisch, relocatable en
  bundle bouwt niets opnieuw.
- Een statische app weigert recepten die niet statisch kunnen linken (openssh, dropbear en tmux), en noemt elk met de
  reden, tenzij de app is ingesteld om die als bundle te bouwen. De pagina Nieuwe app en **Wijzigen** bieden alle vier
  de wijzen en, voor een statische app, die keuze.
- Een statisch programma dat gebruikers, groepen of hostnamen opzoekt, leest die alleen uit `/etc/passwd`,
  `/etc/group`, `/etc/hosts` en de DNS van de NAS zelf, wat `/etc/nsswitch.conf` ook noemt, dus accounts uit een
  domein of LDAP kent het niet. Bouw zulke programma’s dynamisch als die accounts ertoe doen. Het paneel waarschuwt
  als een statische app er een bevat.
- Na het laatste recept draait de controle van elk recept opnieuw over het voltooide prefix.
- Configuratiebestanden onder `etc/` van het prefix overleven een nieuwe build: een bewerkt bestand blijft, en de
  nieuwe standaard komt ernaast als `<file>.bvs-new`.

### Receptupdates

Nieuwe en bijgewerkte recepten komen ook als ondertekende catalogus van pub.qnaputilities.com, zodat ze de NAS
bereiken vóór de volgende versie van Bucket & Code Server IDE. bvsd zoekt één keer per dag naar een nieuwere catalogus. Hij gebruikt
er een alleen als de handtekening past bij de sleutel die in bvsd is ingebouwd, elk bestand overeenkomt met de lijst
van de catalogus en de recepten nieuwer zijn dan die met het pakket kwamen; een catalogus die een controle niet
doorstaat, wordt nooit gebruikt. Zolang een catalogus in gebruik is, markeert Nieuwe app de recepten die hij brengt
als **nieuw in de catalogus** of **bijgewerkt door de catalogus**.

De kaart **Receptenbibliotheek** op de pagina Apps toont welke bibliotheek builds gebruiken (**Pakketbibliotheek**
met de versie van het pakket, of **Catalogus** met de versie van de catalogus) en de laatste controle met het
resultaat. Een admin heeft daar twee knoppen; de tweede verschijnt zolang een catalogus in gebruik is:

- **Zoek naar receptupdates** controleert meteen, zonder op de dagelijkse controle te wachten.
- **Bibliotheek van het pakket gebruiken** gaat terug naar de recepten die met Bucket & Code Server IDE kwamen. De catalogus blijft
  opzijgezet tot er een nieuwere komt; builds gebruiken dan de nieuwere.

Na een wissel hergebruikt een nieuwe build elk recept waarvan de bestanden niet veranderd zijn. Voor elke build
controleert bvsd de bestanden van de catalogus opnieuw. Is er een op de NAS veranderd, dan stopt de build voordat er
iets draait, en builds gebruiken de vorige catalogus of de bibliotheek van het pakket.

### Een app bouwen

**Apps**, **Nieuwe app** (rol developer) vraagt om een naam, het prefix (`/usr/app/<naam>` tenzij je een ander kiest),
de omgeving waarin de app wordt gebouwd (een Debian- of Ubuntu-omgeving; de galerij Voorbeelden maakt de ‘Ubuntu
24.04 builder’, die de pagina als eerste kiest), het doel (de architectuur van deze NAS, of aarch64 of armv7 als
cross-build), de NAS-modellen waarop hij draait, de linkwijze en de recepten.
De recepten waarvan die afhangen, worden in bouwvolgorde toegevoegd.

Een app kan ook de recepten nemen van één project dat een eigen map `recipes/` heeft. Kies dat project bij **Recepten
uit een project** in de stap **Pakketten**. De recepten van het project staan bovenaan, onder de naam van het project
en gemarkeerd met **project**, en daarna volgen die van de bibliotheek. Een recept van het project met de naam van een
recept uit de bibliotheek vervangt dat recept in deze app en toont welke versie uit de bibliotheek het vervangt. Een
recept dat het project weglaat, wordt met de reden genoemd. Een gekozen recept dat in de lijst van het project
ontbreekt, wordt onder de keuzelijst genoemd en uit de app weggelaten; het komt terug zodra je een lijst kiest die het
heeft. De pagina van de app noemt het project, en een developer opent vanuit die naam de projectweergave. Een
viewer ziet de recepten van de laatste build van de app in de volgorde die het logboek noemt, elk met zijn toestand en
logboek; voor de eerste build is er geen volgorde om te tonen.

Elke linkwijze is een kaart, met een afbeelding van waar de programma’s hun bibliotheken vinden, wat de wijze kost aan
grootte, bij updates en in waar de app draait, en welke recepten haar ondersteunen. Voordat je recepten kiest, tellen
de kaarten de hele receptenbibliotheek; als je terugkomt bij de eerste stap, tellen ze de recepten van je build. Voor
een statische app kies je wat er gebeurt met recepten die niet statisch kunnen linken: ze als bundel bouwen, of ze
weglaten.

- **Bouwen** (of **Opnieuw bouwen**) voert een taak uit: ze downloadt elke bron en controleert de sha256, installeert
  de buildtools die de recepten in de omgeving nodig hebben, bouwt in een kopie van de vorige build van de app, en
  controleert elk programma.
- Een build die mislukt, wordt geannuleerd of wordt onderbroken, laat de app zoals ze was: activeren, downloaden en
  exporteren blijven de nieuwste geslaagde build gebruiken.
- Er draait één build van een app tegelijk, en een build wacht zolang programma’s van de actieve app draaien (stop die
  eerst).
- De pagina van de app toont de bouwvolgorde, de status en het logboek van elk recept, de bestanden onder het prefix,
  en het archief van de nieuwste geslaagde build (**Downloaden**, een `.tar.zst`).
- **Wijzigen** op de pagina van de app stelt een andere omgeving, de NAS-modellen waarop hij draait, een andere
  linkwijze, het project waaruit hij recepten neemt of andere recepten in; de volgende build past dat toe.
- **Afhankelijkheden**, een tabblad van de pagina van de app, toont wat elk ELF-bestand van de nieuwste build nodig
  heeft en waar de loader van de NAS het vindt, gelezen zonder iets uit te voeren. Een ontbrekende bibliotheek, een
  versie die de bibliotheek niet definieert, of een RUNPATH die buiten de app wijst, staat als bevinding met de
  oplossing in de lijst. Een developer voert een oplossing uit met haar knop: **… toevoegen en bouwen**,
  **Overschakelen op … en bouwen**, **Opnieuw bouwen**, **RUNPATH patchen**, **Interpreter instellen**, **Andere
  omgeving kiezen** of **Alleen voor deze NAS bouwen**. Een oplossing zonder knop (een pakket in een omgeving installeren, bouwen voor een andere
  machine) is een zin om naar te handelen. Onder de bevindingen is elk bestand met wat het nodig heeft getekend als
  graaf (programma’s, de bibliotheken van de app, die van de NAS en de ontbrekende in kolommen; een gestreepte lijn is
  een versie die de bibliotheek mist, een gestippelde lijn een bibliotheek die nergens is gevonden) of als boom; kies
  een bestand voor de gegevens ervan. Op de NAS toont `bvs deps <app>` hetzelfde.
- **ELF-inspecteur** (Toolchains, rol developer) controleert één bestand: uit de build van een app, uit de projectmap
  of uit een omgeving, en voor een admin elk bestand op de NAS, zoals het op deze NAS of in een omgeving zou draaien.
  `bvs ldd <file>` doet hetzelfde op de NAS (`--env <name>` voor een omgeving).
- **Elke build** voert dezelfde controle over zijn resultaat uit voordat hij de app vervangt, en mislukt bij een
  probleem dat een programma op deze NAS tegenhoudt, of bij een programma dat een nieuwere glibc nodig heeft dan de
  app toestaat, met het probleem en de oplossing in het bericht van de taak.
- **Patchgereedschap** (het tabblad Afhankelijkheden) stelt de RUNPATH in (de `lib` van de app, de vorm met `$ORIGIN`,
  of een map van de app) of de programma-interpreter van gekozen bestanden van de nieuwste build, met de patchelf van
  de omgeving, op een kopie die de build alleen vervangt als het lukt. **Voorbeeld** toont elk bestand ervoor en erna
  en welke bibliotheken de nieuwe RUNPATH niet in de app vindt; **Patch toepassen** voert het uit. De volgende build
  schrijft de RUNPATH’s weer in de linkwijze van de app.

Een Qt-build voert Qt’s eigen hulpmiddelen uit (moc, rcc, en lrelease van Linguist voor vertalingen). In een
cross-build bouwen de Qt-recepten (`qtbase`, `qttools`) die hulpmiddelen daarom eerst voor deze NAS, in `host/` onder
het prefix, en daarna Qt voor het doel; de recepten die op Qt bouwen, voeren de hulpmiddelen van daar uit. Niets op
het doel voert `host/` uit. Het hoort bij de build van de app, dus het archief en **Exporteren als QPKG** bevatten
het ook.

### Je eigen recepten

Heeft de receptenbibliotheek geen recept voor een programma, dan kan een project een eigen recept meebrengen. De
recepten staan in de map `recipes/` van het project, in het formaat van de bibliotheek, en alleen de apps die het
project noemen bouwen ze. Een pakket ontstaat zo in vijf stappen:

1. **Maak het project.** In [Nieuw project](#nieuw-project) maakt de sjabloon **Package from source** (pakket uit
   broncode) een map met een startrecept dat de naam van het project draagt, een Makefile voor twee apps (een voor de
   processor van deze NAS, een voor aarch64, een cross-build op een x86_64-NAS) en het start- en stopscript van het
   pakket uit de stap **Dienst**.
2. **Schrijf het recept.** **New recipe** in de editor voegt nog een recept toe vanuit hetzelfde startrecept
   ([De editor openen](#de-editor-openen)). Zet het echte bronadres van het programma en de sha256 ervan in `recipe.json`
   (`curl -fsSL <adres> | sha256sum` in de terminal van de editor geeft de som) en de bouwstappen in `build.sh`. Een
   receptmap bevat `recipe.json`, `build.sh` en het vendorbestand dat `recipe.json` noemt, en verder niets; een patch
   gaat dus als here-document in `build.sh`.
3. **Controleer het.** Elke keer dat je een bestand van een recept opslaat, worden de recepten van het project
   gecontroleerd en staan de bevindingen in de weergave **Problems**; `make lint` in de terminal en de kaart
   **Recepten** in de [projectweergave](#de-projectweergave) geven dezelfde bevindingen. De controle leest
   `recipe.json` en ontleedt `build.sh` met `bash -n`. Het recept wordt nooit uitgevoerd.
4. **Bouw.** **Bouwen** in de projectweergave maakt de twee apps als ze ontbreken en bouwt ze na elkaar. Een build
   leest de recepten van het project één keer, bij de start, dus een bestand dat later wordt opgeslagen wacht op de
   volgende build. Een recept van het project met de naam van een recept uit de bibliotheek vervangt dat recept alleen
   in de apps van dit project, en het logboek van de build zegt het.
5. **Verpak en installeer.** `make qpkg` in de terminal van de editor zet de nieuwste geslaagde build van elke app in het
   pakket, met QDK uit Toolchains, en laat een app zonder zo’n build weg. Staan de bestanden voor de codeondertekening
   in `qpkg/` naast `qpkg.cfg`, of in een map die `QPKG_CERT_DIR` noemt, dan is het pakket ondertekend ([Een QPKG
   code-ondertekenen](#een-qpkg-code-ondertekenen)), en **Testuitrol** in de projectweergave (admin) installeert het
   op deze NAS.

Voor recepten van een project gelden een paar regels:

- Een recept dat het project weglaat (een extra bestand in de map, een link, een bestand boven de groottegrens) wordt
  met de reden genoemd op de kaart Recepten en door de controle, en een build die het nodig heeft, wordt geweigerd.
- Wordt de map van het project hernoemd of verwijderd, dan bouwen de apps ervan niet: de build antwoordt "Er is geen
  project ‘…’" totdat **Wijzigen** op de pagina van de app de nieuwe naam van het project of **Geen: alleen de bibliotheek**
  instelt.
- Recepten van een project draaien als root in de bouwomgeving, net als die van de bibliotheek. Elke developer kan
  ze schrijven en bouwen, zoals elke developer het Makefile van een project via Bouwen kan uitvoeren. Een viewer ziet
  de namen van de recepten van een app in het logboek van de build, nooit de receptbestanden.
- Een recept van een project blijft in het project. Het komt alleen in de receptenbibliotheek, en in de ondertekende
  catalogus die elke NAS ophaalt, via een beoordeelde wijziging van Bucket & Code Server IDE zelf.
- Een recept waarvan de build pakketten van npm, Go of Cargo nodig heeft, zet ze in een vendorbestand. Die lijst
  schrijven `npm-vendor.py`, `go-vendor.py` en `cargo-vendor.py` in `/usr/bin/bucketvs/share/bucketvs/tools/`; ze
  hebben een Python-runtime uit [Toolchains](#toolchains-en-omgevingen) nodig.

### Op welke NAS-modellen een app draait

Een programma dat tegen glibc is gebouwd, start alleen op een NAS waarvan de glibc minstens zo nieuw is als de
nieuwste versie die het programma vraagt. Het pakket rekent op glibc 2.39 of nieuwer op elke NAS waarop het draait
(QTS 6.0, QuTS hero 6.0 en nieuwer): zijn glibc-ondergrens. 2.39 is de glibc van Ubuntu 24.04 en van QuTS hero 6.0.1,
gelezen uit de eigen `/lib/libc.so.6` van die firmware; QNAP publiceert geen glibc-versie, en zijn release notes noemen geen QTS 6.0. De test-NAS’en met QTS 6.1.0 en QuTS hero 6.1.0 hebben 2.43.

**Draait op** in de nieuwe app en in **Wijzigen** kiest een van twee:

- **Elke NAS waarop het pakket draait** (standaard): geen programma mag een glibc boven 2.39 nodig hebben. Bouw zo’n
  app in een omgeving met glibc 2.39 of ouder, zoals de Ubuntu 24.04 builder (2.39) of de Debian 12 builder (2.36).
  Dit is de keuze voor **Exporteren als QPKG** en de pakketrepository.
- **Alleen deze NAS**: programma’s mogen de glibc van deze NAS nodig hebben, voor software die een nieuwere nodig
  heeft. Zo’n app wordt voor de architectuur van deze NAS gebouwd en wordt nooit als QPKG geëxporteerd.

Een cross-build (aarch64 of armv7) is altijd voor elke NAS. De nieuwe app waarschuwt wanneer de glibc van de gekozen
omgeving boven wat de app toestaat ligt. Na elke build leest bvsd de glibc-versie die elk programma en elke bibliotheek
vraagt, ook bij een cross-build. Een bestand boven de grens laat de build mislukken, en de oplossingen worden genoemd:
een andere omgeving (**Andere omgeving kiezen**), de linkwijze static of **Alleen voor deze NAS bouwen**. De pagina
van de app zegt welke glibc de nieuwste geslaagde build nodig heeft, en het tabblad **Afhankelijkheden** noemt elk
bestand boven de grens.

Op de NAS voert `bvs tree-deps <prefix> --tree <map> --glibc-max 2.39` dezelfde controle uit over elke appboom.

### Een app op de NAS activeren

**Activeren** (admin) maakt het prefix een koppeling naar de nieuwste geslaagde build van de app, zodat de programma’s
ervan op de NAS draaien vanuit `/usr/app/<naam>/bin`. Activeren vervangt nooit iets wat het zelf niet heeft gemaakt:
een map, een bestand of een andere koppeling op het prefix wordt met de reden geweigerd. De systeemschijf van QTS
wordt bij elke start opnieuw opgebouwd, daarom maakt het pakket de koppelingen bij elke start opnieuw. **Deactiveren**
(admin) verwijdert de koppeling; de app verwijderen deactiveert haar eerst en verplaatst haar voor 7 dagen naar de
prullenbak. Een developer kan een app alleen verwijderen zolang ze niet actief is. Een app die voor een andere
architectuur is gebouwd, kan hier niet worden geactiveerd; download het archief voor een NAS van dat soort.

### Een app exporteren

**Exporteren als QPKG** (admin) pakt de nieuwste geslaagde build in als een eigen QPKG, dat de app op een andere NAS
installeert zonder dit pakket. Het weigert een app die alleen voor deze NAS is gebouwd, en stopt vóór het inpakken
wanneer een programma van de build een glibc boven 2.39 nodig heeft (zie [Op welke NAS-modellen een app
draait](#op-welke-nas-modellen-een-app-draait)). Daarvoor is QDK nodig: installeer ‘QNAP QDK 2.5.0’ op de pagina
Toolchains, tabblad QNAP. Zolang QDK niet is geïnstalleerd, wordt de knop niet getoond en zegt de pagina van de app
waar je het haalt. De export draait qbuild in de omgeving van de app, op dezelfde manier als [`bvs
qbuild`](#een-qpkg-bouwen-met-bvs-qbuild). Elke build installeert daar `rsync`; bij een app die het laatst met een
oudere versie van het pakket is gebouwd, noemt de weigering de opdracht die het toevoegt. Het geëxporteerde pakket
maakt bij de start de koppeling van zijn prefix en verwijdert die bij het stoppen. Het is niet code-ondertekend: het
logboek van de taak noemt [geval 3](#een-qpkg-code-ondertekenen), net als een regel onder de knop. In een SSH-sessie
zetten `/etc/init.d/<naam>.sh disable` en `enable` de schakelaar van het geëxporteerde pakket in App Center, en de
koppeling van het prefix blijft zoals die is. Bij elke start voegt het geëxporteerde pakket ook een regel toe aan
`/etc/profile` en `/root/.profile`, die `bin` en `sbin` van de app achter de eigen opdrachten van de NAS in het `PATH`
van een SSH-aanmelding zet, voor admin en elk ander account dat mag aanmelden; bij verwijderen gaat de regel er weer
uit.

## Pakketrepository

Het pakket houdt een App Center-repository bij: QPKG-pakketten die erin worden gepubliceerd, kunnen vanuit App Center
op andere NAS-apparaten worden geïnstalleerd, net als de apps van elke repository.

- **Het adres.** `bvs repo list` toont het (en `GET /api/v1/repo`): `<base>/repo/repo.xml`, waarbij `<base>` de
  gepubliceerde hostnaam voor pakketten is (anders de gepubliceerde hoofdhostnaam) zolang het pakket op internet is
  gepubliceerd ([Publiceren op internet](#publiceren-op-internet-cloudflare-tunnel)), anders de openbare URL als die
  is ingesteld, anders `http://<NAS-adres>:9950` (`https://…:9951` als gewoon HTTP alleen doorstuurt). Op de andere
  NAS: App Center, **Instellingen**, **App-repository**, **Toevoegen**, en dat adres.
- **Publiceren.** `bvs repo publish <file.qpkg>` (op de NAS, als root) of `POST /api/v1/repo/packages` met het bestand
  als body (rol developer). De architectuur komt uit de QDK-bestandsnaam `<name>_<version>_<arch>.qpkg` (`x86_64`,
  `arm_64`, `arm-x41`), of uit `--arch`. Naam, versie, beschrijving en auteur komen uit het pakket zelf;
  `PUT /api/v1/repo/packages/<name>/meta` en `…/icon` (een PNG, admin) wijzigen wat App Center toont. Een pakket mag
  niet `default` heten: zo heet het eigen pictogram van de repository.
- **Alleen ondertekende pakketten.** Een pakket zonder codehandtekening wordt geweigerd, omdat App Center zulke
  pakketten alleen installeert op een NAS die is ingesteld om ze toe te staan. De repository beschouwt een pakket als
  ondertekend als het codesignatuurblok een PKCS#7-handtekening met een certificaat en een ondertekenaar is. Hij
  controleert die handtekening en de certificaten niet tegen die van QNAP; dat doet App Center als het het pakket
  installeert. Een admin kan `repo.accept_unsigned` in de instellingen aanzetten voor een netwerk waar dat bekend is;
  die pakketten zijn in hun beschrijving gemarkeerd. Pakketten die de App Builder exporteert, zijn niet ondertekend en
  hebben deze instelling dus nodig.
- **Versies.** Elke versie blijft downloadbaar, maar App Center ziet één versie per pakket, de versie die een admin in
  de lijst zet. De eerste versie van een pakket van een admin komt meteen in de lijst; een latere als de admin haar
  verplaatst (`POST /api/v1/repo/packages/<name>/latest`, of `bvs repo publish --latest`). De versie van een
  developer, ook de eerste van een nieuw pakket, staat nergens in de lijst totdat een admin een versie de nieuwste
  maakt: elke NAS die de repository heeft toegevoegd, installeert de versie uit de lijst als root. Een versie
  verwijderen (admin) verwijdert al haar architecturen; als het de versie uit de lijst was, staat er geen versie in de
  lijst totdat een admin een andere kiest.
- **Uploads.** Twee uploads worden tegelijk gekopieerd; de repository weigert een upload die minder dan 256 MiB vrij
  zou laten op het datavolume. De repository hoort niet bij een back-up: publiceer de pakketten na een herstel
  opnieuw.
- **In het paneel.** De pagina **Pakketten** toont het adres om te kopiëren, de pakketten met hun versies en of ze
  ondertekend zijn; een developer uploadt er een QPKG, en een admin zet een versie als nieuwste en verwijdert een
  versie. De kaart **Code-ondertekening** toont de [drie gevallen](#een-qpkg-code-ondertekenen), hoeveel vermelde
  versies geen handtekening hebben, en de schakelaar van de admin voor ongetekende pakketten, die eerst om bevestiging
  vraagt voordat hij aangaat. De pagina van een app toont de pipeline (bouwen, exporteren als QPKG, publiceren) met
  een knop **Publiceren**.

## Kernelwerkplaats

De kernelwerkplaats bouwt kernelmodules voor de kernel die de NAS draait, controleert elk tegen die kernel, en laadt
het. QNAP heeft de broncode van zijn huidige kernels niet gepubliceerd (de GPL-releases eindigen bij QTS 5.2.3),
daarom leest de werkplaats wat de draaiende kernel over zichzelf toont en configureert een kernelboom die daarbij
past.

### Wat je nodig hebt

- De omgeving Kernel builder uit de galerij Voorbeelden (Ubuntu 24.04 met GCC 13 en de kernelbuildtools). Een module
  moet met dezelfde hoofdversie van GCC worden gebouwd als de kernel; de werkplaats vergelijkt ze.
- De rol admin, en root in een terminal: de commando’s `bvs kernel` gebruiken de CLI-socket van bvsd of laden modules.

### Het profiel

`bvs kernel profile` toont wat de draaiende kernel is: de release (`6.6.129-qnap`), de compiler waarmee QNAP hem
bouwde, de vermagic van de eigen modules van QNAP, de grootte van `struct module` daarin en waar init en exit zitten,
of modules ondertekend moeten zijn, en de instellingen die een kernelboom krijgt om erbij te passen.

### De werkruimte

`bvs kernel prepare` maakt de werkruimte van de draaiende kernel in `kernels/<release>` van de datamap:

1. Hij neemt de kernelbroncode die de catalogus voor deze versie heeft (`bvs kernel sources`): de eigen GPL-boom van
   QNAP als QNAP er een publiceerde, anders de boom van kernel.org, waarin de eigen wijzigingen van QNAP ontbreken.
   `--source` kiest een andere.
2. Hij configureert de boom: met de configuratie van QNAP voor dit model als de boom van QNAP is, anders met de
   instellingen van het profiel. `--option NAME=m` voegt een instelling toe, bijvoorbeeld een stuurprogramma.
3. Hij bereidt de boom voor op het bouwen van modules (`--full` bouwt in plaats daarvan de hele kernel, wat veel
   langer duurt en de controle de geëxporteerde symbolen van de kernel laat zien).
4. Hij controleert dat de boom precies voor de release van de draaiende kernel bouwt.

Hij draait als taak op de pagina Taken. Een mislukte prepare houdt de boom van de vorige.

### Een module bouwen

- Je eigen module is een map in `projects/` met een `Kbuild`-bestand. `bvs kernel make` in die map (of
  `bvs kernel make --project <folder>`) bouwt hem. Het voorbeeld ‘Kernel module’ is zo’n project.
- Een stuurprogramma van de kernel dat QNAP niet meelevert, wordt gebouwd vanuit zijn map in de kernelboom:
  `bvs kernel make --dir drivers/usb/serial`. Het voorbeeld ‘USB serial driver ch341’ bouwt het stuurprogramma ch341
  op deze manier; de instelling ervan komt eerst met
  `bvs kernel prepare --option USB_SERIAL=m --option USB_SERIAL_CH341=m`.

De modules die een build maakt, worden bewaard in `kernels/<release>/modules/`. Een module die QNAP voor deze kernel
meelevert, wordt niet bewaard: de eigen kopie van QNAP blijft degene die laadt. `bvs kernel make --build-only` bewaart
daar niets: de module blijft waar hij is gebouwd. De CI-workflows van de kernelsjablonen van Nieuw project bouwen zo.

### De controle en het laden

`bvs kernel check <module>` vergelijkt een module met de draaiende kernel: de vermagic, de grootte van `struct module`
en waar init en exit erin zitten (vergeleken met de eigen modules van QNAP), of een handtekening vereist is, de
modules waarvan hij afhangt, en elk symbool dat hij nodig heeft. Elke bevinding zegt wat niet past en wat je moet
doen. Afsluitstatus 3 betekent dat de module niet mag worden geladen.

`bvs kernel load <module>` voert de controle opnieuw uit en laadt de module, de modules waarvan hij afhangt eerst (die
van QNAP van de NAS, of die van jou). Een module met een fout in de controle wordt nooit geladen.
`bvs kernel unload <module>` verwijdert hem; een module die in gebruik is, blijft geladen. Elk laden en ontladen wordt
geschreven naar `log/kernel-modules.log`.

De kernel van QNAP toont zijn datasymbolen niet, dus de controle kan symbolen zoals `jiffies` niet opzoeken; hij noemt
ze als waarschuwingen. Als een daarvan ontbreekt, weigert de kernel de module bij het laden.

### Modules in een app

Een app kan modules van de werkruimte noemen. De app activeren laadt ze voordat de koppeling wordt gemaakt, en elke
start van het pakket laadt ze opnieuw zodra het paneel draait; de pagina Kernels toont een laadactie die nog bezig is.
Deactiveren ontlaadt ze eerst; een module die in gebruik is, houdt de app actief.

- Als de NAS herstartte terwijl modules werden geladen, laadt de volgende start er geen en toont een melding: laad ze
  met de hand of activeer de app opnieuw.
- Als de NAS stopte zonder de geordende stop van het pakket (een crash, een stroomstoring of een harde reset) tijdens
  een opstart waarin het pakket modules had geladen, laadt de volgende start die modules niet, omdat een ervan de
  crash kan hebben veroorzaakt. De melding en de pagina Kernels noemen ze. Er een met de hand laden, of de app ervan
  opnieuw activeren, laat de start hem weer laden. Een normale uitschakeling of herstart van de NAS stopt het pakket
  geordend en verandert niets.
- Een module waarvan de start nooit eindigt (een stuurprogramma dat op zijn hardware wacht), laat het paneel werken.
  Een melding noemt hem na een minuut; ander laden en ontladen wacht erop, en een herstart van de NAS maakt er een
  eind aan.

Na een QTS-update naar een andere kernel moeten de modules opnieuw worden gebouwd in de werkruimte van de nieuwe
kernel. Hetzelfde geldt na een herstel naar een andere datamap: `kernels/` hoort niet bij een back-up.

### In het paneel

De pagina **Kernels** toont het profiel van de draaiende kernel, de kernelbroncode uit de catalogus, de werkruimte en
de modules ervan. Een admin bereidt daar de werkruimte voor (een bron, de omgeving, extra instellingen, de hele kernel
of niet) en bouwt modules uit een projectmap of een map van de boom; beide draaien als taken. **Controleren**
(developer) toont elke bevinding met de oplossing; **Laden** en **Ontladen** (admin) doen wat `bvs kernel load` en
`unload` doen, en een geweigerde laadactie toont de controle die haar tegenhield. Een `.ko`-bestand ergens onder de
datamap kan via zijn pad worden gecontroleerd.

## Cron-taken en bewerkingen

De pagina Taken heeft twee lijsten: geplande taken (cron) en bewerkingen (de lange taken die bvsd uitvoert, zoals
installaties en verplaatsingen).

### Geplande taken

Een developer maakt en wijzigt zijn eigen taken; een admin wijzigt ze allemaal. Een taak heeft:

- Schema: vijf cron-velden (`0 3 * * *`), zes met seconden vooraan, of een descriptor (`@daily`, `@hourly`,
  `@every 6h`). Een voorvoegsel `CRON_TZ=<zone>` kiest een andere tijdzone; zonder draait de taak in de tijdzone van
  de NAS, die bovenaan de pagina staat.
- Opdracht: draait als root via `bash -lc` van het pakket. Meerdere regels draaien als één script, en de uitvoer ervan
  wordt het logboek van de uitvoering.
- Waar ze draait: op de NAS, of in een omgeving (met de `bash` van de omgeving, of `/bin/sh` als die er geen heeft).
- Werkmap: relatief aan `projects/` in de datamap, of een volledig pad. In een omgeving is alleen
  `projects/` zichtbaar, als `/projects`.
- Time-out: in seconden (0: geen). Een uitvoering die daarna nog loopt, wordt gestopt en als time-out gemarkeerd.
- Terwijl een uitvoering loopt: de volgende wordt overgeslagen, of ze wacht (hoogstens één wacht).
- Bij fout: een mislukte uitvoering of een uitvoering met time-out stuurt een [melding](#meldingen).

Elke taak houdt haar laatste 100 uitvoeringen met het einde van hun uitvoer (4 KB). Het volledige logboek van een
uitvoering wordt 30 dagen bewaard.

Een taak draait als root namens haar eigenaar, met de thuismap van de eigenaar als `HOME`, en draait dus alleen
zolang haar eigenaar een ingeschakelde developer of admin is. Zolang de eigenaar uitgeschakeld is of de rol member of
viewer heeft, wordt elke tijd van de taak vastgelegd als een overgeslagen uitvoering die zegt waarom, bijvoorbeeld
‘its owner ola is disabled’, en **Nu uitvoeren** antwoordt met dezelfde reden. Er wordt geen melding gestuurd. Een
uitvoering die loopt wanneer de eigenaar wordt uitgeschakeld of een rol onder developer krijgt, wordt gestopt. De
taak draait weer zodra het account weer ingeschakeld is, of weer developer. Een account verwijderen verwijdert zijn
taken. Ingebouwde taken draaien, wat er ook met een account gebeurt.

Als een opdracht eindigt, blijven processen die ze op de achtergrond startte doorlopen. Bij een time-out, het
verwijderen van de taak of een stop van het pakket krijgt de hele procesgroep van de uitvoering SIGTERM, en na 5
seconden SIGKILL.

In de nachten waarin de klok verzet wordt, volgen de taken de regel van de cron van Debian. Een taak met `*` aan het
begin van het minuut- of uurveld (ook `*/N`), `@hourly` of `@every` volgt de verstreken tijd: in de herfst draait ze
in beide doorgangen van het herhaalde uur, en in de lente komt het overgeslagen uur niet. Elke andere taak voert elk
van haar tijden één keer uit: een tijd in het herhaalde uur draait één keer, en een tijd in het overgeslagen uur
draait direct na de omschakeling.

Elk aangemeld account, ook een `viewer`, leest de opdrachten van de taken en de uitvoer van hun runs. Zet geen
wachtwoorden of tokens in een opdracht: laat hem ze lezen uit een bestand onder `<data>/etc` dat alleen root leest
(`chmod 600`).

### Ingebouwde taken

| Taak | Wanneer | Wat ze doet |
|---|---|---|
| Onderhoud | dagelijks om 03:30 | verwijdert uit `tmp/` in de datamap wat al 10 dagen niet is gewijzigd; roteert `log/audit.log` boven 10 MB |
| De prullenbak legen | dagelijks om 03:45 | verwijdert wat meer dan 7 dagen geleden naar `trash/` in de datamap is verplaatst (zoals de thuismap van een verwijderd account) |
| Vernieuwen van de toolchaincatalogus | dagelijks om 04:15 | haalt de ondertekende toolchaincatalogus op |
| Logboeken van nginx roteren | elk uur, in minuut 7 | hernoemt `nginx-access.log` of `nginx-error.log` naar `.1` zodra het boven 20 MB komt (vijf oude bestanden bewaard) en vraagt nginx zijn logboeken opnieuw te openen |

Een admin kan het schema, de time-out, het beleid en de melding van een ingebouwde taak wijzigen, en haar uitzetten.
Haar naam en opdracht blijven.

### Bewerkingen

Installaties, omgevingstaken, verplaatsingen van data en het aanzetten van een module draaien als bewerkingen. De
lijst toont de laatste 50, de nieuwste eerst, met hun voortgang en logboek. Bewerkingen en hun logboeken worden 30
dagen bewaard. Een bewerking die liep toen het pakket stopte, verschijnt als `interrupted`. Een developer annuleert
zijn eigen bewerkingen; een admin annuleert elke bewerking.

## Back-ups

De pagina **Back-ups** (admin) stelt in wat er wordt geback-upt, wanneer, waar de kopieën heen gaan en hoeveel er
worden bewaard. **Nu een back-up maken** start op elk moment een back-up. Op een nieuwe installatie staan geplande
back-ups uit; zodra **Geplande back-ups** aan staat, draait er standaard dagelijks een back-up (de ingebouwde
cron-taak `backup`). Hij bewaart de nieuwste uitvoering van elk van de laatste 7 dagen, 4 weken en 6 maanden, en
altijd de nieuwste goede uitvoering. Er draait één back-up, controle, herstel of verwijdering tegelijk.

Twee uitvoeringen op één dag tellen als één dag: als de nieuwere klaar is, wordt de eerdere uitvoering van die dag
verwijderd. Een **Nu een back-up maken** in de middag vervangt dus de geplande uitvoering van die ochtend; kopieer
eerst een uitvoering naar elders als de toestand van die ochtend ertoe doet.

### Wat een back-up bevat

Een uitvoering is een map `<data>/backups/<id>/` met een `manifest.json` die elk bestand met zijn sha256 opsomt:

| Deel | Wat |
|---|---|
| instellingen | `etc/`: instellingen, accounts, TOTP-geheimen, de TLS-sleutel, de back-upsleutels |
| Git | de dump van Forgejo: repository’s, wiki’s, LFS-objecten, bijlagen en de database |
| SVN, buckets | een kopie van elke repository, incrementeel gemaakt: een revisie die al gekopieerd is, wordt niet opnieuw gekopieerd |
| VS Code-instellingen | de VS Code-instellingen van elk account, sneltoetsen, snippets en de lijst van zijn extensies (niet de bestanden van de extensies: de weergave Extensions installeert ze opnieuw) |
| omgevingen | de omgevingen uit Toolchains (standaard uit: ze kunnen opnieuw worden gemaakt) |
| projecten | elke projectmap met zijn Git-geschiedenis en de bestanden die nog niet zijn gecommit, zonder `node_modules` |
| HOME-mappen | de HOME-map van elk account, zonder caches en de eigen installaties van de AI-tools (hun knop Installeren zet ze terug) |
| App Builder | de instellingen van elke app en de geschiedenis van zijn builds; een build maakt de bestanden van de app opnieuw |
| pakketrepository | de gepubliceerde pakketten, hun pictogrammen en de index die App Center leest |

Deze vier delen zitten in elke back-up, wat er ook op de pagina is aangevinkt. Een map die sinds de vorige back-up
niet is veranderd, wordt gekoppeld aan de kopie van die back-up en neemt geen ruimte in.

De uitvoering wordt pas zichtbaar als elk deel is geschreven en gecontroleerd; een fout of een vol volume verwijdert
de onvoltooide map en laat de eerdere uitvoeringen zoals ze waren. **Controleren** leest een uitvoering volledig
terug: de sha256 van elk archief, `svnadmin verify` van elke revisie, `git fsck` van elke repository en een
integriteitscontrole van de database van Forgejo.

### Doelen

Elke uitvoering wordt naar elk doel op de pagina gekopieerd:

- **Lokale map** op deze NAS: de uitvoering zoals ze is, met ongewijzigde bestanden gekoppeld aan de vorige kopie,
  zodat elke kopie alleen kost wat er veranderd is. Niet versleuteld. Op dezelfde schijven als de data beschermt ze
  tegen vergissingen, niet tegen een defect volume.
- **rsync** (`user@host:path`) via SSH. **Aanmelden met de back-upsleutel van het pakket**: de pagina toont de
  openbare sleutel die in `authorized_keys` van de andere machine moet.
- **rclone** (`remote:path`, een remote uit `<data>/etc/rclone.conf`): cloudopslag en alles wat rclone bereikt.

Een kopie die de NAS verlaat (rsync, rclone) moet versleuteld zijn: zet **Kopieën versleutelen die de NAS verlaten**
aan en **De sleutel maken**. De kopieën worden met age versleuteld voor de openbare helft ervan; **de geheime helft
wordt één keer getoond** en bvsd bewaart haar niet. Zonder haar kan geen enkele NAS, deze ook niet, een versleutelde
kopie herstellen: bewaar haar buiten deze NAS. Een nieuwe sleutel versleutelt de uitvoeringen vanaf dan; oudere
kopieën hebben de oude geheime sleutel nodig.

Een doel dat mislukt, wordt genoemd in het resultaat van de uitvoering; de uitvoering zelf blijft goed.

`rclone.conf` bevat de inloggegevens van de remotes. Voordat een kopie of een **Test** het gebruikt, maakt bvsd het
(en de back-upsleutel) alleen leesbaar voor root (modus 600) als dat nog niet zo is, en het logboek van de taak zegt
dat.

### Herstellen

**Herstellen** bij een uitvoering zet alles terug, één repository (een SVN-repository, een bucket of een
Git-repository) of één projectmap:

- Niets wordt vervangen voordat het is gecontroleerd: elk archief tegen zijn sha256, elke SVN-kopie met
  `svnadmin verify`. Een kopie die niet slaagt, stopt het herstel voordat er iets aan de live data verandert.
- De live data die worden vervangen, gaan 7 dagen naar `<data>/trash`, zodat ook een herstel ongedaan kan worden
  gemaakt.
- **Eén repository** of **één projectmap** wordt teruggezet terwijl de modules blijven draaien. Een per ongeluk
  verwijderde projectmap komt terug met zijn geschiedenis en de bestanden die nog niet zijn gecommit.
- **Alles** stopt de modules, wisselt de data om en herstart bvsd met de herstelde instellingen: elke sessie eindigt,
  en de accounts en wachtwoorden zijn die uit de back-up. Een project- of HOME-map die na de back-up is gemaakt,
  blijft.

## Meldingen

bvsd schrijft elke melding naar de QTS-systeemlogboeken. Het kan ze ook naar chat- en maildiensten sturen via
shoutrrr-URL’s (Telegram, Discord, Slack, e-mail, een generieke webhook en andere).

| Melding | Wanneer | Schakelaar voor de URL’s |
|---|---|---|
| mislukte taak | een cron-uitvoering van een taak met een melding bij fout is mislukt of liep in een time-out; een bewerking is mislukt | `notify.on_job_failure` |
| back-up | het resultaat van elke back-up, controle en herstel, ook de geslaagde; een geplande back-up die niet kon starten | `notify.on_backup` |
| herstartlus | een proces begon steeds opnieuw te herstarten | `notify.on_crashloop` |
| certificaat | een geüpload certificaat heeft 30 dagen of minder; nginx viel terug op het zelfondertekende certificaat; er is een nieuw zelfondertekend certificaat gemaakt | `notify.on_cert_expiry` |
| systeem | instellingen die bij de start zijn geweigerd, een onvoltooide verplaatsing van data gevonden, achtergebleven processen beëindigd, de configuratie kon niet worden geschreven | altijd verzonden |

- De schakelaars bepalen alleen wat naar de URL’s gaat. De systeemlogboeken krijgen elke melding.
- Dezelfde melding gaat hoogstens één keer per 10 minuten uit. De volgende daarna zegt hoeveel er zijn tegengehouden.
- Elke URL krijgt een eigen `shoutrrr send` met een grens van 10 seconden. Een fout wordt gelogd met alleen het schema
  van de URL, omdat de rest van een URL het token bevat.

De URL’s en de schakelaars staan onder **Meldingen** op de pagina Systeem; de schakelaars staan standaard aan. Een
admin wijzigt ze daar. Andere accounts zien de schakelaars en van elke URL alleen het schema, omdat de rest van een
URL het token bevat. De NAS-admin kan ze ook via de CLI-socket instellen:

```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
```

## Logboeken en de supportbundel

Elk logboek is `log/<name>.log` in de datamap. De logboeken van bvsd en van elk proces dat het bewaakt,
worden bij 20 MB geroteerd, en vijf oude bestanden worden bewaard. `nginx-access.log` en `nginx-error.log` hebben
dezelfde grenzen, gecontroleerd bij elke start van bvsd en elk uur door de taak ‘Logboeken van nginx roteren’, dus een
ervan kan 20 MB met hoogstens een uur verkeer overschrijden. De taak ‘Onderhoud’ roteert `audit.log` zodra het boven
10 MB komt en bewaart drie oude bestanden. De pagina Logboeken (admin) toont de laatste regels van elk logboek,
filtert ze en volgt nieuwe regels terwijl ze binnenkomen:

| Logboek | Wat het schrijft |
|---|---|
| `bvsd` | bvsd zelf: starts, stops, toegepaste instellingen, problemen |
| `nginx` | de eigen uitvoer van nginx |
| `nginx-access`, `nginx-error` | elk verzoek, en de fouten van nginx |
| `audit` | elk verzoek op de CLI-socket |
| één per moduleproces | het programma van elke module |

De pagina Systeem toont de versie, het platform, de tijdzone en elk proces dat bvsd bewaakt. ‘De supportbundel
downloaden’ (admin) maakt één archief voor een foutmelding:

- de instellingen met het SMTP-wachtwoord en elke shoutrrr-URL gemaskeerd;
- de gegenereerde `nginx.conf`, de gegevens van het certificaat zonder de sleutel, de volumes;
- de status van elke module, elk proces en elke recente bewerking;
- de laatste 2000 regels van elk logboek, met elke querystring erin vervangen door `?(query removed)`.

Het bevat nooit wachtwoorden, sessies, tokens of sleutels.

## Updates

bvsd leest `https://pub.qnaputilities.com/bucketvs/latest.json` bij de start en elke 24 uur. Als het bestand een
nieuwere versie noemt, noemt de pagina Systeem die, met de koppeling die de feed geeft. bvsd meldt de nieuwe versie
alleen: het downloadt of installeert nooit een pakket. Een mislukte controle verandert niets in het paneel.

### Een nieuwe versie installeren

Installeer het nieuwe pakket over het oude heen, vanuit App Center of met **Handmatig installeren**. De installatie
stopt bvsd, vervangt de programmabestanden en start het weer; de datamap, de instellingen, de accounts en
elke repository blijven, net als de sessies van het paneel. De instanties van de editor stoppen met bvsd, dus de open terminals van de editors
eindigen; een instantie start weer als haar account de editor opent.

**Vanaf een versie die de poorten 6050, 6051 en 6052 gebruikte.** Eerdere builds luisterden op 6050 (HTTP), 6051
(HTTPS) en 6052 (Git via SSH). De eerste start van de nieuwe versie verplaatst elk daarvan naar 9950, 9951 en 9952 en
zegt dat in een melding; een poort die je zelf hebt gekozen, blijft. Wijzig wat de oude poorten noemt: bladwijzers,
een Plesk-proxy naar 6050, en git-remotes zoals `ssh://git@<NAS>:6052/…` (`git remote set-url`).

### Verwijderen, opnieuw installeren en een beschadigd pakket

Je werk blijft als het pakket wordt verwijderd, opnieuw geïnstalleerd of beschadigd raakt. Alles wat je maakt, staat
in de datamap: accounts en instellingen, Git- en SVN-repository’s, buckets, projectmappen, HOME-mappen,
VS Code-instellingen en extensies, apps van de App Builder, omgevingen, de pakketrepository en de back-ups. De
installatiemap (`<volume>/.qpkg/bucketvs`) bevat alleen de programma’s en één bestand dat de datamap
noemt.

- **Verwijderen, dan opnieuw installeren.** Het verwijderen van het pakket wist de installatiemap en houdt de
  datamap en `/etc/config/bucketvs-data-root`, het bestand dat haar noemt. De volgende installatie
  leest dat bestand en start op dezelfde datamap: dezelfde accounts en wachtwoorden, dezelfde projecten
  en repository’s, aangemelde sessies inbegrepen.
- **Over het geïnstalleerde pakket installeren.** Dezelfde of een nieuwere versie vervangt de programmabestanden; de
  datamap wordt niet aangeraakt.
- **Een beschadigd pakket.** Als de installatiemap beschadigd is (een programma ontbreekt, de map is met de hand
  verwijderd), installeer het pakket dan opnieuw vanuit App Center of met **Handmatig installeren**. De installatie
  vindt de datamap op dezelfde manier. Een bvsd die nog vanuit verwijderde bestanden draait, wordt bij
  de start gestopt, en de geïnstalleerde start.
- **Wat de data wist.** Alleen het verwijderen van de datamap zelf. Om bewust leeg te beginnen, verwijder
  je het pakket, verwijder je de datamap en installeer je daarna: de eerste start maakt een nieuwe datamap,
  zoals [Waar de datamap begint](#waar-de-datamap-begint) beschrijft.

Een schijf die uitvalt, is iets anders: de datamap staat op de schijven van de NAS zelf. Bewaar
back-ups op een andere machine ([Back-ups](#back-ups)); elke back-up bevat je projectmappen, HOME-mappen, de
instellingen van de App Builder en de pakketrepository.

## Het commando bvs

`bvs` is de opdrachtregel van het pakket, in `/usr/bin/bucketvs/bin`. Het staat in de `PATH` van elk proces dat bvsd
start, cron-taken inbegrepen. Zolang het pakket draait, is `/usr/bin/bvs` een koppeling ernaar, zodat je het ook in een
SSH-sessie op de NAS bij naam aanroept; stopt het pakket, dan verdwijnt de koppeling.

Elk commando dat via de CLI-socket met bvsd praat, heeft root nodig, omdat de socket alleen van root is: `user`,
`jobs` en, terwijl bvsd draait, `tc install`, `tc remove` en `env create`, `clone`, `export` en `delete`. `env shell`,
`env run` en `qbuild` hebben ook root nodig: ze starten `bvs-chroot`. Als een andere gebruiker stopt een commando dat
de socket nodig heeft met ‘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>
```

- Terwijl bvsd draait, gaan installaties en omgevingstaken via de CLI-socket naar bvsd, zodat het paneel ze toont, en
  `bvs` volgt ze tot ze eindigen. Zonder bvsd draaien ze in het `bvs`-proces zelf; `--local` voor het commando doet
  dat met opzet.
- `bvs jobs` en de `user`-commando’s hebben bvsd nodig.
- `bvs guide` opent deze handleiding met de `less` van het pakket in een terminal, en toont anders, of met `--path`,
  het pad van het bestand. Zonder taal volgt het de eerste die is ingesteld van `LC_ALL`, `LC_MESSAGES` en `LANG`:
  `de_DE.UTF-8` opent de Duitse handleiding, `zh_TW` en `zh_HK` de Traditioneel-Chinese, en een taal waarin de
  handleiding niet is geschreven (`C`, `zh_CN`) de Engelse.

## Bestanden en mappen

| Pad | Wat |
|---|---|
| `/usr/bin/bucketvs` | de programma’s van het pakket (een koppeling naar de installatiemap) |
| `/usr/bin/bucketvs/etc/data-root` | het pad van de datamap |
| `/usr/bin/bucketvs/var/run/bvsd.sock` | de API-socket van het paneel (nginx gebruikt hem) |
| `/usr/bin/bucketvs/var/run/bvsd-cli.sock` | de socket van het commando `bvs`, alleen root |
| `<datamap>/etc/` | instellingen, accounts, sessies, tokens, de certificaten, de gegenereerde `nginx.conf` (geheimen met modus 600) |
| `<datamap>/log/` | de logboeken |
| `<datamap>/tmp/` | tijdelijke bestanden van elk proces |
| `<datamap>/home/<gebruiker>/` | de thuismap van elk account |
| `<datamap>/jobs/` | bewerkingen, cron-taken en hun uitvoeringen, met logboeken |
| `<datamap>/toolchains/` | de downloadcache, geïnstalleerde toolchains en runtimes, exports |
| `<datamap>/envs/` | omgevingen |
| `<datamap>/projects/` | werkruimten, zichtbaar als `/projects` in omgevingen |
| `<datamap>/trash/` | verwijderde items, 7 dagen bewaard |

De modules voegen eigen mappen toe; hun secties noemen ze.
