# Bucket & Code Server IDE: Benutzerhandbuch

Bucket & Code Server IDE (QPKG `bucketvs`) bewahrt Quellcode und Dateien auf einem QNAP-NAS auf und baut dort
Software. Das Paket bringt einen eigenen Webserver, eigene Werkzeuge und eigene Konten mit.

Dieses Handbuch beschreibt das Paket so, wie es auf dem NAS läuft.

## Inhalt

- [Was auf dem NAS läuft](#was-auf-dem-nas-läuft)
- [Das Paket installieren](#das-paket-installieren)
- [Erste Anmeldung](#erste-anmeldung)
- [Arbeiten im Panel](#arbeiten-im-panel)
- [Konten](#konten)
- [Der Datenordner](#der-datenordner)
- [Netzwerk](#netzwerk)
- [Hinter einem Reverse Proxy (Plesk)](#hinter-einem-reverse-proxy-plesk)
- [Hostnamen für die Module](#hostnamen-für-die-module)
- [Im Internet veröffentlichen (Cloudflare Tunnel)](#im-internet-veröffentlichen-cloudflare-tunnel)
- [Module](#module)
- [Git](#git)
- [CI-Runner](#ci-runner)
- [SVN und Bucket](#svn-und-bucket)
- [Editor und KI-Werkzeuge](#editor-und-ki-werkzeuge)
- [Toolchains und Umgebungen](#toolchains-und-umgebungen)
- [App Builder](#app-builder)
- [Paket-Repository](#paket-repository)
- [Kernel-Werkstatt](#kernel-werkstatt)
- [Cron-Jobs und Vorgänge](#cron-jobs-und-vorgänge)
- [Sicherungen](#sicherungen)
- [Benachrichtigungen](#benachrichtigungen)
- [Protokolle und das Support-Paket](#protokolle-und-das-support-paket)
- [Updates](#updates)
- [Der Befehl bvs](#der-befehl-bvs)
- [Dateien und Ordner](#dateien-und-ordner)

## Was auf dem NAS läuft

| Teil | Was er tut | Wo er lauscht |
|---|---|---|
| nginx | die einzige Tür im LAN: liefert das Panel aus und reicht jeden Pfad an sein Modul weiter | 9950 (HTTP), 9951 (HTTPS) |
| bvsd | startet und überwacht alle anderen Prozesse, verwaltet Konten und Einstellungen, führt Jobs und Cron-Jobs aus, beantwortet das Panel | zwei Unix-Sockets in `/usr/bin/bucketvs/var/run/` |
| Git (Forgejo) | Git-Hosting mit Weboberfläche | ein Unix-Socket in `/usr/bin/bucketvs/var/run/forgejo/` und 9952 für Git über SSH |
| SVN und Bucket (httpd) | Subversion-Repositorys und WebDAV-Ordner mit Historie | 127.0.0.1:9961, oder der nächste freie Port, wenn ein anderes Programm ihn belegt |
| Editor (code-server) | VS Code im Browser, eine Instanz pro Konto | ein Unix-Socket pro Konto in `/usr/bin/bucketvs/var/run/ide/` |
| CI | der Forgejo-Actions-Runner | kein eigener Port; nginx lauscht für ihn auf 127.0.0.1:9960, solange das Modul eingeschaltet ist, oder auf dem nächsten freien Port, wenn ein anderes Programm 9960 belegt |

Der Kern (nginx, bvsd, das Panel, Cron-Jobs, Toolchains) ist immer eingeschaltet. Git, CI, SVN und Bucket sowie der
Editor sind Module, die ein Admin ein- und ausschaltet. Auf jeder Adresse des NAS antworten die Module unter ihren
Pfaden: `/git/`, `/svn/`, `/bucket/`, `/ide/`. Das Panel liegt unter `/`, dieses Handbuch unter `/guide/`.

Solange das Paket läuft, liegen seine Programme in `/usr/bin/bucketvs/bin`. Alles, was das Paket aufbewahrt
(Konten, Einstellungen, Repositorys, Umgebungen, Protokolle), liegt in einem Ordner auf einem Datenvolume: dem
[Datenordner](#der-datenordner).

## Das Paket installieren

Das Paket braucht QTS 6.0 oder QuTS hero 6.0 oder neuer, auf einem NAS mit x86_64 oder ARM 64-Bit; für jede
Architektur gibt es ein eigenes Paket (`bucketvs_<Version>_x86_64.qpkg`, `bucketvs_<Version>_arm_64.qpkg`). Es ist
code-signiert: Installieren Sie es aus dem App Center (aus einem Repository, das es anbietet, oder mit **Manuell
installieren** und der heruntergeladenen Datei). Das App Center prüft die Signatur; Apps ohne gültige Signatur müssen
nicht erlaubt werden.

Die Installation legt den Dienstbenutzer `bucketvs` an (für Forgejo und httpd), den Link `/usr/bin/bucketvs` auf den
Installationsordner, und startet bvsd. Die Systemprotokolle nennen den Datenordner.

### Wo der Datenordner beginnt

Beim ersten Start ist der Datenordner `BucketVS` auf dem Volume, auf dem das Paket installiert ist, zum Beispiel
`/share/CACHEDEV1_DATA/BucketVS` (QTS) oder `/share/ZFS2_DATA/BucketVS` (QuTS hero). Später lässt es sich im Panel auf
ein anderes Volume verschieben ([Den Datenordner verschieben](#den-datenordner-verschieben)).

Das Paket merkt sich seinen Datenordner in `/etc/config/bucketvs-data-root`; diese Datei überdauert das Paket. Das
Entfernen des Pakets lässt den Datenordner und diese Datei stehen, sodass eine Installation nach dem Entfernen mit
denselben Daten und denselben Konten startet. Ein Start hängt nie von einem Ordner ab, den es nicht gibt. Ist der
Datenordner weg (gelöscht, auf einer ausgetauschten Festplatte oder auf einem Volume, das in diesem Moment offline
ist), legt der Start `BucketVS` auf dem eigenen Volume des Pakets an, einen neuen Ordner, außer es liegt dort schon
einer mit diesem Namen, und in einem neuen legt die [erste Anmeldung](#erste-anmeldung) das Passwort des Administrators
fest. Das gilt nach einer Neuinstallation ebenso wie für ein installiertes Paket, auch beim Hochfahren. Die
Systemprotokolle enthalten dazu eine Warnung, und `.prev`-Dateien behalten den alten Pfad:
`/etc/config/bucketvs-data-root.prev` und bei einem installierten Paket auch `etc/data-root.prev` im
Installationsordner. Lag der alte Ordner auf einem Volume, das in diesem Moment nur offline war, bringen Sie das Volume
online, stoppen das Paket, kopieren diese Datei über `etc/data-root` im Installationsordner (zum Beispiel
`/share/CACHEDEV1_DATA/.qpkg/bucketvs/etc/data-root`) und starten das Paket: Es läuft dann wieder mit den alten Daten.

### Das Panel aus dem App Center öffnen

**Öffnen** beim Paket im App Center öffnet das Panel auf Port 9950 (HTTP) oder 9951 (HTTPS). Werden die Ports im Panel
geändert ([Ports](#ports)), teilt das Paket dem App Center die neuen beim nächsten Start mit.

### Autostart aus einer SSH-Sitzung

Solange der Schalter des Pakets im App Center eingeschaltet ist, startet das NAS das Paket beim Hochfahren. In einer
SSH-Sitzung auf dem NAS setzen Sie als Admin denselben Schalter mit dem Dienstskript:

```sh
/etc/init.d/bucketvs.sh disable   # der nächste Start wird abgelehnt, auch beim Hochfahren
/etc/init.d/bucketvs.sh enable    # das Paket startet wieder beim Hochfahren
```

Jeder der beiden Befehle schreibt nur den Schalter (`Enable` in `/etc/config/qpkg.conf`), gibt aus, was er gesetzt
hat, und schreibt dieselbe Zeile in die Systemprotokolle. Ein laufendes Paket läuft nach `disable` weiter, und
`enable` startet nichts: das tun `stop` und `start`, und `start` lehnt ab, solange das Paket ausgeschaltet ist.
`status` sagt, ob bvsd läuft.

## Erste Anmeldung

Öffnen Sie das Panel unter `https://<NAS-Adresse>:9951/` oder `http://<NAS-Adresse>:9950/`. Die HTTPS-Adresse
verwendet anfangs ein selbstsigniertes Zertifikat, daher warnt der Browser, bis das Gerät ihm vertraut; der Abschnitt
[Das Zertifikat](#das-zertifikat) zeigt, wie Sie seinen Fingerabdruck vergleichen.

Eine neue Installation hat kein Konto. Solange der erste Administrator kein Passwort hat, öffnet das Panel einen
einzigen Bildschirm, „Administratorpasswort setzen“, für das Konto `administrator`. Geben Sie ein Passwort zweimal
ein: mindestens 10 Zeichen und höchstens 72 Byte. Das Speichern legt das Konto an und meldet Sie an.

```mermaid
flowchart TD
    start["Das Paket startet<br/>ohne Konto"] --> screen["Administratorpasswort setzen<br/>60 Minuten, nur lokales Netz"]
    screen -->|"gespeichert"| panel["administrator angemeldet"]
    screen -->|"60 Minuten vorbei"| restart["Paket im App Center neu starten:<br/>ein neues 60-Minuten-Fenster"]
    restart --> screen
```

- Der Bildschirm nimmt ein Passwort 60 Minuten lang nach dem Start des Pakets an. Danach meldet er, dass das
  Einrichtungsfenster geschlossen ist; ein Neustart von Bucket & Code Server IDE im App Center öffnet ein neues
  60-Minuten-Fenster.
- Er nimmt ein Passwort nur aus dem lokalen Netz an: vom NAS selbst oder von einer Adresse in einem privaten Bereich
  (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `169.254.0.0/16`, `fc00::/7`, `fe80::/10`).
- Solange der Bildschirm offen ist, zeigen die QTS-Systemprotokolle für jede LAN-Adresse des NAS einen
  Informationseintrag wie:

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

- Er lehnt eine Anfrage ab, die über einen Proxy kam (eine mit `X-Forwarded-For`, `X-Forwarded-Host`,
  `X-Forwarded-Proto`, `X-Real-IP` oder `Forwarded`), auch von einem Proxy im LAN: Öffnen Sie das Panel direkt von
  einem Computer im lokalen Netz, um das Passwort zu setzen.

Aus einer SSH-Sitzung auf dem NAS legt `/usr/bin/bucketvs/bin/bvs user password administrator` das Konto an, wenn es
noch keines gibt, und gibt ein zufälliges Passwort aus; das Panel verlangt dann bei der ersten Anmeldung ein eigenes
Passwort ([Wenn ein Admin ausgesperrt ist](#wenn-ein-admin-ausgesperrt-ist)).

Nach der ersten Anmeldung sagt die Übersicht, dass nur der Kern läuft. Die Kachel jedes Moduls sagt, was das Modul
bietet, und hat eine Schaltfläche, die es einschaltet, etwa **Git einschalten**: Schalten Sie die Module ein, die Sie
brauchen ([Module](#module)).

## Arbeiten im Panel

Das Panel funktioniert wie ein Code-Editor: Jede Seite öffnet sich in einem Tab, und eine Befehlspalette erreicht die
Seiten und Aktionen über ihren Namen.

- **Aktivitätsleiste** (die Symbole links). Jedes Symbol öffnet seine Seite und zeigt seinen Baum in der Seitenleiste:
  die Module, Repositorys und Buckets, Apps, Pakete, Umgebungen, Jobs und Sicherungen. Ein Punkt auf einem Symbol
  markiert ein Modul mit Problemen, einen fehlgeschlagenen Build oder eine fehlgeschlagene Sicherung. Ein Klick auf
  das Symbol der vorderen Seite blendet die Seitenleiste aus; Strg+B tut dasselbe.
- **Tabs.** Eine mit einem einfachen Klick geöffnete Seite ist eine Vorschau, in Kursivschrift: Die nächste Seite
  öffnet sich an ihrer Stelle. Bearbeiten Sie die Seite oder doppelklicken Sie auf ihren Tab, um sie zu behalten. Die
  Tabs kommen nach einem Neuladen zurück, für jedes Konto in jedem Browser. In einem Fenster, das mindestens 1280
  Pixel breit ist, zeigt **Tab auf die andere Seite verschieben** (`Ctrl+\`) zwei Seiten nebeneinander.
- **Objekte und ihre Menüs.** Ein Klick auf eine Zeile der Seitenleiste, eine Tabellenzeile oder eine Karte öffnet
  dieses Objekt: Seine Seite kommt nach vorn, das Objekt mit einem grünen Rahmen oder mit geöffneten Details. Die
  Adresse nennt das Objekt, sodass Zurück, Vorwärts und ein kopierter Link wieder dorthin führen. Ein Doppelklick
  behält den Tab. Ein Rechtsklick, Umschalt+F10 oder langes Drücken auf einem Touchscreen öffnet das Menü des Objekts:
  die Arten, es zu öffnen (in diesem Tab, in einem behaltenen Tab, auf der anderen Seite, in einem neuen Browser-Tab),
  was seine Seite damit tun kann, und Kopien seines Namens und seiner Adresse. Das Menü eines Tabs schließt ihn, die
  anderen Tabs, die Tabs rechts davon oder alle. Nur ein mit ↗ markiertes Element verlässt das Panel. Das Symbol
  **Verwaltung** öffnet die Seite System.
- **Befehlspalette.** Strg+Umschalt+P (oder Strg+K, oder F1) listet die Befehle, die Ihre Rolle ausführen darf: eine
  Seite öffnen, eine App bauen oder veröffentlichen, eine Sicherung starten, ein Modul neu starten, eine Shell in
  einer Umgebung öffnen. Strg+P springt über ein paar Buchstaben des Namens zu einer Seite, einer App, einem
  Repository, einer Umgebung oder einem Paket.
- **Unteres Panel** (Strg+J). **Jobs** zeigt die laufenden und letzten Jobs mit Fortschritt und Protokoll.
  **Probleme** listet, was die Module melden: fehlgeschlagene Builds, abgeschaltete Sicherungsziele, Module mit
  Problemen, ein bald ablaufendes Zertifikat und die Befunde des ELF-Analysators für die vordere App. **Ausgabe**
  folgt dem Job, den Sie zuletzt gestartet haben.
- **Verschieben und Größe ändern.** Ziehen Sie eine Kante, um die Größe dessen zu ändern, woran sie grenzt: die
  Seitenleiste, das untere Panel, zwei Seiten nebeneinander oder zwei Teile einer Seite nebeneinander, etwa ein
  Assistent und seine Zusammenfassung oder die Dateiliste eines Projekts und die Datei. Ein Doppelklick auf eine Kante
  setzt sie zurück. Einen Dialog verschieben Sie an seinem Titel und ändern seine Größe an einer Kante oder an seiner
  rechten unteren Ecke; ein Doppelklick auf den Titel setzt ihn in die Mitte zurück. Um das untere Panel neben die
  Seiten zu stellen, ziehen Sie seine Kopfzeile an den rechten Rand des Editors oder klicken auf die Schaltfläche
  neben seinem ×; an den unteren Rand gezogen oder mit einem zweiten Klick auf die Schaltfläche kommt es zurück. Ein
  Tab lässt sich an eine andere Stelle der Tableiste oder auf die andere Seite ziehen. Das Panel behält jede Größe und
  Position für Ihr Konto in diesem Browser, und **Layout zurücksetzen** (in der Befehlspalette oder unter der
  Schaltfläche Ihres Kontos unten in der Aktivitätsleiste) setzt alles zurück. Mit der Tastatur verschieben die
  Pfeiltasten eine fokussierte Kante (mit Umschalt in größeren Schritten, Pos1 setzt sie zurück), Strg+Umschalt+Links
  und Rechts verschieben einen fokussierten Tab, und ein Dialog hat oben eine Leiste und rechts unten einen Griff, mit
  denen die Pfeiltasten ihn verschieben und in der Größe ändern. Auf einem Telefon bleiben Dialoge und das untere
  Panel, wo sie sind.
- **Statusleiste.** Modulzustände, laufende Jobs, Probleme, freier Platz auf dem Datenvolume, das Zertifikat, das
  angemeldete Konto, die Sprache (der Editor übernimmt sie beim nächsten Start, bei Schwedisch und Niederländisch bleibt er Englisch; Git folgt ihr, außer Sie wählen dort eine andere), das Design (dunkel, hell, wie das System, Tiefblau, Graphit oder Papier: es gehört zu Ihrem Konto, und Editor und Git übernehmen es, außer Sie wählen dort ein anderes) und die Version. Jedes Element öffnet
  die Stelle, an der man genauer hinsieht.
- **Tastatur.** Strg+/ oder ? listet die Tastenkürzel. Ein Browser-Tab behält Strg+W und Strg+Tab für sich, daher
  schließt das Panel einen Tab mit Alt+W und wechselt Tabs mit Alt+Bild↓ und Alt+Bild↑.
- **Auf einem Telefon** liegen die ersten Aktivitäten unten, **Mehr** öffnet die Seitenleiste mit dem Rest, und die
  offenen Seiten sind eine Liste oben.

## Konten

Das Panel hat eigene Konten. QTS-Konten melden sich dort nicht an.

### Rollen

| Rolle | Was sie darf |
|---|---|
| viewer | liest das Panel; die Seiten Sicherungen, Speicher, Benutzer und Protokolle sind Admins vorbehalten. In Forgejo legt sie kein Repository an, forkt und importiert keines, legt keine Organisation an und pusht nie; sie liest die SVN-Repositorys und Buckets, für die sie Rechte hat, und schreibt in keines |
| member | liest, was ein viewer liest, und schreibt: pusht in die Git-Repositorys, für die sie Schreibzugriff erhält, schreibt in die SVN-Repositorys und Buckets, für die sie Lesen und Schreiben hat, und stellt dort ältere Versionen von Dateien wieder her. In Forgejo legt sie kein Repository an, forkt und importiert keines und legt keine Organisation an, und sie kann nicht in ein Repository pushen, das einem Konto unter developer gehört oder von einem solchen verwaltet wird, und, solange das CI-Modul eingeschaltet ist, auch nicht in eines, dessen Actions eingeschaltet sind. Sie bekommt keinen Editor, keine Cron-Jobs und keine Builds |
| developer | öffnet zusätzlich den Editor, liest die `bvs use`-Zeilen installierter Toolchains, legt eigene Cron-Jobs an, ändert und startet sie, legt Apps an und baut sie, veröffentlicht Pakete im Repository (ein Admin listet sie), prüft Kernel-Module und legt Git-Repositorys an, deren Workflows auf dem NAS laufen, solange das CI-Modul eingeschaltet ist |
| admin | alles: Konten, Einstellungen, Speicher, Netzwerk, Module, Toolchains, Umgebungen, jeder Cron-Job, Apps aktivieren, Kernel-Module laden, Pakete im Repository listen |

Die Rolle developer ist root auf dem NAS: Ihre Cron-Befehle laufen als root, ebenso das Terminal ihres Editors und,
solange das CI-Modul eingeschaltet ist, die Workflows der Repositorys, in die sie pushen darf. Geben Sie sie nur
Personen, denen Sie auch das Admin-Passwort des NAS geben würden. Wer nur in Repositorys schreibt, braucht nur die
Rolle member.

Unter **Ihr Konto** sieht jede Person ihre eigene Rolle und was sie erlaubt, mit einem Link zu diesem Abschnitt. Eine
Seite über der Rolle einer Person sagt, welche Rolle sie erfordert, und was nur ein Admin ändert, ist dort, wo es steht,
so gekennzeichnet. Netzwerk, Git und CI zeigen allen anderen ihre Einstellungen als Werte, mit „Nicht gesetzt“, wo einer
leer ist.

Workflows laufen auf dem NAS als root, deshalb hält bvsd die Actions in jedem Git-Repository ausgeschaltet, das einem
member oder viewer gehört oder in das ein solches Konto schreiben kann: Gibt man einem solchen Konto Schreibzugriff
auf ein Repository, gehen dessen Actions innerhalb einer Minute aus, und die Git-Seite listet diese Repositorys für
Admins. Um dort Actions zu nutzen, entziehen Sie zuerst diesen Schreibzugriff in Forgejo oder übertragen ein
Repository, das einem solchen Konto gehört, auf das Konto eines developers.

### Konten anlegen und ändern

Ein Admin verwaltet die Konten auf der Seite Benutzer.

- Ein neues Konto beginnt mit der Rolle viewer. Developer und admin werden bewusst gewählt: Die Rolle developer ist
  root auf dem NAS (oben).
- Ein neues Konto beginnt ohne Cron-Jobs, auch wenn ein Konto mit demselben Namen welche hatte.
- Ein Name hat 2 bis 32 Zeichen: zuerst ein Kleinbuchstabe, dann Kleinbuchstaben, Ziffern, `.`, `_` oder `-`. Zwei von
  `.`, `_`, `-` hintereinander oder eines am Ende werden abgelehnt. Ebenso die Namen, die Forgejo reserviert (`admin`,
  `api`, `user` und andere), `bucketvs` (der Systembenutzer des Pakets), `cli` und `system` (beide verwendet bvsd
  selbst).
- „Muss bei der ersten Anmeldung ein eigenes Passwort wählen“ lässt das Konto sein eigenes Passwort setzen, bevor es
  irgendetwas anderes tun kann.
- Das Deaktivieren eines Kontos beendet sofort seine Sitzungen und stoppt seine API-Token. Seine Daten bleiben. Seine
  [Cron-Jobs](#geplante-jobs) bleiben ebenfalls, laufen aber nicht, solange es deaktiviert ist, und ein Lauf, der
  gerade andauert, wird gestoppt.
- Bekommt ein Konto die Rolle member oder viewer, geschieht mit seinen Cron-Jobs dasselbe: Sie bleiben, laufen nicht,
  solange seine Rolle unter developer liegt, und ein Lauf, der gerade andauert, wird gestoppt.
- Ein viewer pusht nie nach Git, auch dort nicht, wo er in Forgejo Schreibzugriff bekommen hat. Ein Konto, das pushen
  soll, bekommt die Rolle member.
- Das Löschen eines Kontos entfernt es mit seinen Sitzungen und API-Token. Sein Home-Ordner wandert nach
  `trash/home/<Name>-<Zeit>` im Datenordner, wo der Job „Papierkorb leeren“ ihn nach 7 Tagen entfernt, und sein
  Ordner `tmp/tmux-<Name>` wird entfernt. Seine Cron-Jobs werden mit ihren Läufen und Protokollen gelöscht.
- Der letzte aktive Admin kann nicht herabgestuft, deaktiviert oder gelöscht werden.
- „Neues Passwort setzen“ beendet die Sitzungen des Kontos und widerruft seine API-Token.

### Passwörter und Sitzungen

- Ein Passwort hat mindestens 10 Zeichen und höchstens 72 Byte.
- Das Ändern des eigenen Passworts (unter Ihr Konto) beendet Ihre anderen Sitzungen und widerruft Ihre API-Token. Die
  Sitzung, in der Sie es geändert haben, bleibt angemeldet.
- Eine Sitzung endet 12 Stunden nach ihrer letzten Anfrage und in jedem Fall 7 Tage nach der Anmeldung. Ein Konto hat
  höchstens 32 Sitzungen; eine weitere Anmeldung beendet die am längsten nicht benutzte.
- Das Sitzungs-Cookie ist `HttpOnly` und `SameSite=Lax`. Es wird als `Secure` markiert, wenn der Browser das NAS über
  HTTPS erreicht hat, direkt oder über einen [vertrauenswürdigen Proxy](#vertrauenswürdige-proxys).

### Falsche Passwörter

- Fünf fehlgeschlagene Anmeldungen innerhalb von 15 Minuten von einer Adresse sperren diese Adresse für 15 Minuten,
  auch mit dem richtigen Passwort. Ein IPv6-Client zählt als sein /64-Netz.
- Fünf Fehlversuche für ein Konto von einer Adresse sperren dieses Konto für diese Adresse für 15 Minuten.
- Fehlversuche für ein Konto von vielen Adressen verlangsamen seine nächsten Anmeldungen: 125 ms nach einem
  Fehlversuch, verdoppelt bis zu 2 Sekunden. Aus dem lokalen Netz sperren sie es nie.
- Aus dem Internet (von jeder Adresse außerhalb des lokalen Netzes) sperren 20 fehlgeschlagene Anmeldungen für ein
  Konto seit seiner letzten erfolgreichen es dort, gleich von welchen Adressen sie kamen; dann wird dort auch das
  richtige Passwort abgewiesen. Aus dem lokalen Netz meldet sich das Konto weiter an. Ein Admin entsperrt es auf der
  Seite Benutzer (**Entsperren**) oder mit `bvs user unlock <Name>`, oder gibt ihm ein neues Passwort. Die Zahl wird
  auf der Seite Benutzer unter **Anmeldungen aus dem Internet** eingestellt; 0 schaltet diese Sperre aus.
- Die Zähler liegen im Speicher, ein Neustart von bvsd setzt sie also zurück; eine Sperre bleibt, bis sie beendet
  wird.

Hinter einem Proxy zählt die Adresse, die der Proxy meldet, aber nur, wenn der Proxy ein [vertrauenswürdiger
Proxy](#vertrauenswürdige-proxys) ist. Sonst zählt jeder Client als die eigene Adresse des Proxys, und fünf falsche
Passwörter von irgendwem sperren alle für 15 Minuten aus.

### Zwei-Faktor-Authentifizierung

Jedes Konto schaltet sie für sich selbst unter Ihr Konto ein: Scannen Sie den QR-Code mit einer Authenticator-App
(oder tippen Sie den Schlüssel ein) und geben Sie dann den 6-stelligen Code ein, den die App zeigt. Danach fragt die
Anmeldung nach dem Passwort nach einem Code.

- Die Codes folgen RFC 6238: 6 Ziffern, alle 30 Sekunden ein neuer Code. Ein Code, der 30 Sekunden zu früh oder zu
  spät kommt, wird angenommen. Derselbe Code wird nie zweimal angenommen.
- Schlägt ein frischer Code fehl, geht die Uhr des Telefons wahrscheinlich falsch; stellen Sie sie auf automatische
  Zeit.
- Das Ausschalten verlangt das Passwort des Kontos.
- Bei einem verlorenen Telefon schaltet ein Admin sie auf der Seite Benutzer aus. Dann meldet das Passwort allein an.

### API-Token

Skripte und CI verwenden API-Token statt eines Passworts. Unter Ihr Konto erzeugt „Neues Token“ eines:

- Es sieht aus wie `bvs_` gefolgt von 40 Hexadezimalziffern und wird einmal angezeigt. bvsd speichert nur seinen Hash.
- Ein Skript sendet es als `Authorization: Bearer bvs_...`.
- Seine Bereiche sind `read`, `write` und `admin`, die für die Rollen viewer, developer und admin stehen. Ein Token
  handelt mit seinem höchsten Bereich, nie über der aktuellen Rolle seines Besitzers; das Herabstufen eines Kontos
  stuft also auch seine Token herab. Kein Bereich steht für member: Ein member erstellt `read`-Token.
- Ein neues Passwort widerruft alle Token des Besitzers. Die Token eines deaktivierten Besitzers funktionieren nicht
  mehr.
- Ein Token kann weder Passwörter noch die Zwei-Faktor-Authentifizierung ändern und keine Token erzeugen oder löschen: Dafür
  ist eine angemeldete Sitzung nötig. Ein geleaktes Token kann seinen Besitzer daher nicht aussperren.

### Wenn ein Admin ausgesperrt ist

Der NAS-Admin kann jedes Konto aus einer SSH-Sitzung auf dem NAS zurücksetzen. Der Befehl `bvs` spricht mit bvsd über
einen Socket, den nur root öffnen kann (`/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 <Name>` setzt ein neues zufälliges Passwort mit 20 Zeichen und gibt es aus. Das Konto muss bei
  der nächsten Anmeldung ein eigenes wählen. Seine Sitzungen enden und seine API-Token werden widerrufen. Auf einer
  neuen Installation ohne Konto legt `bvs user password administrator` das Konto an.
- `bvs user totp-reset <Name>` schaltet die Zwei-Faktor-Authentifizierung für das Konto aus.
- `bvs user unlock <Name>` beendet die Sperre des Kontos nach zu vielen falschen Passwörtern aus dem Internet.

Jede Anfrage auf diesem Socket handelt als der eingebaute Admin `cli` und wird in `log/audit.log` im Datenordner
geschrieben. Für diese Befehle muss bvsd laufen.

## Der Datenordner

Der Datenordner ist der Ordner, der alles enthält, was das Paket aufbewahrt. Sein Pfad ist die eine Zeile in
`/usr/bin/bucketvs/etc/data-root`. Die Seite Speicher zeigt ihn, mit dem Volume, auf dem er liegt, und dem freien
Platz dort.

bvsd startet nur mit einem Datenordner, der ein Ordner auf einem echten Volume ist. Der Start des Pakets prüft das
vorher: Ein fehlender Ordner oder einer auf tmpfs oder ramfs ergibt einen neuen Datenordner auf dem
Installationsvolume, wie unter [Wo der Datenordner beginnt](#wo-der-datenordner-beginnt) beschrieben. Ein
Volume, das offline ist, hinterlässt seinen Einhängepunkt als leeren Ordner im RAM des NAS, und dort geschriebene
Einstellungen wären nach dem nächsten Neustart weg. bvsd selbst lehnt einen solchen Ordner nur ab, wenn es von Hand
gestartet wird, und der Grund geht dann in die QTS-Systemprotokolle.

### Was bvsd jedem Prozess mitgibt

QTS hält `/tmp` in einer kleinen RAM-Disk und `/root` auf einer RAM-Disk, die ein Neustart leert. Deshalb bekommt
jeder Prozess, den bvsd startet (Modulprogramme, Cron-Jobs, Installationen), seine temporären Dateien und seinen
Home-Ordner im Datenordner:

| Variable | Wert |
|---|---|
| `HOME` | `<Datenordner>/home/<Benutzer>` |
| `TMPDIR` | `<Datenordner>/tmp` |
| `XDG_CACHE_HOME`, `XDG_CONFIG_HOME` | `.cache` und `.config` in diesem Home-Ordner |
| `npm_config_cache` | `.npm` in diesem Home-Ordner |
| `PATH` | `~/.local/bin`, `~/.npm-global/bin`, dann `bin`, `sbin` und `node/bin` des Pakets, dann die Ordner des Systems |
| `SSL_CERT_FILE`, `GIT_SSL_CAINFO`, `CURL_CA_BUNDLE` | das CA-Bündel des Pakets, `/usr/bin/bucketvs/etc/ssl/cacert.pem` |
| `TZ` | die Zeitzone des NAS, wie in QTS eingestellt |
| `SHELL`, `EDITOR` | `bash` und `nano` des Pakets |
| `LANG`, `LESSCHARSET` | `C.UTF-8` und `utf-8`, damit Dateinamen mit Umlauten und anderen Zeichen so erscheinen, wie sie sind |

### Den Datenordner verschieben

Die Seite Speicher verschiebt den ganzen Datenordner in einen anderen Ordner unter `/share`. Wählen Sie einen
freigegebenen Ordner oder ein Volume; die Daten kommen in einen neuen Ordner namens `BucketVS` darin. Um einen Ordner
selbst zu verwenden, öffnen Sie einen leeren Ordner namens `BucketVS`. Freigegebene Ordner werden in QTS angelegt
(Systemsteuerung, Freigabeordner); innerhalb eines freigegebenen Ordners oder eines Volumes kann die Seite einen neuen
Ordner anlegen. Die Auswahl öffnet sich dort, wo die Daten jetzt liegen, und sagt das; öffnen Sie von dort den
freigegebenen Ordner oder das Volume, in das sie umziehen sollen.

Die Seite prüft das Ziel, bevor der Umzug beginnt, und listet jeden Grund, aus dem sie eines ablehnt:

- der Pfad muss unter `/share` liegen und nicht `/share` selbst sein;
- kein `:`, `&`, `|` oder Steuerzeichen in einem Teil des Pfads, auch nicht im Pfad, auf den ein Link zeigt;
- nicht der aktuelle Datenordner, kein Ordner darin, kein Ordner, der ihn enthält;
- ein neuer Ordner oder ein leerer;
- ein Dateisystem, das Unix-Besitzer und -Rechte kennt (FAT, exFAT und NTFS werden abgelehnt: Forgejo und SVN brauchen
  Besitzer und Modi);
- ein Volume, das mit `exec`, `dev` und `suid` eingehängt ist, was Build-Umgebungen brauchen;
- beschreibbar, mit freiem Platz für die Daten plus 10 %.

```mermaid
flowchart TD
    check["Ziel erneut prüfen<br/>freier Platz: die Daten + 10 %"] --> stop["Module stoppen<br/>das Panel bleibt an"]
    stop --> copy["Kopieren mit<br/>rsync -aHAX --numeric-ids"]
    copy --> verify["Kopie prüfen<br/>Dateien, Bytes, Besitzer, Modi, Zeiten,<br/>sha256 von bis zu 1000 Dateien"]
    verify --> switch["Datenordner umschalten<br/>in /usr/bin/bucketvs/etc/data-root"]
    switch --> start["bvsd startet neu im neuen Verzeichnis<br/>und startet die Module"]
    start --> old["Die alte Kopie bleibt, markiert mit MOVED-TO,<br/>bis Sie sie entfernen"]
    copy -->|Fehler| failed["Das alte Verzeichnis bleibt in Gebrauch,<br/>die Module starten wieder"]
    verify -->|ein Unterschied| failed
```

Während der Umzug läuft:

- Git, CI, SVN und Bucket sowie der Editor antworten nicht. Das Panel bleibt an und zeigt den Fortschritt.
- Kein neuer Cron-Lauf startet. Ein Lauf, der schon lief, läuft weiter.
- Ein Modul ein- oder auszuschalten wird abgelehnt, bis der Umzug endet.

Nach dem Umschalten stoppt bvsd und startet sich im neuen Verzeichnis neu, daher ist das Panel für ein paar Sekunden
weg.

Schlägt der Umzug fehl, bleibt der alte Datenordner in Gebrauch und die Module starten wieder. Der Fehler des
Jobs sagt, was schiefging und was aus der Kopie wurde:

- Ein volles Volume oder ein Abbruch entfernt, was der Umzug angelegt hat.
- Jeder andere Fehler (das Kopieren, die Prüfung, das Schreiben auf die Platte) behält die Kopie, markiert mit einer
  Datei namens `INCOMPLETE`, und listet sie auf der Seite Speicher unter „Übrig gebliebene Kopien fehlgeschlagener
  Umzüge“.
- Ein Stopp des Pakets während des Umzugs behält den alten Ordner als Datenordner. Beim nächsten Start meldet
  bvsd den unvollendeten Umzug in den Systemprotokollen und listet die Kopie.

### Die alte Kopie und übrig gebliebene Kopien

Nach einem erfolgreichen Umzug bleibt der alte Datenordner, wo er war, mit einer Datei namens `MOVED-TO` darin.
Nichts verwendet es. „Alte Kopie entfernen“ auf der Seite Speicher löscht es, und „Diese Kopie entfernen“ löscht eine
übrig gebliebene Kopie eines fehlgeschlagenen Umzugs. Beide Löschungen bleiben auf einem Dateisystem und folgen nie
einem Link. Ein Ordner, der ein Einhängepunkt oder der Ordner eines freigegebenen Ordners selbst ist, wird geleert und
behalten.

## Netzwerk

Die Seite Netzwerk enthält die Ports, den Modus für reines HTTP, die Hostnamen der Module, die vertrauenswürdigen
Proxys, die öffentliche URL und das Zertifikat. Das Speichern startet neu, was die Änderung braucht, und listet es.

### Ports

| Einstellung | Standard | Was sie ist |
|---|---|---|
| HTTPS | 9951 | das Panel und die Module über TLS |
| HTTP | 9950 | das Panel und die Module über reines HTTP |
| Git über SSH | 9952 | Git über SSH |
| SSH-Port in Klon-URLs | 0 | der Port, den SSH-Klonadressen zeigen; 0 bedeutet den SSH-Port oben. Setzen Sie ihn, wenn ein Router einen anderen Port an das NAS weiterleitet. |
| SSH-Host in Klon-URLs | leer | der Name oder die Adresse, die SSH-Klonadressen zeigen. Leer bedeutet den Git-Hostnamen, sonst den Host der öffentlichen URL, sonst die LAN-Adresse des NAS; für einen über den Cloudflare Tunnel veröffentlichten Namen die LAN-Adresse und den Port für Git über SSH. Setzen Sie ihn auf einen Namen, der nur im DNS steht, oder auf die Adresse des Routers, wenn der Router den SSH-Port weiterleitet. |

Ports liegen zwischen 1024 und 65535 und unterscheiden sich voneinander. Die Modulports auf 127.0.0.1 (9960 und 9961)
werden abgelehnt, ebenso der Port, den httpd oder der Server des CI-Runners verwendet, wenn ein anderes Programm 9960
oder 9961 belegt hat (die Fehlermeldung nennt ihn). Bleibt nginx beim Speichern auf seinen alten Ports, weil es einen
neuen nicht verwenden konnte, sagt eine Benachrichtigung warum. nginx lauscht auch auf IPv6, wenn das NAS es hat.

### Reines HTTP

- „Ausliefern“ (der Standard): Das Panel und die Module antworten auch auf reinem HTTP. Behalten Sie das, wenn ein
  Proxy im LAN reines HTTP mit Port 9950 spricht.
- „Auf HTTPS umleiten“: Eine Anfrage auf reinem HTTP bekommt eine Umleitung (307) auf den HTTPS-Port. Eine Anfrage von
  einem vertrauenswürdigen Proxy, der meldet, dass sein Client HTTPS verwendet hat, wird auf reinem HTTP beantwortet,
  damit ein solcher Proxy nicht in einer Schleife landet.

Die Anmeldeseite warnt, wenn sie über reines HTTP kam: Das Passwort geht dann lesbar durch das Netz.

### Das Zertifikat

Beim ersten Start erzeugt bvsd ein selbstsigniertes Zertifikat (ECDSA P-256) für:

- den Hostnamen des NAS, die Hostnamen der Module und `localhost`;
- die LAN-Adressen des NAS und `127.0.0.1`.

Es ist 825 Tage gültig, so lange, wie Apple-Geräte es höchstens annehmen, auch bei einem von Hand als vertrauenswürdig
eingestuften Zertifikat. bvsd prüft es beim Start und einmal am Tag und erzeugt ein neues, wenn noch 30 Tage oder
weniger übrig sind, wenn sich die Menge der Namen ändert oder wenn sein Schlüssel nicht passt. Ein neues sendet eine
[Benachrichtigung](#benachrichtigungen). Die Seite Netzwerk zeigt seine Namen, seine Gültigkeit und seinen
SHA-256-Fingerabdruck: Vergleichen Sie den Fingerabdruck, bevor ein Gerät ihm vertraut.

„Eigenes Zertifikat hinzufügen“ nimmt eine Zertifikatskette und ihren privaten Schlüssel im PEM-Format:

- zuerst das Serverzertifikat, dann die Zwischenzertifikate;
- ein Schlüssel ohne Passphrase (nginx startet unbeaufsichtigt);
- der Schlüssel muss zum ersten Zertifikat gehören, jedes Zertifikat muss vom nächsten signiert sein, keines darf
  abgelaufen sein, und das erste muss seine Hosts als Subject Alternative Names nennen.

Das Paar wird in `etc/tls/` im Datenordner mit Modus 600 gespeichert. Läuft ein hochgeladenes Zertifikat ab oder
besteht es diese Prüfungen nicht mehr, liefert nginx stattdessen das selbstsignierte aus, und eine Benachrichtigung
sagt warum. Ein hochgeladenes Zertifikat mit 30 Tagen oder weniger sendet ebenfalls eine Benachrichtigung. „Neues
selbstsigniertes Zertifikat“ erzeugt ein neues selbstsigniertes Paar und schaltet auf dieses zurück.

Clients, die über einen Proxy kommen, sehen das Zertifikat des Proxys, nicht dieses.

**Zertifikat herunterladen** auf der Seite Netzwerk speichert das Zertifikat, das das Panel ausliefert, ohne seinen
Schlüssel als `<name>.crt`. Fügen Sie es den vertrauenswürdigen Zertifikaten eines Geräts hinzu, dann vertrauen die
Browser dieses Geräts dem Panel; die Erweiterungsansichten des Editors brauchen das ([Erweiterungen](#erweiterungen)).
Einem neuen selbstsignierten Zertifikat muss man erneut vertrauen.

### Vertrauenswürdige Proxys

Eine Liste von IP-Adressen oder CIDR-Bereichen, etwa `192.168.1.5`, die Adresse des Proxys vor dem NAS. Nur bei einer
Anfrage, deren TCP-Verbindung von einer davon kommt, übernimmt nginx:

- die Adresse des Clients aus `X-Forwarded-For`;
- das Schema, das der Client verwendet hat, aus `X-Forwarded-Proto` (nur `http` oder `https` zählen).

Eine Anfrage von jeder anderen Adresse gilt als direkt von ihrem Client kommend, und nginx ersetzt ihre
`X-Forwarded-*`-Header durch eigene Werte. Ein Bereich macht jeden Host darin vertrauenswürdig: Jeder von ihnen kann
dann Clientadresse und Schema setzen, tragen Sie also die eigene Adresse des Proxys ein. Ein Bereich aller Adressen
(`0.0.0.0/0`, `::/0`) wird abgelehnt.

### Öffentliche URL

Nur die Adresse, etwa `https://git.example.com`, ohne Pfad. Die Abschnitte der Module sagen, welche Links mit ihr
beginnen.

## Hinter einem Reverse Proxy (Plesk)

Die übliche Einrichtung legt TLS auf einen Proxy mit einem öffentlichen Zertifikat und lässt ihn im LAN reines HTTP
mit Port 9950 sprechen. Die Beispiele unten verwenden `bvs.example.com` als öffentlichen Namen, `192.168.1.20` für das
NAS und `192.168.1.5` für den Plesk-Server; setzen Sie Ihre eigenen Werte ein.

```mermaid
flowchart LR
    client["Browser, git, WebDAV-Client"] -->|"HTTPS 443"| plesk["Plesk-nginx<br/>bvs.example.com<br/>öffentliches Zertifikat"]
    plesk -->|"HTTP 9950<br/>Host, X-Forwarded-For,<br/>X-Forwarded-Proto: https,<br/>Upgrade für WebSocket"| nas["nginx auf dem NAS<br/>192.168.1.20"]
    nas --> panel["/ Panel, /api/ bvsd"]
    nas --> mods["/git/, /svn/, /bucket/, /ide/"]
    sshc["git über SSH"] -->|"TCP 9952, nicht über den Proxy"| nas
```

### Auf dem NAS

1. Tragen Sie auf der Seite Netzwerk die Adresse des Plesk-Servers (so, wie das NAS sie sieht) unter Vertrauenswürdige
   Proxys ein.
2. Reines HTTP kann auf „Ausliefern“ bleiben. „Auf HTTPS umleiten“ funktioniert ebenfalls, sobald der Plesk-Server ein
   vertrauenswürdiger Proxy ist.

### In Plesk

Tragen Sie diese Direktiven in die Apache- und nginx-Einstellungen der Domain ein, im Feld „Zusätzliche
nginx-Anweisungen“ (in der englischen Oberfläche „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: der Editor und sein Terminal
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $http_connection;

    # große Pushes und Uploads: keine Größengrenze, gestreamt, eine Stunde Stille erlaubt
    client_max_body_size 0;
    proxy_request_buffering off;
    proxy_buffering off;
    proxy_read_timeout 3600s;
    proxy_send_timeout 3600s;
}
```

`location ~ ^/` ist eine Location mit regulärem Ausdruck. nginx zieht einen passenden regulären Ausdruck einem
einfachen `location /` vor (Plesk schreibt ein eigenes), daher bedient dieser Block jeden Pfad der Site, und
`proxy_pass` ohne Pfad reicht den Pfad der Anfrage unverändert weiter. Schalten Sie auf derselben Einstellungsseite
„Statische Dateien direkt durch nginx bereitstellen“ aus: Ist es an, beantwortet Plesk Anfragen nach `.js`-, `.css`-
und Bilddateien aus dem eigenen Ordner der Domain, und das Panel lädt nicht.

Schalten Sie in den Hosting-Einstellungen der Domain die dauerhafte, SEO-sichere 301-Weiterleitung von HTTP auf HTTPS
ein (in der englischen Oberfläche „Permanent SEO-safe 301 redirect from HTTP to HTTPS“). Die zusätzlichen
nginx-Anweisungen gelten auch für die Site der Domain auf reinem HTTP (Port 80); ohne die Weiterleitung antwortet die
Site auch dort, und Passwörter gehen unverschlüsselt durch das Internet.

Warum jede Zeile zählt:

| Direktive | Was ohne sie nicht funktioniert |
|---|---|
| `Host $http_host` | nginx auf dem NAS wählt den Serverblock nach `Host`, ein Modul-Hostname passt also nie. bvsd vergleicht den `Origin` der Seite mit `Host`, einschließlich Port, daher scheitert jede Änderung im Panel mit `403 csrf`; `$host` würde einen anderen öffentlichen Port als 443 weglassen und genauso scheitern. |
| `X-Forwarded-For` | jeder Client zählt als der Plesk-Server: fünf falsche Passwörter einer Person sperren alle für 15 Minuten aus |
| `X-Forwarded-Proto $scheme` | das Sitzungs-Cookie wird nicht als `Secure` markiert, und im Modus „Auf HTTPS umleiten“ wird jede Anfrage an Port 9951 des öffentlichen Namens geschickt |
| `Upgrade`, `Connection` | die Seite des Editors öffnet sich, aber der Editor und sein Terminal verbinden sich nie |
| `client_max_body_size 0`, `proxy_request_buffering off`, die Timeouts | für große Pushes und Uploads gelten die eigene Größengrenze und die Timeouts des Proxys |

`X-Forwarded-For` und `X-Forwarded-Proto` zählen nur, wenn der Plesk-Server unter Vertrauenswürdige Proxys steht.
Steht er dort nicht, ignoriert nginx auf dem NAS sie.

### Git über SSH

Ein HTTP-Proxy transportiert kein SSH. Git über SSH geht direkt an Port 9952 des NAS oder über eine
TCP-Portweiterleitung im Router. „SSH-Port in Klon-URLs“ auf der Seite Netzwerk setzt den Port, den die Klonadressen
zeigen, und „SSH-Host in Klon-URLs“ den Namen oder die Adresse, wenn der öffentliche Name nur den Proxy erreicht.

### Die Einrichtung prüfen

- Das Panel öffnet sich unter `https://bvs.example.com/`, und Anmelden und Speichern einer Einstellung funktionieren.
- Die Entwicklerwerkzeuge des Browsers zeigen das Sitzungs-Cookie `bvs_session` mit dem Flag `Secure`.
- Die Seite Protokolle zeigt `nginx-access`: Das erste Feld jeder Zeile ist die eigene Adresse des Clients, nicht die
  des Plesk-Servers.

## Hostnamen für die Module

Ein Modul kann einen eigenen Hostnamen haben (Seite Netzwerk, Hostnamen). nginx liefert dieses Modul dann allein an
der Wurzel des Namens aus. Der Name muss auf das NAS zeigen oder auf den Proxy davor. Auf einem Proxy braucht jeder
Hostname eine eigene Site mit denselben Direktiven wie oben.

| Modul | An der Wurzel seines Hostnamens | Außerdem dort |
|---|---|---|
| Git | Forgejo | `/git/` auf jedem anderen Namen antwortet mit 302 auf den Git-Hostnamen, mit demselben Pfad, derselben Query und demselben Port |
| Editor | code-server | die Anmeldeseite des Panels und `/api/`, damit sich ein Benutzer auf diesem Namen anmelden kann |
| SVN und Bucket | httpd | Repositorys bleiben unter `/svn/<Name>` und Buckets unter `/bucket/<Name>` |

Ein ausgeschaltetes Modul antwortet mit `503` und einem Satz, der das sagt, auf seinen Pfaden und auf seinem
Hostnamen.

### Eine Anmeldung für alle Hostnamen

Das Sitzungs-Cookie gehört zu dem Namen, auf dem es gesetzt wurde, es sei denn, die Einstellung
`network.cookie_domain` nennt eine übergeordnete Domain aller Modul-Hostnamen. Mit `example.com` erreicht eine
Anmeldung auf `bvs.example.com` auch `git.example.com` und `ide.example.com`. bvsd lehnt ein öffentliches Suffix ab
(`com`, `com.pl`, `co.uk`, `myqnapcloud.com`), eine Domain, die nicht allen Modul-Hostnamen übergeordnet ist, und jede
Domain, solange kein Modul-Hostname gesetzt ist. Jede Site unter dieser Domain erhält das Cookie, setzen Sie also die
engste gemeinsame übergeordnete Domain.

Die Seite Netzwerk zeigt diese Einstellung nicht. Der NAS-Admin setzt sie über den CLI-Socket mit dem `curl` des
Pakets:

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

Ein leerer Wert (`""`) hält das Cookie wieder auf jedem Namen für sich.

## Im Internet veröffentlichen (Cloudflare Tunnel)

Die Seite **Veröffentlichen** (Verwaltung, nur Admins) stellt das Paket unter Namen einer Domain ins Internet, deren
DNS bei Cloudflare liegt. `cloudflared`, das das Paket mitbringt, baut vom NAS aus die Verbindung zu Cloudflare auf:
Kein Router-Port wird geöffnet, und Cloudflare hält die Zertifikate für die Namen. Jede Anfrage geht durch das nginx
des Pakets: Das Panel und seine API verlangen eine Anmeldung, ohne Konto öffnet sich nur, was Sie öffentlich gemacht
haben (öffentliche Repositorys, ihre Webansicht, das Paket-Repository).

Es gibt zwei Typen von Namen, von jedem höchstens einen:

| Typ | Beispiel | Was er ausliefert |
|---|---|---|
| Haupt-Hostname | `code.example.com` | das Panel und seine API, Forgejo unter `/git/`, die Webansicht unter `/svn-web/` und `/bucket-web/`, das Paket-Repository unter `/repo/` (nur lesend) und das Handbuch. `/svn/` und `/bucket/` antworten dort mit 404, `/ide/` ebenso, bis Sie den Editor veröffentlichen (unten) |
| Paket-Hostname | `qpkg.example.com` | nur GET und HEAD von `/repo/`, für das App Center auf anderen NAS-Geräten; alles andere antwortet mit 404 |

### Was das Token braucht

Erzeugen Sie unter dash.cloudflare.com (My Profile, API Tokens) ein API-Token mit diesen Berechtigungen, für die Zone
der Namen:

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

Das Paket bewahrt das Token auf dem NAS in einer Datei auf, die nur root liest, und zeigt es nie wieder an.

Nutzt das Token Client IP Address Filtering, tragen Sie dort beide öffentlichen Adressen des NAS ein, IPv4 und
IPv6: Das NAS ruft Cloudflare über IPv6 auf, wenn es eine IPv6-Adresse hat. Sonst lehnt die Seite Veröffentlichen mit
`token_ip_filter` ab und nennt die Adresse, die Cloudflare gesehen hat.

### Veröffentlichen

Geben Sie die Namen und das Token ein und dann **Veröffentlichen**. Das Paket prüft zuerst alles bei Cloudflare und
ändert dort nichts, wenn eine Prüfung fehlschlägt; die Meldung sagt, was zu beheben ist:

- eine Berechtigung, die dem Token fehlt, mit ihrem Namen;
- ein Name in einer Zone, die das Token nicht sieht;
- ein DNS-Eintrag, den der Name schon hat (sein Typ und wohin er zeigt): Löschen Sie ihn bei Cloudflare oder wählen
  Sie einen anderen Namen;
- ein Haupt-Hostname, solange **Öffentliches Durchsuchen** (Seite Git) aus ist, denn seine Besucher haben kein Konto;
- Namen in Zonen zweier Cloudflare-Konten.

Dann legt es den Tunnel an (mit dem Namen `bucketvs-<NAS-Name>`, außer Sie geben einen Namen an; ein Tunnel dieses
Namens, den Cloudflare schon hat, wird übernommen), lässt jeden Namen mit einem proxied CNAME-Eintrag auf ihn zeigen,
fügt die Cache-Regeln hinzu und startet cloudflared. **Erneut veröffentlichen** mit denselben Werten ändert nichts.
Setzen Sie die öffentliche URL (Netzwerk) auf die `https://`-Adresse des Haupt-Hostnamens, damit die Links und
HTTPS-Klonadressen von Forgejo ihn nennen; die Seite erinnert Sie daran, solange sie sich unterscheiden.

Git über SSH geht nicht durch den Tunnel: Cloudflare transportiert zu einem veröffentlichten Namen nur HTTP. Die
SSH-Klonadresse ist daher die LAN-Adresse des NAS mit seinem Port für Git über SSH (9952), im Panel und im eigenen
Klonfeld von Forgejo, und die Seite Git und die Übersicht sagen „Klonen über SSH funktioniert im lokalen Netz; aus dem
Internet klonen Sie über HTTPS.“ Für SSH aus dem Internet leiten Sie den SSH-Port im Router weiter und setzen
**SSH-Host in Klon-URLs** (Netzwerk) auf die öffentliche Adresse des Routers oder auf einen Namen, der nur im DNS
steht und auf sie zeigt, mit **SSH-Port in Klon-URLs**, wenn der Router einen anderen Port nutzt.

Der Haupt-Hostname antwortet auf `/ide/` mit 404, bis ein Admin auf der Seite **Veröffentlichen** den Schalter
**Den Editor unter dem Haupt-Hostnamen veröffentlichen** einschaltet. Das Terminal des Editors ist eine root-Shell auf dem
NAS, und wer sich dort aus dem Internet als Entwickler oder Admin anmeldet, bekommt sie; schalten Sie daher zuerst die
Zwei-Faktor-Authentifizierung für diese Konten ein. Mit eingeschaltetem Schalter durchläuft der Editor dieselbe Anmeldung wie
im LAN, sein Port-Proxy (`/ide/proxy/<port>/`) bleibt dort aus, und Cloudflare hält seine Seiten nie im Cache.
Solange der Schalter an ist, zeigt die Übersicht **Editor im Internet**.

Solange das CI-Modul an ist, läuft ein Workflow, der in ein beliebiges Repository dieses Forgejo gepusht wird, über
den eigenen Runner des NAS als root auf dem NAS, ob er aus dem Internet oder aus dem LAN gepusht wurde: dieselbe Macht
wie das Terminal des Editors. Die Seite **Veröffentlichen** sagt das unter **Was das Internet erreicht**. Geben Sie Push-Zugriff
nur Personen, denen Sie das Admin-Passwort des NAS geben würden.

### Der Cache

Mit eingeschalteten **Cache-Vorgaben** behält Cloudflare Pakete (`*.qpkg`) ein Jahr, Symbole einen Tag, `repo.xml` 5
Minuten und die Dateien des Panels ein Jahr; nie behält es die API, die Seiten von Forgejo, die Anmeldeseiten oder die
Webansicht. Eine Veröffentlichung in das Paket-Repository entfernt `repo.xml` (und ein geändertes Symbol oder die
Dateien einer gelöschten Version) sofort aus dem Cache von Cloudflare. Das Paket fügt seine eigenen Regeln am Ende der
Cache-Regeln der Zone an und ändert die anderen nie.

### Anmeldung aus dem Internet

Das erste Passwort lässt sich nie über einen veröffentlichten Namen setzen: Die Ersteinrichtung nimmt nur einen
lokalen Client an. Falsche Passwörter zählen pro Internet-Client, wie im LAN. Ein mit git an Forgejo gesendetes
Passwort zählt ebenfalls; ein Forgejo-Zugriffstoken funktioniert wie immer. Cloudflare nimmt Anfragen bis 100 MB an,
daher geht ein größerer Push oder Upload über die LAN-Adresse.

### Stoppen und entfernen

**Stoppen** beendet cloudflared; die Namen zeigen dann die eigene Fehlerseite von Cloudflare, und bei Cloudflare
ändert sich nichts. **Bei Cloudflare entfernen** löscht die DNS-Einträge, die Cache-Regeln und den Tunnel, die das
Paket angelegt hat (ein übernommener Tunnel bleibt), und vergisst das Token. Solange Namen veröffentlicht sind, zeigt
die Übersicht ein Etikett **Internet** mit dem Zustand des Tunnels.

## Module

Die Übersicht zeigt jedes Modul mit seinen Prozessen, seinem Speicher und seiner CPU und schaltet es ein und aus (nur
Admins). Ein ausgeschaltetes Modul sagt, was es bietet; ein Admin schaltet es mit seiner Schaltfläche (**Git
einschalten**, **Editor einschalten** usw.) auf seiner Kachel, auf seiner eigenen Seite oder, beim Editor, im Tab
Editor ein.

- Das Einschalten eines Moduls startet einen Job: Das Modul bereitet sich vor, der Schalter wird gespeichert, und
  seine Prozesse starten. Der Job wartet bis zu 30 Sekunden auf sie.
- CI braucht Git: Git lässt sich nicht ausschalten, solange CI eingeschaltet ist.
  Solange Git aus ist, heißt die Schaltfläche von CI **Git und CI einschalten** und schaltet zuerst Git ein.
- Ein ausgeschaltetes Modul führt keinen Prozess aus und belegt keinen Speicher.
- Der Kern ist immer eingeschaltet.

| Zustand | Bedeutung |
|---|---|
| läuft | jeder Prozess des Moduls läuft |
| startet | ein Prozess wurde vor weniger als einer Sekunde gestartet |
| beeinträchtigt | ein Prozess wartet auf seinen Neustart, die Dateien des Moduls konnten nicht geschrieben werden, oder seine Zustandsprüfung schlägt fehl |
| startet in einer Schleife neu | ein Prozess hat sich innerhalb von 10 Minuten 5-mal beendet; bvsd startet ihn alle 15 Minuten neu, und eine Benachrichtigung ging hinaus |
| gestoppt | die Prozesse des Moduls laufen nicht (ein Datenumzug hat sie gestoppt, oder sie konnten nicht starten) |
| aus | das Modul ist ausgeschaltet |

Ein Prozess, der sich beendet, wird nach 1 Sekunde wieder gestartet, dann nach 2, 4 und so weiter bis zu 60 Sekunden.
Nach 10 Minuten Laufzeit beginnt die Wartezeit wieder bei 1 Sekunde. Ein Prozess, der in einer Schleife neu startet,
startet sofort wieder, wenn sein Modul aus- und wieder eingeschaltet wird.

## Git

Das Git-Modul betreibt Forgejo, eine Weboberfläche für Git-Repositorys mit Issues, Pull-Requests, Releases, Wikis,
LFS, Spiegeln und Webhooks. Ein Admin schaltet es mit **Git einschalten** in der Übersicht oder auf der Git-Seite ein.
Der Job bereitet die Ordner und die Datenbank von Forgejo in `<Daten>/forgejo` vor, legt jedes Panel-Konto in Forgejo
an und startet es. Forgejo öffnet sich dann unter `/git/` auf der Adresse des Panels oder unter `/` des
Git-Hostnamens, wenn einer gesetzt ist (siehe [Hostnamen für die Module](#hostnamen-für-die-module)).

**Neues Repository** auf der Git-Seite und, solange es keines gibt, auf der Git-Kachel der Übersicht öffnet das
Formular von Forgejo für ein Repository.
Das Panel bietet ihn und **Ein Repository migrieren oder spiegeln** ab der Rolle developer an: Forgejo lässt einen
viewer oder ein member kein Repository anlegen.

### Repositorys und Konten

- **Eine Anmeldung.** Forgejo hat keine eigene Anmeldung: `/git/user/login` führt zur Anmeldung des Panels, mit dem
  Authentifizierungscode, wenn das Konto einen hat, und danach zurück zu Forgejo.
- **Die Konten kommen aus dem Panel.** Jedes Panel-Konto hat ein Forgejo-Konto mit demselben Namen und demselben
  Passwort. Die Rolle `admin` ist in Forgejo Site-Administrator. Ein deaktiviertes Konto oder eines, das zuerst sein
  Passwort ändern muss, kann sich auch bei Forgejo nicht anmelden. Eine Änderung im Panel erreicht Forgejo innerhalb
  von Sekunden. Die Seiten Benutzer und Git zeigen den Zustand jedes Kontos in Forgejo, und **Jetzt alle Konten
  synchronisieren** auf der Seite Git führt den Abgleich erneut aus.
- **Das Löschen eines Kontos** im Panel legt sein Forgejo-Konto still: Es kann sich nicht mehr anmelden, seine Token
  und SSH-Schlüssel werden entfernt, aber seine Repositorys bleiben. Der Name kann keinem neuen Panel-Konto gegeben
  werden, bis ein Admin das alte Forgejo-Konto in der Site-Administration von Forgejo umbenennt oder löscht
  (**Benutzerkonten**).
- **Ein in Forgejo selbst angelegtes Konto** ändert das Panel nie. Ein Panel-Konto mit demselben Namen zeigt den Grund
  und wartet, bis eines von beiden umbenannt ist.
- **Zwei-Faktor-Authentifizierung und git.** Forgejo nimmt für ein Konto mit eingeschalteter Zwei-Faktor-Authentifizierung kein
  Passwort an. git über HTTP oder HTTPS braucht dann ein Forgejo-Zugriffstoken (in Forgejo **Einstellungen**,
  **Anwendungen**) anstelle des Passworts. SSH-Schlüssel funktionieren wie bisher.
- **Repositorys** werden auf den eigenen Seiten von Forgejo angelegt, importiert und gespiegelt. Importe von Adressen
  im LAN sind standardmäßig erlaubt (Einstellung: `git.allow_local_networks`), weil ein NAS meist aus seinem eigenen
  Netz spiegelt.

### Klonen über HTTPS und SSH

Auf der Seite Git zeigt **Ein Repository klonen** beide Befehle für einen Besitzer und ein Repository:

| Weg | Adresse | Anmeldung |
|---|---|---|
| HTTPS (oder HTTP) | `https://<NAS-Adresse>:9951/git/<Besitzer>/<repo>.git` | Benutzername und Passwort des Panels oder ein Zugriffstoken |
| SSH | `ssh://git@<NAS-Adresse>:9952/<Besitzer>/<repo>.git` | ein SSH-Schlüssel, hinzugefügt in Forgejo unter **Einstellungen**, **SSH- / GPG-Schlüssel** |

- Mit einer öffentlichen URL (hinter einem Proxy, [Öffentliche URL](#öffentliche-url)) verwendet die HTTPS-Adresse
  diese; mit einem Git-Hostnamen ist der Pfad `/<Besitzer>/<repo>.git` auf diesem Namen.
- Der SSH-Port ist `network.ssh_port` (9952) oder `network.public_ssh_port`, wenn ein Router einen anderen Port darauf
  weiterleitet. Der Proxy transportiert kein SSH: Leiten Sie den Port im Router weiter (siehe [Git über
  SSH](#git-über-ssh)).
- Ein über den Cloudflare Tunnel veröffentlichter Name trägt kein SSH, daher nennt die SSH-Klonadresse dann die
  LAN-Adresse des NAS, außer `network.public_ssh_host` (Netzwerk, „SSH-Host in Klon-URLs“) nennt einen Namen nur im
  DNS oder die Adresse des Routers.
- Große Pushes werden nicht abgeschnitten: nginx setzt auf `/git/` keine Größengrenze für den Body.

### Repositorys für Personen ohne Konto zeigen

**Öffentliches Durchsuchen** auf der Seite Git lässt jeden, der Forgejo erreicht, dessen öffentliche Repositorys ohne
Konto lesen. In Forgejo ist jedes Repository öffentlich oder privat (seine **Einstellungen**, **Sichtbarkeit**). Mit
eingeschaltetem Schalter kann ein Besucher ohne Konto:

- ein öffentliches Repository öffnen, seine Ordner und Dateien, und eine Datei unverändert herunterladen;
- die Historie lesen, einen Commit mit seinem Diff öffnen (auch als `.diff`- oder `.patch`-Datei) und zwei Commits
  vergleichen;
- seine Releases sehen, ein Archiv herunterladen und es über HTTP klonen
  (`git clone https://<NAS-Adresse>:9951/git/<Besitzer>/<repo>.git`);
- öffentliche Repositorys unter **Entdecken** und im Profil ihres Besitzers finden.

Ein privates Repository bleibt verborgen: Seine Adresse antwortet mit „nicht gefunden“. Solange der Schalter an ist,
listet Forgejo außerdem keine Konten (der Tab **Benutzer** unter Entdecken und die Benutzersuche sind aus, auch für
angemeldete Benutzer), sodass ein Besucher die Kontonamen nicht sammeln kann. Pushen, Issues, Pull-Requests und
Einstellungen brauchen weiterhin ein Konto. Mit ausgeschaltetem Schalter verlangt jede Seite vom Besucher zuerst eine
Anmeldung.

Um ein Repository zu teilen, machen Sie es in Forgejo öffentlich, schalten **Öffentliches Durchsuchen** ein und senden
die Adresse. Auf einem Git-Hostnamen lautet die Adresse `https://<Git-Hostname>/<Besitzer>/<repo>`.

### Neues Projekt

**Neues Projekt** ist eine Seite, die ein Projekt Schritt für Schritt vom Nichts zu einer gebauten und veröffentlichten
ersten Version führt. Sie öffnet sich in einem eigenen Tab aus der Befehlspalette (Strg+Umschalt+P, **Neues
Projekt**), oben in der Seitenleiste des Editors, im Kopf der Git-Seitenleiste, auf der Git-Seite und auf der Seite
Projekte; ein Beispiel unter **Editor-Projekte** auf der Seite Beispiele hat **Neues Projekt daraus**. Sie braucht
die Rolle Entwickler, die Module Git und Editor und Ihr Konto in Forgejo (die Git-Seite zeigt seinen Abgleich).
Solange Git oder der Editor ausgeschaltet ist, sagt die Seite das über den Schritten, und ein Admin schaltet sie dort
mit einer Schaltfläche ein.

1. **Vorlage.** Die Vorlagen sind gruppiert: Programme (C, C++ mit CMake, Go, Rust, Node, Python, Java), Pakete für QTS (ein
   QPKG-Paket, eine App-Builder-App, ein Paket aus Quellen), Kernel (ein Kernelmodul, der USB-Seriell-Treiber ch341),
   Websites (eine statische Website: Seiten in `src/`, ein Makefile, das `public/` baut, eine Linkprüfung als `make
   test`) und Beispiele. Jede Karte sagt, was ein Build erzeugt, und markiert, was diesem NAS noch fehlt (QDK, eine
   Build-Umgebung, ein ausgeschaltetes Modul), mit einem Link zur Seite, die es hinzufügt.
2. **Name und Sichtbarkeit.** Der Name benennt das Repository und den Ordner `projects/<Name>`; die Seite prüft ihn
   beim Tippen. Dann der Besitzer (Ihr Konto oder eine Organisation, in der Forgejo Ihnen Repositorys erlaubt),
   privat oder öffentlich und auf Wunsch eine Beschreibung. Für eine App-Builder-App die Vorgabe, mit der sie
   beginnt, und das Ziel.
3. **Dienst**, für ein QPKG-Paket und ein Paket aus Quellen: wie das Paket startet und stoppt, aus Blöcken gebaut.
   Wählen Sie eine Vorgabe (**Kommandozeilenwerkzeug** verlinkt das Programm nach `/usr/bin`; **Hintergrunddienst**
   fügt ein Konto, einen Datenordner auf dem Volume, das unter diesem Konto gestartete Programm, ein Warten auf seinen
   Port und einen Stopp mit Zeitlimit hinzu; **Web-App** fügt die Schaltfläche Öffnen in App Center hinzu; **Leer**
   hat nur die Verben) und ändern Sie dann ihre Blöcke. Die Karte jedes Blocks zeigt schon beim Tippen die Zeilen, die
   er ins Skript schreibt, und ein abgelehnter Wert ist unter seinem Feld markiert. Der Start schaltet Enable in App
   Center nie ein, und der Stopp berührt es nie. Die Projektansicht ändert die Blöcke später.
4. **Bauen.** Die Umgebung, in der das Projekt gebaut wird, für das Projekt gespeichert (die Projektansicht ändert sie
   später), oder keine, dann wählt bvsd bei jedem Build eine. Der Schritt bietet zuerst die Umgebung an, die bvsd
   selbst wählt und in der `bvs qbuild` und CI bauen: den Ubuntu 24.04 builder, sonst den Debian 12 builder, sonst
   eine andere Debian- oder Ubuntu-Umgebung. Gibt das Makefile der Vorlage jeder App ihre Umgebung in einer ENV-Zeile,
   sagt der Schritt das und fragt nach keiner, und das Projekt speichert keine. Für ein Paket aus Quellen sagt der
   Schritt außerdem, dass Quelladresse und sha256 im Startrezept Platzhalter sind: Der erste Build schlägt fehl, bis
   das Rezept die echte Quelle Ihres Programms und die sha256 dieser Datei nennt. Die Seite zeigt den Workflow, den CI
   bei jedem Push ausführt. **Direkt nach dem Anlegen bauen** ist an, bis Sie es ausschalten; bei einem Paket aus Quellen
   ist es zunächst aus, und Sie können es einschalten. Ohne Debian- oder Ubuntu-Umgebung für das NAS sagt der Schritt
   das: Ein Admin legt dort mit **Diese Umgebung anlegen** die Builder-Umgebung an, und danach bietet der Schritt sie
   an; sonst schalten Sie **Direkt nach dem Anlegen bauen** aus und bauen das Projekt später.
5. **Veröffentlichen**, für ein QPKG-Paket, eine App-Builder-App oder eine Website: **Testeinsatz auf diesem NAS** (Admin,
   nur signierte Pakete; für eine App **Auf diesem NAS aktivieren**), **Im App Center-Repository dieses NAS
   veröffentlichen** und **Auf meinem Webserver veröffentlichen** (Admin) mit einem der
   [Webziele](#eigener-webserver-webziele); **Ziel hinzufügen** speichert dort ein neues. Jedes ist optional. Die Wahl
   zur Veröffentlichung wird auch zum Schalter **Im App Center-Repository veröffentlichen** des Projekts
   (Projektansicht, Einstellungen): Solange er an ist, veröffentlicht die CI des Projekts dort bei jedem Lauf.
6. **Überprüfen** listet alle Antworten und die Schritte, die laufen werden. **Projekt anlegen** schickt sie ab.
7. **Fortschritt** verfolgt den Job jedes Schritts: das Repository und sein erster Commit, der erste Build, dann der
   Testeinsatz, die Veröffentlichung im App Center und im Web, einer nach dem anderen. Eine Zeile zeigt ihren Zustand
   und das Ende ihres Protokolls; ein Klick zeigt das Protokoll. Ein fehlgeschlagener Schritt sagt warum und bietet
   **Erneut versuchen**, die Schritte danach warten. Am Ende **Projekt öffnen**, **Im Editor öffnen** und **In Forgejo
   öffnen**. Ein Neuladen der Seite oder die Rückkehr zu ihrem Tab liest die Jobs erneut und schickt keinen Schritt
   zweimal. Wer die Seite verlässt, hält die Kette an; die Projektansicht hat dieselben Schritte.

Lehnt bvsd das Projekt ab (ein Name, den ein Ordner, eine App oder ein Repository belegt, ein Besitzer, in dem Sie
nichts anlegen dürfen), kehrt die Seite zum Schritt mit dieser Antwort zurück, zeigt den Grund unter dem Feld und
behält alle anderen Antworten. Ein ausgeschaltetes Modul (ein Admin schaltet es auf der Seite ein), ein fehlendes
Forgejo-Konto und keine Debian- oder Ubuntu-Umgebung für eine App-Builder-App werden in Überprüfen erklärt.

Was der erste Job tut:

- Er legt das Repository als Sie an, privat, außer Sie wählen öffentlich.
- Er legt den Ordner `projects/<Name>` im Datenordner mit dem ersten Commit an: jede Datei der Vorlage und den
  Workflow `.forgejo/workflows/build.yml`, auf `main`. Der Commit ist Ihrer (Name und Adresse stehen als Autor im
  Ordner), und die Skripte der Vorlage sind darin ausführbar. Er pusht diesen Commit als Sie. `origin` ist die
  HTTPS-Adresse des Repositorys, Ihr nächster Push geht also dorthin. Der Commit enthält auch den [Skill für
  KI-Assistenten](#programmieren-mit-einem-ki-assistenten), `.claude/skills/qnap-dev/` und `AGENTS.md`. Der Ordner liegt
  im gemeinsamen Ordner des Editors: Der Editor jedes Entwicklers öffnet `projects/`, ob das Repository privat oder
  öffentlich ist.
- Er speichert die gewählte Umgebung für das Projekt, außer wenn das Makefile der Vorlage jeder App ihre Umgebung gibt. Für
  eine App-Builder-App legt er die App mit dem Namen des Projekts und den Rezepten der Vorgabe an und bindet sie an das
  Projekt, sodass die App auch Rezepte bauen kann, die Sie in den Ordner `recipes/` des Projekts legen. Er speichert
  auch Ihre Wahl zur Veröffentlichung als Schalter Im App Center-Repository veröffentlichen des Projekts, bevor der
  erste Lauf des Workflows ihn liest.

Der Workflow läuft bei jedem Push auf dem Runner des NAS, sobald das CI-Modul an ist, in der Umgebung, die der Workflow
nennt. Der QPKG-Workflow veröffentlicht das Paket im [Paket-Repository](#paket-repository), das nur codesignierte Pakete
annimmt. Scheitert der erste Commit oder sein Push, nachdem das Repository angelegt wurde, entfernt der Job den Ordner,
löscht das Repository wieder und nennt den Grund. Solange der Schalter **Im App Center-Repository veröffentlichen** des
Projekts aus ist, gibt `bvs repo publish` im Workflow einen Hinweis aus, statt zu veröffentlichen, und der Lauf geht
weiter.

### Die Projektansicht

Jeder Ordner von `projects/` im Datenordner hat eine eigene Seite: ein Projekt aus dem Assistenten, die Kopie eines
Beispiels aus den Editor-Projekten oder ein Repository, das Sie dort geklont haben. Sie öffnet sich aus dem Zweig
**Projekte** der Seitenleiste des Editors, von der Seite **Projekte**, mit **Projekt öffnen** am Ende von Neues Projekt
und aus der Befehlspalette (**Projekt … öffnen** oder Strg+P und sein Name). Sie braucht die Rolle Entwickler. Das
Menü eines Projekts auf der Seite Projekte hat auch Bauen, Neu bauen, Veröffentlichen und die Veröffentlichung auf den
Webzielen.
Dasselbe Menü schaltet die Veröffentlichung durch CI im App Center ein oder aus.

Die Seite liest den Ordner und ändert von sich aus nichts darin. Der Typ des Projekts ergibt sich aus seinen Dateien:
eine App-Builder-App mit dem Namen des Ordners, eine `qpkg.cfg` oben (ein QPKG-Paket), ein `Makefile` oben oder nichts
davon, wenn der CI-Workflow des Projekts es baut. Hat eine App-Builder-App den Namen des Ordners, behält ein Projekt mit
einer `qpkg.cfg` oben, oder mit Makefile und einer `qpkg.cfg` in `qpkg/`, seinen eigenen Build, und Testeinsatz und
Veröffentlichen nehmen weiter sein QPKG.

Die Leiste oben enthält die Schritte. Ein Schritt, der nicht laufen kann, behält seine Schaltfläche, ausgegraut, und
sagt daneben und unter der Leiste, warum. Ein Schritt, den das Projekt gar nicht nehmen kann, verschwindet aus der
Leiste, und ein Satz darunter sagt, was das Projekt macht und wohin das Ergebnis des Builds geht: in den Ordner des
Projekts bei einem Programm, auf die Seite Kernel bei einem Kernelmodul, auf die Seite jeder App bei einem Makefile, das
nur App-Builder-Apps baut.

- **Bauen** läuft als Job im unteren Panel. Ein QPKG-Paket baut mit QDKs `qbuild`, ein Makefile-Projekt mit `make`,
  beide in der Build-Umgebung des Projekts. Das Protokoll nennt jede `.qpkg`, die der Build in `build/` schrieb, und ob
  sie eine Codesignatur trägt. Eine App-Builder-App baut wie auf ihrer eigenen Seite. Führt das Standardziel eines
  Makefiles nur Zeilen `./bvsapp.sh create` und `./bvsapp.sh build` aus, tut Bauen dasselbe über den App Builder: Es
  legt jede App einer `create`-Zeile an, die es noch nicht gibt, und baut die Apps dann nacheinander. Eine
  `create`-Zeile lautet `./bvsapp.sh create NAME REZEPTDATEI [ENV] [LINK] [TARGET] [OTHERS]`. Ein solches Makefile nennt
  sein Standardziel und dessen Voraussetzungen in `.PHONY` und enthält nur einfache Zuweisungen, jede Variable einmal
  gesetzt, und einfache Regeln; eines mit etwas anderem (einer Bedingung, einem Include) baut mit make. TARGET ist
  `native`, `aarch64` oder `armv7`, sodass ein Bauen eine App für dieses NAS und eine für ARM 64-Bit machen kann. OTHERS
  sagt, was eine statische App mit Rezepten tut, die nicht statisch linken können (`refuse` oder `bundle`). Eine so
  angelegte App nimmt auch die eigenen Rezepte des Projekts aus `recipes/`, außer der Name des Ordners ist kein
  Projektname (etwa einer mit Leerzeichen). Gibt es eine App dieses Namens schon mit einem anderen Ziel, oder mit einem
  anderen Projekt, während der Ordner eigene Rezepte hat, bleibt Bauen aus und sagt, wie es zu beheben ist. Vor dem
  ersten Build nennt der Schritt die Apps, die er baut. Ein Makefile-Projekt mit seinem Paket in `qpkg/` macht die
  Paketdatei mit `make qpkg` im Terminal des Editors; solange sie fehlt, sagen Testeinsatz, Veröffentlichen und Im Web
  veröffentlichen „Braucht eine Paketdatei“.
- **Neu bauen** baut von Grund auf: `make clean`, dann `make`; bei einem QPKG-Paket gehen zuerst die alten
  `.qpkg`-Dateien von `build/`. Ein Makefile ohne Ziel `clean` kann nicht neu bauen, und die Seite sagt es. Eine
  App-Builder-App hat kein Neu bauen, ebenso wenig ein Makefile, das nur App-Builder-Apps anlegt und baut.
- **Als QPKG exportieren** (Admin) gibt es für eine App-Builder-App, wie auf der Seite der App. Veröffentlichen wartet
  dann auf einen Export, wie auf der Seite der App, und **App öffnen** in der Kopfzeile öffnet diese Seite.
- **Testeinsatz** ist für Admins. Er installiert die neueste `.qpkg` auf diesem NAS mit `qpkg_cli`, so wie App Center
  eine Datei installiert, und der Job wartet, bis App Center es bestätigt. Das Paket bleibt installiert, bis Sie es in
  App Center entfernen. Ein Paket ohne Codesignatur wird abgelehnt: Der Testeinsatz installiert nur signierte Pakete
  und überspringt die Signaturprüfung von App Center nie (in App Center selbst kann der Besitzer eines NAS Apps ohne
  Signatur zulassen; diese Seite tut das nicht). Signieren Sie es mit einem Zertifikat als Dateien (Fall 1 in [Ein
  QPKG code-signieren](#ein-qpkg-code-signieren)). Das Paket von Bucket & Code Server IDE selbst wird abgelehnt, weil
  seine Installation das laufende Paket ersetzen würde. Für eine App-Builder-App aktiviert der Testeinsatz ihren Build
  auf diesem NAS.
- **Veröffentlichen** legt die neueste `.qpkg` nach seinen Regeln ins [Paket-Repository](#paket-repository): Die
  Veröffentlichung eines Admins wird die gelistete Version, die eines Entwicklers wartet auf einen Admin, und ein Paket
  ohne Codesignatur wird abgelehnt, es sei denn, das Repository nimmt unsignierte Pakete an. Eine Zeile darunter sagt,
  ob die CI des Projekts ebenfalls im App Center-Repository veröffentlicht; diese Schaltfläche veröffentlicht in jedem
  Fall.
- **Im Web veröffentlichen** (Admin) kopiert die neueste `.qpkg` oder den Website-Ordner `public/` (sonst `dist/`) auf
  ein [Webziel](#eigener-webserver-webziele) und liest sie über dessen öffentliche Adresse zurück. Bei einer Website,
  die der Assistent angelegt hat, wartet der Schritt auf den ersten Build, der `public/` schreibt.

Ein Makefile-Projekt wählt neben **Bauen** ein Ziel (das Standardziel von make, sein erstes, außer Sie wählen ein
anderes); ein CMake-Projekt baut, oder baut und führt seine Tests aus. Ein Kernelmodul-Projekt baut im Arbeitsbereich
der Kernel-Werkstatt für den laufenden Kernel, und dieser Build ist Sache eines Admins. Ein laufender Schritt hat
**Abbrechen**, und der Job endet meist sofort. **Workflow ausführen** startet den CI-Workflow des Projekts von Hand,
wenn der Workflow einen `workflow_dispatch`-Auslöser hat; sonst ist die Schaltfläche aus und die Seite sagt warum. Der
Workflow jeder Vorlage hat diesen Auslöser. **Auf diesem NAS installiert** listet die Pakete, die die Testeinsätze des
Projekts installiert haben, mit **Von diesem NAS entfernen** für einen Admin. **Projekt löschen**, im Menü neben den
Schaltflächen des Kopfs oder im Menü des Projekts auf der Seite Projekte, ist für einen Admin oder den Entwickler,
dessen Forgejo-Konto das Repository hält. Sie tippen zuerst den Namen des Projekts; der Ordner geht, und das
Forgejo-Repository, eine App-Builder-App des Namens und installierte Pakete bleiben. Die Seite verlinkt danach das
Repository.

**Einstellungen** enthält, was bvsd für das Projekt außerhalb seines Ordners speichert. Für ein Makefile-, CMake- oder
QPKG-Projekt zeigt es die Umgebung, in der Bauen und Neu bauen laufen, und ändert sie. Für ein Makefile, das nur
App-Builder-Apps anlegt und baut, nennt es jede App mit der Umgebung, die ihre Zeile im Makefile angibt. Ein Projekt,
das ein Paket macht oder dessen Workflow `bvs repo publish` ausführt, hat dort den Schalter **Im App Center-Repository
veröffentlichen**: Ist er an, fügt `bvs repo publish` in der CI des Projekts dessen Pakete dem Repository hinzu; ist er
aus, überspringt der CI-Lauf diesen Schritt mit einem Hinweis. Ein Projekt ohne gespeicherte Wahl (die Kopie eines
Beispiels, ein Klon) hat ihn an. **Git** zeigt den Branch, wie weit er laut letztem Fetch vor oder hinter seinem
Remote liegt (die Seite fetcht nie), den letzten Commit und die geänderten Pfade. **Dateien** listet einen Ordner auf
einmal; wählen Sie eine Datei, um sie zu lesen, eine `.qpkg` wird heruntergeladen. Bearbeitet wird im Editor, und der
Ordner `.git` bleibt außen vor.

**Rezepte** listet für ein Projekt mit eigenem Ordner `recipes/` die Rezepte, die seine Apps nehmen: jedes mit seiner
Version, der Version der Bibliothek, die es ersetzt, wenn es den Namen eines Bibliotheksrezepts trägt, und darunter
dem, was die Rezeptprüfung gefunden hat, mit Datei und Zeile. Ein Rezept, das bvsd auslässt, hat eine Zeile mit dem
Grund; sind mehr als drei auf dieselbe Weise ausgelassen, zählt eine Zeile den Rest. Was die Prüfung am Ordner
`recipes` als Ganzem gefunden hat, hat eine eigene Zeile. Ein Klick öffnet den Ordner des Rezepts im Editor; das Menü
kopiert auch den Befehl, der dieselbe Prüfung in einem Terminal des NAS ausführt: `bvs recipe lint` mit dem Ordner des
Projekts. Die Karte erscheint auch, wenn `recipes` des Projekts ein Link, eine Datei, ein Ordner, den bvsd nicht lesen
kann, oder ein Ordner nur mit Links ist, und sagt, warum kein Rezept daraus gelesen wird.

**Dienst und Paket** (ein QPKG-Paket oder ein Makefile-Projekt mit seinem Paket in `qpkg/`, etwa ein Paket aus
Quellen) enthält das Start- und Stoppskript des Pakets als Blöcke, die Felder seiner `qpkg.cfg` (**Paket**) und die
daraus geschriebenen Dateien (**Skript**). **Speichern** zeigt den Vergleich jeder Datei und committet sie dann ins
Repository des Projekts; hat sich das Projekt inzwischen geändert, planen Sie neu. **Von Hand bearbeiten** schaltet
die Blöcke aus, sodass Sie das Skript im Editor bearbeiten; **Blöcke wieder anbinden** zeigt, was ein erneutes
Schreiben ersetzen würde. **Bibliothek aktualisieren** bringt die `bvs-qpkg.sh` dieses Bucket & Code Server IDE ins Paket, mit ihrem
Vergleich. Ein Projekt aus der Zeit vor den Blöcken bietet **Blöcke für dieses Skript verwenden**.

**Versionen** listet, wozu das Projekt wechseln kann, und **Zu dieser Version wechseln** tut es:

| Version | Was der Wechsel tut |
|---|---|
| Eine `.qpkg` in `build/` oder ein App-Builder-Export | ein Testeinsatz dieser Datei (Admin) |
| Der Build des App Builders | aktiviert ihn (Admin); der App Builder behält einen Build |
| Eine Version im Paket-Repository | macht sie zur gelisteten Version (Admin) |
| Ein Git-Tag | checkt den Tag im Ordner aus |

Ein Klick wählt eine Version; ihr Menü setzt auch testweise ein, veröffentlicht, veröffentlicht auf einem Webziel oder
lädt eine `.qpkg` herunter. Ein Tag wird als losgelöster HEAD ausgecheckt, und **Zurück zu main** in der Git-Karte
kehrt zum Branch zurück. Der Wechsel wird abgelehnt, solange der Ordner nicht committete Änderungen hat; die Meldung
nennt sie, und Sie committen oder stashen sie zuerst im Editor. Haken Sie zwei Versionen an, dann listet **Die zwei
vergleichen** die Dateien, die die eine hat und der anderen fehlen, und die geänderten Dateien. Zwei Tags oder zwei
Pakete werden nach Inhalt verglichen, ein Paket mit einem Commit nur nach Größe. Auf der Seite **Pakete** installiert
ein Admin auch eine Repository-Version auf diesem NAS (**Auf diesem NAS installieren**), mit den Prüfungen des
Testeinsatzes.

### Eigener Webserver (Webziele)

Ein Webziel ist der Ort, an dem ein Admin ein Paket oder eine Website auf seinem eigenen Webserver veröffentlicht: ein
Ordner dieses NAS (die Web-Freigabe von QTS, die der Web Server von QTS ausliefert, oder ein anderer freigegebener
Ordner) oder ein rclone-Remote (SFTP, FTP, WebDAV, S3, eingerichtet in der `rclone.conf` der Sicherungen), mit der
öffentlichen Adresse, die es ausliefert. Sie stehen auf der Seite **Veröffentlichen** unter **Webziele**: **Ziel
hinzufügen**, **Ändern**, **Entfernen** und **Testen**, das eine kleine Datei dorthin kopiert, sie über die öffentliche
Adresse holt und wieder löscht.

Ein Ordnerziel ist ein eigener Ordner in einer Freigabe (`/share/Web/meineseite`, nie eine ganze Freigabe oder ein
Volume), außerhalb des Datenordners, weil eine Website-Veröffentlichung auf dem Ziel entfernt, was die Website nicht
mehr enthält. Die Adresse beginnt mit `http://` oder `https://` und enthält keinen Benutzer und kein Passwort.

Jede Veröffentlichung liest die Datei über die öffentliche Adresse zurück: die `.qpkg` eines Pakets mit ihrer
`.sha256`, nach sha256 verglichen, oder die `index.html` einer Website, Byte für Byte verglichen. Liefert die Adresse
nicht, was kopiert wurde, schlägt der Job mit `web_check:` und beiden Werten fehl.

## CI-Runner

Das CI-Modul betreibt Forgejo Actions: Eine Workflow-Datei in `.forgejo/workflows/` eines Repositorys läuft auf einem
Runner, wenn etwas gepusht wird. Es braucht das Git-Modul. Ein Admin schaltet es unter **Module**, **CI**,
**Einschalten** ein. Der Job registriert den eigenen Runner des NAS bei Forgejo, startet Forgejo mit eingeschalteten
Actions neu und startet den Runner; er endet, wenn der Runner Forgejo erreicht hat.

### Der Runner auf dem NAS

- **Seine Labels** sind `bucketvs`, `qnap-x86_64` (oder `qnap-aarch64`) und `self-hosted`, dazu die zusätzlichen
  Labels, die ein Admin auf der Seite CI setzt. Ein Job, dessen `runs-on` eines davon nennt, läuft hier, zum Beispiel
  `runs-on: bucketvs`.
- **Er führt jeden Job auf dem NAS selbst aus, als admin**, nicht in einem Container. Ein Job hat die Werkzeuge des
  Pakets (git, curl, make und die übrigen), Node für JavaScript-Actions wie `actions/checkout` und `bvs env run <env>
  -- <command>` für einen Build in einer Umgebung. Wer einen Workflow in ein Repository pushen kann, kann also
  Befehle als admin auf dem NAS ausführen. Ein member oder viewer kann in Forgejo keine Repositorys anlegen, forken
  oder importieren und keine Organisationen anlegen; developers und admins können das. Ein viewer pusht nie. Solange
  CI eingeschaltet ist, wird der Push eines members in ein Repository, dessen Actions eingeschaltet sind, mit dem
  Grund abgelehnt, ebenso ein Push mit einem Deploy-Key. Der Push eines members oder mit einem Deploy-Key in ein
  Repository, das einem Konto unter developer gehört oder von einem solchen verwaltet wird, wird ebenfalls abgelehnt,
  auch solange CI ausgeschaltet ist: Ein solches Konto könnte Actions selbst einschalten. bvsd schaltet die Actions
  innerhalb einer Minute in jedem Repository aus, das einem member oder viewer gehört oder in das ein solches Konto
  schreiben kann, und vor dem Start des Runners, wenn CI eingeschaltet wird.
- **Was bleibt,** ist die eigene Handlung eines developers oder admins: Actions in einem Repository einschalten oder
  einen Workflow starten, in das ein member zuvor geschrieben hat, oder einem solchen Konto Admin-Zugriff auf ein
  Repository geben, in das es zuvor geschrieben hat. Ein developer, der Commits eines members zusammenführt oder
  baut, führt sie ebenfalls als admin aus.
- **Jobs gleichzeitig** ist `ci.capacity` (standardmäßig 1). Ein Job, den kein Runner online übernehmen kann, wartet
  in Forgejo.
- **Repositorys, die angelegt wurden, bevor CI eingeschaltet war**, haben die Actions von Forgejo ausgeschaltet, daher
  startet ein gepushter Workflow nichts: Schalten Sie sie in den **Einstellungen** des Repositorys unter **Einheiten**
  (Actions) ein. Repositorys, die bei eingeschaltetem CI angelegt werden, haben sie.
- **Actions** wie `actions/checkout@v4` werden von `https://data.forgejo.org` heruntergeladen, daher braucht das NAS
  für Workflows, die sie verwenden, Internetzugang. Der Runner hat keinen Cache: `actions/cache` findet nichts, und
  der Job läuft weiter.
- **Artefakte** (`actions/upload-artifact`) werden an die eigene Adresse von Forgejo hochgeladen (die auf der Seite
  Git), daher muss das NAS diese Adresse erreichen und, bei HTTPS, ihrem Zertifikat vertrauen.
- **Der Zustand des Runners** liegt in `<Daten>/runner`; sein Protokoll ist **runner** unter **Protokolle**. Das
  Ausschalten des Moduls stoppt den Runner und schaltet die Actions in Forgejo aus.

### Externe Runner

Ein Build-Rechner anderswo (ein Server, eine VM) kann ebenfalls Jobs von diesem Forgejo übernehmen. Auf der Seite CI,
unter **Einen externen Runner registrieren**, gibt ein Admin ihm einen Namen und bekommt die Befehle für Linux x86_64
und aarch64. Sie laden forgejo-runner 13.2.0 von code.forgejo.org herunter, prüfen seine sha256, legen das Token des
Runners in `runner-token` ab (nur für seinen Besitzer lesbar) und starten den Runner mit dem Label `linux-amd64` (oder
`linux-arm64`). Der Runner erscheint sofort in der Liste, offline, bis die Befehle laufen. Das Token wird nur in
diesem Moment angezeigt. Ein solcher Runner führt seine Jobs auf seinem eigenen Rechner aus, als der Benutzer, der ihn
gestartet hat, und erreicht Forgejo über dessen öffentliche Adresse.

## SVN und Bucket

Das Modul SVN und Bucket liefert Subversion-Repositorys unter `/svn/<Name>` und Buckets unter `/bucket/<Name>` aus.
Ein Bucket ist ein Netzlaufwerk (WebDAV), in dem jede gespeicherte Datei zu einer Revision wird, sodass jede frühere
Version einer Datei gelesen oder zurückgeholt werden kann. Ein Admin schaltet das Modul unter **Module** ein; die
Seite **SVN & Bucket** legt dann beide Typen an, listet und löscht sie.

- **Die Konten** sind die des Panels. Berechtigungen werden pro Repository gesetzt: Lesen und Schreiben, Lesen oder keine,
  für jedes Konto, dazu ein Standard für jedes nicht aufgeführte Konto. Admins lesen und schreiben immer; member und
  developer schreiben, wo sie Lesen und Schreiben haben; viewer lesen höchstens. Ein neues Repository gibt niemandem
  außer den Admins Zugriff, bis ein Admin ihn setzt (das Personensymbol in seiner Zeile, **Berechtigungen für `<name>`**).
  Unterhalb von admin zeigt die Seite jeder Person ihren eigenen Zugriff auf jedes Repository und jeden Bucket.
- **Falsche Passwörter** zählen wie bei der Anmeldung im Panel: 5 falsche Passwörter innerhalb von 15 Minuten sperren
  dieses Konto für diese Adresse, und die Adresse selbst, für 15 Minuten.
- **Zwei-Faktor-Authentifizierung.** Ein Konto mit Zwei-Faktor-Authentifizierung verwendet sein Passwort nicht für SVN oder WebDAV.
  Es erzeugt auf seiner Kontoseite ein **App-Passwort** (**App-Passwort für WebDAV und SVN**): 25 zufällige Zeichen,
  einmal angezeigt. Ein neues ersetzt das alte; eine Passwortänderung oder das Ausschalten der Zwei-Faktor-Authentifizierung
  entfernt es.
- **Das Löschen** eines Repositorys verschiebt es nach `<Daten>/trash`, wo der Job `trash` es nach 7 Tagen entfernt.

### SVN-Repositorys

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

Die Seite zeigt den Checkout-Befehl mit der Adresse des Repositorys. Hinter einem Proxy folgt die Adresse der
öffentlichen URL, und mit einem SVN-Hostnamen ist sie `/svn/<Name>` auf diesem Namen. Die **Historie** eines
Repositorys listet seine Revisionen; eine Datei kann in jeder Revision gelesen, mit einer anderen verglichen und als
neue Revision zurückgeholt werden (**Diese Version wiederherstellen**, für Konten mit Lesen und Schreiben).

### Buckets

- **Speichern ist Committen.** Ein Client, der eine Datei speichert, erzeugt eine Revision mit dem Namen des Kontos
  und der Zeit; die Revision ist als über WebDAV erzeugt markiert. Viele Clients schreiben zuerst eine leere Datei und
  danach die Daten, daher kann ein Speichern zwei Revisionen erzeugen.
- **macOS-Dateien.** Ein Bucket, der mit **Die macOS-Dateien ._\* und .DS_Store ablehnen** angelegt wurde (der
  Standard), speichert die Dateien `._*` und `.DS_Store` nicht, die der Finder neben das Kopierte ablegt. Der Client
  bekommt keinen Fehler, aber die Datei wird nie zu einer Revision. Das Löschen einer solchen Datei ist erlaubt.
- **Zurückgehen.** Die Historie des Buckets zeigt die Versionen jeder Datei; **Diese Version wiederherstellen**
  speichert den alten Inhalt als neue Revision, sodass nichts verloren geht.

### WebDAV-Clients und ihre Grenzen

| Client | Wie verbinden | Hinweise |
|---|---|---|
| macOS Finder | **Gehe zu**, **Mit Server verbinden**, `https://<NAS-Adresse>:9951/bucket/<Name>` | der Finder sperrt eine Datei, während er sie schreibt; der Bucket beantwortet WebDAV-Sperren |
| rclone | ein `webdav`-Remote mit `vendor = other` und der Adresse des Buckets | geeignet für Kopien per Skript |
| davfs2 (Linux) | `mount -t davfs https://<NAS-Adresse>:9951/bucket/<Name> /mnt/<Ordner>` | Benutzername des Panels und das Passwort oder App-Passwort |
| Cyberduck, WinSCP | eine WebDAV-Verbindung (HTTPS) zu Port 9951, Pfad `/bucket/<Name>` | |
| curl | `curl -T datei -u <Benutzer> https://<NAS-Adresse>:9951/bucket/<Name>/datei` | ein Upload, eine Revision (oder zwei, siehe oben) |
| Windows Explorer | **Netzlaufwerk verbinden** | der WebClient von Windows sendet ein Passwort nur über HTTPS mit einem Zertifikat, dem Windows vertraut: Stellen Sie den Bucket hinter einen Proxy mit einem echten Zertifikat ([Hinter einem Reverse Proxy](#hinter-einem-reverse-proxy-plesk)); das selbstsignierte Zertifikat reicht nicht |

- Große Dateien werden nicht abgeschnitten: nginx setzt auf `/bucket/` keine Größengrenze für den Body und reicht den
  Upload weiter, während er ankommt.
- Eine Datei, die der Bucket nicht speichert (eine macOS-Datei von oben), beantwortet der WebDAV-Server trotzdem mit
  `201`, daher zeigt ein Client keinen Fehler; die Datei ist nicht im Bucket.

### Ein Repository oder einen Bucket für Personen ohne Konto zeigen

Ein Admin kann ein SVN-Repository oder einen Bucket mit dem Schalter in seiner Zeile auf der Seite **SVN & Bucket**
öffentlich machen (**Im Web**). Jeder, der die Adresse hat, kann es dann ohne Konto im Browser lesen:

| Adresse | Was sie zeigt |
|---|---|
| `https://<NAS-Adresse>:9951/svn-web/<Name>/` | ein SVN-Repository |
| `https://<NAS-Adresse>:9951/bucket-web/<Name>/` | einen Bucket |

Der Link **Im Web ansehen** der Zeile öffnet diese Seite. Sie listet die Dateien und Ordner in der neuesten oder einer
älteren Revision, zeigt eine Textdatei mit Zeilennummern (bis 1 MiB) oder ein Bild und bietet jede Datei zum
Herunterladen an. Die **Historie** listet die Revisionen mit ihren Autoren, Nachrichten und geänderten Dateien. Die
Seite einer Revision zeigt den Diff jeder Datei, die sie geändert hat (bis zu 20 Dateien mit höchstens 1 MiB; die
übrigen sind Links); jede Datei lässt sich zwischen zwei Revisionen vergleichen.

- **Nur Lesen.** Die Seiten können nichts ändern. WebDAV-Clients und `svn` brauchen weiterhin ein Konto, auch für ein
  öffentliches Repository.
- **Nur, was öffentlich ist.** Ein Repository, das nicht öffentlich ist, und ein Name, den es nicht gibt, antworten
  beide mit „There is no public repository at this address“, daher verraten die Seiten nicht, welche Namen es gibt.
- **Grenzen.** Eine Adresse darf 60 Seiten auf einmal öffnen und danach 2 pro Sekunde, und 2 Dateien gleichzeitig
  herunterladen; darüber hinaus bittet die Seite, es gleich noch einmal zu versuchen. Ein Diff wird bei 2 MiB
  abgeschnitten.
- **Suchmaschinen** werden gebeten, die Seiten nicht zu indexieren. Die Seiten sind auf Englisch. Sie sehen aus wie das
  Panel, dunkel oder hell, je nachdem, wie der Browser des Besuchers eingestellt ist.
- Hinter einem Proxy oder mit einem SVN-Hostnamen folgt die Adresse der des Repositorys selbst (der öffentlichen URL,
  oder dem SVN-Hostnamen mit denselben Pfaden).

## Editor und KI-Werkzeuge

Das Editor-Modul betreibt VS Code im Browser (code-server) für jedes Konto mit der Rolle developer oder admin. Ein
Admin schaltet es unter **Module** ein. Jedes Konto bekommt eine eigene Instanz, die startet, wenn das Konto den Editor
öffnet, und nach einer Zeit ohne Datenverkehr stoppt.

**Das Terminal des Editors ist eine root-Shell auf dem NAS.** Unter QTS laufen die Prozesse des Pakets als admin (uid 0),
außer Forgejo und httpd. Geben Sie die Rolle developer nur Personen, denen Sie auch das Admin-Passwort des NAS geben
würden.

### Den Editor öffnen

- **Editor öffnen** auf der Seite Editor-Einstellungen (oder `/ide/` auf der Adresse des Panels, oder der
  Editor-Hostname) öffnet die Workbench in einem neuen Tab. Die erste Anfrage startet die Instanz; ohne Sitzung kommt
  zuerst die Anmeldung des Panels.
- Über den veröffentlichten Haupt-Hostnamen, solange der Editor dort nicht veröffentlicht ist, sagen die Seite
  Editor-Einstellungen, der Editor-Tab, die Übersicht und **Shell öffnen**, dass sich der Editor nur aus dem lokalen
  Netz öffnet, und verlinken seine LAN-Adresse (`https://<NAS-Adresse>:<HTTPS-Port>/ide/`); einem Admin verlinken sie
  auch den Schalter auf der Seite **Veröffentlichen**.
- Der Editor öffnet `<Daten>/projects`, wo die Beispiele aus den Editor-Projekten ihre Projekte ablegen. Die
  Einstellungen, Erweiterungen und die Terminal-Historie des Kontos liegen in `<Daten>/ide/<Konto>`, sein Home-Ordner
  in `<Daten>/home/<Konto>`.
- Das Terminal hat die Werkzeuge des Pakets vorne im `PATH`: git, svn, node, npm, rsync, curl, bash und die übrigen.
  `bvs env shell <Name>` betritt eine Umgebung aus Toolchains, und `eval "$(bvs use <id>)"` richtet eine Toolchain
  oder Laufzeitumgebung ein. **Shell öffnen** auf der Seite Toolchains öffnet den Editor mit einem Terminal, das schon in
  dieser Umgebung ist.
- Die Aktivität **Editor** und die Befehlspalette öffnen den Editor mit einer Shell in einer Umgebung oder mit dem
  Ordner einer App. Der Editor erscheint im Tab Editor des Panels; auf einem Telefon öffnet er sich in einem eigenen
  Browserfenster. Nur Seiten der eigenen Adresse des Panels dürfen den Editor in sich zeigen: Das Paket teilt das den
  Browsern bei jeder Antwort des Editors mit, sodass keine andere Site das root-Terminal des Editors unter eine eigene
  Seite legen kann.
- **Leerlaufzeit** (standardmäßig 30 Minuten) stoppt eine Instanz ohne Browser-Datenverkehr, aber nie, solange ein aus
  ihren Terminals gestartetes Programm, ein KI-Agent, den eine Erweiterung gestartet hat (der `claude` von Claude Code
  for VS Code, Codex, Gemini CLI), oder etwas, das von dort aus weiterläuft, noch läuft. 0 schaltet den
  Leerlauf-Stopp aus. Ein Konto kann seine eigene Instanz auf der Seite Editor-Einstellungen stoppen; ein Admin
  kann jede stoppen.
- **Speicher und Prozesse** auf der Seite Editor-Einstellungen zeigt, was eine Instanz ausführt und wie viel Speicher
  sie im Verhältnis zu dem des NAS belegt, markiert, was sie am Laufen hält, beendet einen Prozess mit **Beenden** und
  sagt, warum der letzte Stopp kam und was er beendet hat. Auch ein KI-Agent, der an seiner Eingabeaufforderung
  wartet, hält die Instanz am Laufen: Beenden Sie ihn dort, wenn er nicht mehr gebraucht wird.
- Die Ansicht **Ports** von VS Code erreicht einen Entwicklungsserver auf dem NAS unter `/ide/proxy/<port>/`; die
  eigenen Loopback-Ports des Pakets werden dort abgelehnt.
- **New recipe** im Menü des Ordners `recipes` eines Projekts oder eines Rezeptordners darin im **Explorer** fragt nach
  einem Namen und legt dort ein neues Rezept an: `recipe.json` und `build.sh` aus dem Startrezept des Pakets. In der
  Befehlspalette heißt es **Bucket: New recipe** und funktioniert, solange eine Datei eines Projekts mit einem Ordner
  `recipes` geöffnet ist. Bei jedem Speichern einer Datei eines Rezepts prüft der Editor die Rezepte des Projekts mit
  `bvs recipe lint` und listet die Befunde in der Ansicht **Problems** auf. Er warnt, solange die sha256 des
  Startrezepts nicht durch die echte Ihrer Quelle ersetzt ist.

Der Editor antwortet nur seinen eigenen Seiten. Ein WebSocket, ein Formular oder die Anfrage eines Skripts, die eine
andere Seite mit der Sitzung des Kontos sendet, von einer anderen Website oder von einem anderen Port des NAS, bekommt
403 und startet keine Instanz; so erreicht eine andere Seite auch keinen Entwicklungsserver hinter der Ansicht **Ports**.

### Erweiterungen

- Erweiterungen installieren Sie in der Extensions-Ansicht des Editors (Strg+Umschalt+X). Sie kommen von Open VSX: Der
  Browser sucht auf open-vsx.org und zeigt Seite und Symbol jeder Erweiterung von openvsx.eclipsecontent.org, und das
  NAS lädt die Erweiterung selbst von open-vsx.org herunter. Browser und NAS brauchen dafür beide Zugang zum Internet.
- Die erste Erweiterung jedes Herausgebers fragt „Do you trust the publisher …?“. **Trust Publisher & Install**
  installiert sie; bis dahin zeigt die Erweiterung „Installing“.
- Jedes Konto hat seine Erweiterungen in `<Daten>/ide/<Konto>/extensions`, und ein Paketupdate behält sie.
- Das Paket bringt eine Auswahl an Erweiterungen und Formatierern mit, die der Editor jedem Konto beim ersten Start
  installiert, und erneut nach einem Paketupdate mit einer neuen Auswahl. Damit hebt der Editor Markdown, Shell-Skripte,
  C, C++, Rust, Go, Pascal, Python, Perl, PHP, JSON, YAML, TOML, nginx-Konfiguration, CSS, HTML, JavaScript und
  TypeScript hervor und formatiert sie (**Format Document** in der Befehlspalette). ShellCheck markiert Fehler in
  Shell-Skripten, systemd-Unit-Dateien bekommen Hervorhebung und Vervollständigung, die `.editorconfig` eines Projekts
  legt die Einrückung fest, Farbcodes wie `#ff8800` oder `rgb(0, 128, 0)` erscheinen in jeder Datei in ihrer Farbe,
  und **Hex Editor: Open Active File in Hex Editor** in der Befehlspalette zeigt die Bytes einer Datei.
- Rust wird formatiert, sobald die Rust-Laufzeitumgebung auf der Seite Toolchains installiert ist und der Editor des
  Kontos neu gestartet hat; bis dahin meldet Format Document in einer Rust-Datei, dass die Anfrage fehlgeschlagen ist.
- Auf einem ARM-NAS (dem aarch64-Paket) werden Pascal, Perl und PHP hervorgehoben, aber nicht formatiert: Das Paket
  hat dafür keinen Build von Free Pascal, Perl oder Mago.
- Eine Erweiterung der Auswahl, die das Konto in der Extensions-Ansicht entfernt, bleibt entfernt, auch nach einem
  Paketupdate, und eine, die es aktualisiert, behält die neuere Version. Ein für eine Sprache gewählter
  Standardformatierer (**Format Document With…**, dann **Configure Default Formatter…**) bleibt die Wahl des Kontos.
- Jede `.conf`-Datei öffnet sich als nginx-Konfiguration. Der Sprachname in der Statusleiste stellt eine Datei auf
  eine andere Sprache um.
- Ruff, der Python formatiert, meldet einmal, dass keine Erweiterung für Python-Umgebungen installiert ist. Er
  formatiert auch ohne sie.
- Im Terminal des Editors installiert `code-server --install-extension <Herausgeber.Name>` ebenfalls eine Erweiterung
  von Open VSX.
- Manche Erweiterungen zeichnen eigene Ansichten, etwa das Panel von Claude Code for VS Code, den Hex-Editor und die
  Markdown-Vorschau. Der Browser zeigt diese Ansichten nur, wenn er dem Zertifikat des Panels vertraut. Mit dem
  selbstsignierten Zertifikat, das nur über die Warnseite des Browsers angenommen wurde, bleiben sie leer, und der
  Editor-Tab und die Seite Editor-Einstellungen sagen das. Jeder dieser Wege behebt es:
  - dem Zertifikat des NAS auf dem Gerät vertrauen: **Zertifikat herunterladen** auf der Seite Netzwerk oder in diesem
    Hinweis, dann den vertrauenswürdigen Zertifikaten des Systems oder des Browsers hinzufügen
    ([Das Zertifikat](#das-zertifikat));
  - ein Admin fügt auf der Seite Netzwerk ein Zertifikat für den Namen des NAS hinzu;
  - den Editor über den veröffentlichten Haupt-Hostnamen öffnen, sobald ein Admin ihn dort veröffentlicht.
- Claude Code for VS Code (Herausgeber Anthropic) wird aus der Extensions-Ansicht installiert, und sein Panel
  fragt, wie Sie sich bei Anthropic anmelden. **Claude Code: Open in Terminal** in der Befehlspalette startet Claude
  Code in einem Terminal, und das funktioniert mit jedem Zertifikat. Die eigenen Chatfunktionen von VS Code sind zu
  Beginn aus (`chat.disableAIFeatures`); diese Einstellung betrifft die Erweiterung nicht.

### Claude Code, Gemini CLI und Codex CLI

Unter **KI-Kommandozeilenwerkzeuge** auf der Seite Editor-Einstellungen installiert, aktualisiert und entfernt jedes
Konto diese Werkzeuge für sich selbst:

| Werkzeug | Installiert mit | Befehl |
|---|---|---|
| Claude Code | dem Installer von Anthropic (`claude.ai/install.sh`), npm, wenn das fehlschlägt | `claude` |
| Gemini CLI | `npm install -g @google/gemini-cli` | `gemini` |
| Codex CLI | `npm install -g @openai/codex` | `codex` |

- Sie kommen in den Home-Ordner des Kontos (`~/.local/bin`, `~/.npm-global/bin`), der im `PATH` des Terminals liegt;
  ein anderes Konto sieht sie nicht.
- Jede Installation ist ein Job mit Protokoll auf der Seite Jobs. Die Anmeldung bei jedem Werkzeug (ein Konto oder ein
  API-Schlüssel) geschieht im Werkzeug selbst, im Terminal des Editors.

### Programmieren mit einem KI-Assistenten

Jedes Projekt, das **Neues Projekt** anlegt, enthält einen Skill für KI-Assistenten: `.claude/skills/qnap-dev/` und eine
`AGENTS.md`, die darauf verweist. Der Skill, auf Englisch, enthält Richtlinien für alles rund um ein QNAP-NAS: was es in
der Shell ist (ein Root-Dateisystem auf einer RAM-Disk, BusyBox, kein systemd, kein Paketmanager, `getcfg` und
`setcfg`), Speicher und Pfade, wie man mit diesem Paket baut, signiert und veröffentlicht (`bvs env run`, `bvs ldd`,
`bvs qbuild`, der App Builder, die Kernel-Werkstatt, das Paket-Repository), Ports, Backups und was er nicht vorschlagen
darf, etwa `apt` auf dem NAS oder unsignierte Apps im App Center zu erlauben. Er sagt klar, dass das Signieren mit einem
Hardware-Token nicht unterstützt und nicht getestet ist.

- Starten Sie den Assistenten im Ordner des Projekts (`cd <Daten>/projects/<Name>`), nicht in `projects/`: Jedes
  Werkzeug sucht in dem Ordner, in dem es startet.
- Claude Code lädt den Skill aus `.claude/skills/`, wenn eine Aufgabe ihn braucht. Codex CLI liest `AGENTS.md`. Gemini
  CLI liest `AGENTS.md`, sobald die `.gemini/settings.json` des Projekts
  `{"context": {"fileName": ["AGENTS.md", "GEMINI.md"]}}` enthält.
- Das Paket hält den aktuellen Skill in `/usr/bin/bucketvs/share/skills/qnap-dev`. Ein Projekt, das angelegt wurde,
  bevor das Paket ihn hatte, oder ein aus der Beispielgalerie kopiertes Projekt bekommt ihn, indem Sie diesen Ordner
  in `.claude/skills/` des Projekts kopieren.
- Der Skill zitiert die Wissensdatenbank von QNAP Club Poland (forum.qnap.net.pl) dafür, was das NAS hat und was ihm
  fehlt.
- Der Build des Pakets schreibt den Skill auch als `qnap-skill-<Version>.zip`, dieselben Dateien, für den Einsatz
  außerhalb des Editors: entpacken Sie ihn in `.claude/skills/` eines Projekts oder nach `~/.claude/skills/`. Der Text
  des Skills steht unter CC BY 4.0 (seine `LICENSE`): Sie dürfen ihn teilen und bearbeiten, mit dem Vermerk "QNAP guidelines
  skill by QNAP Club Poland / qnaputilities.com, CC BY 4.0".

## Toolchains und Umgebungen

Die Seite Toolchains lädt herunter, was zum Bauen von Software auf dem NAS nötig ist:

| Typ | Was es ist | Beispiele |
|---|---|---|
| Root-Dateisystem | ein Linux-System zum Entpacken in eine Umgebung | Debian 12 und 13, Ubuntu 22.04 und 24.04, Alpine 3.22 |
| Cross-Toolchain | ein Compiler, der für einen anderen Prozessor baut | Bootlin-Toolchains für aarch64, x86-64 und armv7, mit glibc oder musl |
| Laufzeitumgebung | eine Sprache oder ein Build-Werkzeug | Python, Go, Node.js, Zig, Rust, CMake, Ninja, Java (JDK) |
| QNAP | die eigenen Werkzeuge von QNAP | QDK 2.5.0, für [`bvs qbuild`](#ein-qpkg-mit-bvs-qbuild-bauen) und Als QPKG exportieren |
| Kernel-Quellen | ein Linux-Kernelbaum für die [Kernel-Werkstatt](#kernel-werkstatt) | Linux 6.6.129 von kernel.org; die GPL-Kernelveröffentlichung von QNAP für QTS 5.2.3 |

Einträge für einen anderen Prozessor sind ausgeblendet; „Einträge für andere Prozessoren anzeigen“ listet sie, aber
sie lassen sich nicht installieren. Die Bootlin-Toolchains laufen auf x86_64, daher hat ein ARM-NAS keine
Cross-Toolchain.

### Der Katalog

Das Paket bringt einen eingebauten Katalog mit. Der signierte Remote-Katalog unter
`https://pub.qnaputilities.com/bucketvs/catalog.json` kann Einträge ohne neues Paket hinzufügen. bvsd verwendet ihn
nur, wenn seine ed25519-Signatur zum im Paket eingebauten Schlüssel passt und er nicht älter ist als der verwendete
Katalog. „Katalog aktualisieren“ holt ihn sofort, und der eingebaute Cron-Job „Aktualisierung des Toolchain-Katalogs“
holt ihn jeden Tag um 04:15.

### Installieren und entfernen

Installieren ist Sache eines Admins und wird mit Fortschritt auf der Seite Jobs gezeigt:

1. Das Archiv wird nach `toolchains/cache/` im Datenordner heruntergeladen. Ein unterbrochener Download wird
   fortgesetzt, wenn die Installation erneut gestartet wird.
2. Seine sha256 wird mit dem Katalog verglichen. Bei einer Abweichung wird die Datei gelöscht.
3. Eine Cross-Toolchain oder eine Laufzeitumgebung wird nach `toolchains/<kind>/<id>` entpackt und ihr Archiv
   gelöscht. Ein Root-Dateisystem oder Kernel-Quellen bleiben als Archiv; jede Umgebung und jeder
   Kernel-Arbeitsbereich entpackt eine eigene Kopie. QNAP veröffentlicht seine GPL-Kernelveröffentlichung in zwei
   Teilen: Beide werden heruntergeladen, jeder mit seiner eigenen sha256 geprüft, und zusammengefügt.

Ein volles Volume lässt die Installation mit „no space left on“ und dem Einhängepunkt des Volumes scheitern.

„Entfernen“ löscht einen installierten Eintrag. Umgebungen, die aus einem Root-Dateisystem entstanden sind, sind
Kopien und bleiben.

### Eine Toolchain oder Laufzeitumgebung verwenden

`bvs use <id>` gibt die `export`-Zeilen aus (etwa `PATH`), die eine installierte Cross-Toolchain oder Laufzeitumgebung
in einer Shell nutzbar machen:

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

Die Seite Toolchains zeigt dieselben Zeilen unter „bvs use“ für jeden installierten Eintrag.

### Umgebungen

Eine Umgebung ist ein Linux-System, das aus einem Root-Dateisystem nach `envs/<Name>` im Datenordner entpackt
wurde. Sie ist veränderbar: `apt install build-essential` funktioniert in einer Debian- oder Ubuntu-Umgebung, und was
Sie installieren, bleibt dort.

- „Neue Umgebung“ (admin) lädt zuerst das Root-Dateisystem herunter, wenn es nicht installiert ist, und entpackt es
  dann.
- Solange es keine Umgebung gibt, bietet die Liste den Ubuntu 24.04 builder an, in dem die App-Beispiele gebaut
  werden, mit einer Schaltfläche (Admin): Sein Job erscheint in der Liste, und die Umgebung erscheint, wenn der Job
  fertig ist. Nichts wird von selbst angelegt.
- Ein Name hat 1 bis 64 Zeichen: Kleinbuchstaben, Ziffern, `.`, `_` oder `-`, beginnend mit einem Buchstaben oder
  einer Ziffer.
- Eine Vorlage aus der Beispielgalerie installiert außerdem Pakete mit apt-get, bevor die Umgebung erscheint: die
  Ubuntu-24.04-Builder (der Standard-Builder) und der Debian-12-Builder, beide voll, mit den Build-Werkzeugen des
  App Builders und patchelf; Clang/LLVM (LLVM 18), Fortran (gfortran 13), Kernel-Builder, .NET 8 SDK und Kotlin
  (Kotlin 1.3.31), alle auf Ubuntu 24.04. Die glibc jeder Vorlage ist höchstens 2.39, darum läuft, was darin gebaut
  wird, auf jedem NAS, auf dem das Paket läuft ([Auf welchen NAS-Modellen eine App läuft](#auf-welchen-nas-modellen-eine-app-läuft)). Schlägt
  apt-get fehl, bleibt keine Umgebung zurück. Aus einer Shell:
  `bvs env create <Name> --from <rootfs-id> --package <Name> --package <Name>`.
- „Klonen“ erzeugt eine vollständige Kopie unter einem neuen Namen, zum Beispiel, um eine Änderung zu testen, ohne das
  Original anzufassen.
- „Exportieren“ packt die Umgebung nach `toolchains/exports/<Name>-<Zeit>.tar.zst`.
- „Löschen“ entfernt sie. bvsd lehnt das ab, solange eine Shell oder ein Prozess darin läuft. Dateien in `projects/`
  sind nicht Teil der Umgebung und bleiben.

In einer Umgebung:

- `projects/` des Datenordners ist als `/projects` sichtbar, und eine Shell startet dort;
- `HOME` ist `/root` der Umgebung, und `LANG` ist `C.UTF-8`;
- `/proc`, ein schreibgeschütztes `/sys` und ein kleines eigenes `/dev` sind eingehängt, und `resolv.conf` und `hosts`
  des NAS werden hineinkopiert;
- die Einhängungen gehören zum eigenen Mount-Namespace der Shell und verschwinden, wenn sie endet, daher bleibt auf
  dem NAS nichts eingehängt.

Dasselbe aus einer Shell (die Beispiele rufen `bvs` beim Namen auf, wie ein Cron-Job und eine SSH-Sitzung auf dem NAS
es können, solange das Paket läuft):

```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` und `bvs env run` starten in dem Ordner, aus dem Sie sie aufrufen. Ein Ordner unter `projects/` des
Datenordners ist derselbe Ordner unter `/projects` in der Umgebung. Jeder andere Ordner (der Checkout eines
CI-Jobs, ein Ordner in einer Freigabe) wird unter `/work` eingehängt, und der Befehl startet dort. Von `/` aus starten
sie in `/projects`.

### Ein QPKG mit bvs qbuild bauen

`bvs qbuild` baut ein QPKG aus dem Ordner, in dem Sie es aufrufen (ein Ordner mit `qpkg.cfg`, wie ihn die QPKG-Vorlage
von Neues Projekt anlegt). Es führt `qbuild` von QNAP in einer Debian- oder Ubuntu-Umgebung aus. qbuild ruft
Systemprogramme über ihre vollständigen Pfade auf, und in der Umgebung führen diese Pfade zu den Programmen der
Umgebung statt zu denen des NAS. Das Paket landet in `build/` des Ordners.

```bash
bvs tc install qdk-2.5.0-x86_64      # einmalig; qdk-2.5.0-aarch64 auf einem ARM-NAS
cd /share/CACHEDEV1_DATA/BucketVS/projects/MyTool
bvs qbuild                           # --env <Name> wählt die Umgebung
```

Es verwendet die Umgebung `ubuntu2404-builder` (das Beispiel „Ubuntu 24.04 builder“), sonst `debian12-builder`, sonst
eine andere Debian- oder Ubuntu-Umgebung, und braucht darin `rsync`. Fehlt etwas, bricht es ab und nennt den Befehl,
der es hinzufügt, zum Beispiel `bvs env run ubuntu2404-builder -- apt-get install -y rsync`. Argumente nach `bvs
qbuild` gehen an qbuild. Bevor qbuild läuft, sagt `bvs qbuild`, welcher [Fall der
Codesignatur](#ein-qpkg-code-signieren) für den Ordner gilt.

Eine Umgebung hat eigene Dateien und Werkzeuge. Alles darin läuft als root, sieht jeden Prozess auf dem NAS und
verwendet das Netz des NAS.

### Ein QPKG code-signieren

App Center prüft die Codesignatur eines Pakets bei der Installation. Zum Signieren braucht es ein
Codesignaturzertifikat, das QNAP Ihnen ausgestellt hat. `bvs qbuild` nennt in seiner ersten Zeile einen von drei
Fällen, zum Beispiel `bvs: code signing, case 1 (a certificate as files): …`:

| Fall | Im Ordner liegt | Was passiert |
|---|---|---|
| 1, ein Zertifikat als Dateien | `certificate` und `private_key`, optional `ca_certs` (die Kette) | qbuild signiert das Paket mit `openssl`; die Umgebung braucht `openssl` und `bsdextrautils` |
| 2, ein Zertifikat auf einem Hardware-Token | `certificate_hsm` | nicht unterstützt und nicht getestet: `bvs qbuild` hält an, bevor etwas gebaut wird |
| 3, kein Zertifikat | keine dieser Dateien | das Paket wird unsigniert gebaut |

- **Fall 1.** Legen Sie die drei Dateien neben `qpkg.cfg`. Fehlt der Umgebung ein Programm, nennt `bvs qbuild` den
  Befehl, zum Beispiel `bvs env run ubuntu2404-builder -- apt-get install -y openssl bsdextrautils`. Es lehnt einen
  Ordner mit nur einer der Dateien `certificate` und `private_key` ab (qbuild würde warnen und ein unsigniertes
  Paket bauen) und einen, in dem eine davon leer ist. Halten Sie die Dateien aus git heraus: die `.gitignore` der
  QPKG-Vorlage nennt sie. In CI bewahren Sie sie als Secrets des Repositorys auf und schreiben sie nur für den Build
  in den Ordner.
- **Fall 2.** qbuild würde den Platz des Schlüssels auf dem Token aus Variablen lesen, die es in der Umgebung nicht
  erreichen, und das Paket wurde nicht mit einem Token getestet. Bauen Sie ohne `certificate_hsm` und signieren Sie
  das Paket auf dem Rechner mit dem Token mit QDKs eigenen Werkzeugen; der QNAP-Skill für KI-Assistenten
  (`references/signing.md`) beschreibt QDKs Weg, den dieses Paket nie ausgeführt hat.
- **Fall 3.** Auf einem Gerät mit QTS 6.1 installierte `qpkg_cli -m` ein unsigniertes Paket nicht, während
  `sh <Datei>.qpkg` in einem freigegebenen Ordner es installierte und startete. Das [Paket-Repository](#paket-repository)
  nimmt es nur an, solange ein Admin unsignierte Pakete erlaubt, und App Center auf einem anderen NAS installiert es
  nur, wenn dessen Besitzer Apps ohne gültige digitale Signatur erlaubt, was dieses Paket nie empfiehlt.
  **Als QPKG exportieren** erzeugt immer diesen Fall.
- `qbuild --sign` fügt eine GnuPG-Signatur hinzu; das ist nicht die Codesignatur, die App Center und das Repository
  lesen.

## App Builder

Der App Builder kompiliert ausgewählte Programme in einen Ordner, das Präfix der App (standardmäßig
`/usr/app/my_app_prefix`), innerhalb einer Umgebung.

Der Reiter **Beispiele** unter **Apps** enthält fertige Apps, Umgebungen, CI-Workflows, Cron-Jobs und Editor-Projekte.
**Code ansehen** auf einer Karte zeigt vor der Verwendung, was das Beispiel anlegt, für jede Rolle: die Dateien eines
Editor-Projekts, die Workflow-Datei, den Befehl des Cron-Jobs oder bei einer App ihre Rezepte in Build-Reihenfolge mit
`build.sh` und `recipe.json` jedes Rezepts. Die Ansicht ist schreibgeschützt und zeigt Textdateien; eine Umgebung
hat keine Datei, daher hat ihre Karte diese Schaltfläche nicht. Ein Beispiel verwenden braucht die Rolle developer,
eine Umgebung die Rolle admin: Die Karte einer Umgebung zeigt einem developer kein **Dieses Beispiel verwenden**.

### Rezepte

Jedes Programm wird aus einem Rezept gebaut. Ein Rezept nennt seine Quelle mit einer sha256, die Rezepte, von denen es
abhängt, die Build-Werkzeuge, die die Umgebung braucht, und eine Prüfung, die zeigt, dass das Ergebnis funktioniert
(etwa `bin/bash --version`). Es gibt Rezepte für Bibliotheken (zlib, openssl, ncurses, readline und andere), Shells
(bash, zsh, busybox), Werkzeuge (coreutils, curl, git, jq, rsync, tmux, htop, nano und andere), Server (nginx, redis,
openssh, dropbear) und eine Sprache (lua).

- Ein Rezept wird erst nach den Rezepten gebaut, von denen es abhängt.
- Ein erneuter Build verwendet das Ergebnis eines Rezepts wieder, solange seine Eingaben unverändert sind (die Quelle,
  das Rezept, seine Abhängigkeiten, Compiler und C-Bibliothek der Umgebung, das Präfix und ob dynamisch oder statisch
  gelinkt wird) und jede Datei, die es installiert hat, noch im Präfix liegt.
- Die Linkart entscheidet, wie Programme ihre Bibliotheken finden. **dynamisch** gibt jedem Programm einen RUNPATH von
  `<prefix>/lib`. **relocatable** schreibt ihn relativ zum eigenen Ordner des Programms (`$ORIGIN/../lib`), sodass der
  Baum seine Bibliotheken in jedem Ordner findet; in Programme einkompilierte Pfade, etwa der terminfo-Ordner, nennen
  weiterhin das Präfix. **bundle** ist relocatable und kopiert außerdem die Bibliotheken nach `lib`, die die Programme
  aus der Build-Umgebung laden (etwa libstdc++), sodass die App nur die C-Bibliothek des NAS braucht. **statisch**
  linkt die Rezepte, die es unterstützen, statisch. Ein Wechsel zwischen dynamisch, relocatable und bundle baut nichts
  neu.
- Eine statische App lehnt Rezepte ab, die nicht statisch linken können (openssh, dropbear und tmux), und nennt jedes
  mit dem Grund, außer die App ist so eingestellt, dass sie diese als bundle baut. Die Seite Neue App und **Ändern**
  bieten alle vier Arten an und, für eine statische App, diese Wahl.
- Ein statisches Programm, das Benutzer, Gruppen oder Hostnamen nachschlägt, liest sie nur aus `/etc/passwd`,
  `/etc/group`, `/etc/hosts` und dem DNS des NAS selbst, egal was `/etc/nsswitch.conf` nennt; Konten aus einer Domäne
  oder aus LDAP kennt es also nicht. Bauen Sie solche Programme dynamisch, wenn diese Konten wichtig sind. Das Panel
  warnt, wenn eine statische App eines enthält.
- Nach dem letzten Rezept läuft die Prüfung jedes Rezepts noch einmal über das fertige Präfix.
- Konfigurationsdateien unter `etc/` des Präfixes überstehen einen erneuten Build: Eine bearbeitete Datei bleibt, und
  der neue Standard kommt als `<file>.bvs-new` daneben.

### Rezept-Updates

Neue und aktualisierte Rezepte kommen auch als signierter Katalog von pub.qnaputilities.com und erreichen das NAS so
vor der nächsten Version von Bucket & Code Server IDE. bvsd sucht einmal am Tag nach einem neueren Katalog. Er verwendet ihn nur, wenn
die Signatur zum in bvsd eingebauten Schlüssel passt, jede Datei zur Liste des Katalogs passt und seine Rezepte neuer
sind als die mit dem Paket gelieferten; ein Katalog, der eine Prüfung nicht besteht, wird nie verwendet. Solange ein
Katalog in Gebrauch ist, markiert Neue App die Rezepte, die er bringt, als **neu im Katalog** oder **vom Katalog
aktualisiert**.

Die Karte **Rezeptbibliothek** auf der Seite Apps zeigt, welche Bibliothek Builds verwenden (**Paketbibliothek** mit
der Version des Pakets oder **Katalog** mit der Version des Katalogs), und die letzte Prüfung mit ihrem Ergebnis. Ein
Admin hat dort zwei Schaltflächen; die zweite erscheint, solange ein Katalog in Gebrauch ist:

- **Nach Rezept-Updates suchen** prüft sofort, statt auf die tägliche Prüfung zu warten.
- **Bibliothek des Pakets verwenden** kehrt zu den Rezepten zurück, die mit Bucket & Code Server IDE kamen. Der Katalog bleibt
  zurückgestellt, bis ein neuerer kommt; dann verwenden Builds den neueren.

Nach einem Wechsel verwendet ein neuer Build jedes Rezept wieder, dessen Dateien sich nicht geändert haben. Vor jedem
Build prüft bvsd die Dateien des Katalogs erneut. Hat sich eine auf dem NAS geändert, hält der Build an, bevor etwas
läuft, und Builds verwenden den vorherigen Katalog oder die Bibliothek des Pakets.

### Eine App bauen

**Apps**, **Neue App** (Rolle developer) fragt nach einem Namen, dem Präfix (`/usr/app/<Name>`, außer Sie wählen ein
anderes), der Umgebung, in der die App gebaut wird (eine Debian- oder Ubuntu-Umgebung; die Beispielgalerie legt den
„Ubuntu 24.04 builder“ an, den die Seite zuerst wählt), dem Ziel (die Architektur dieses NAS, oder aarch64 oder armv7
als Cross-Build), den NAS-Modellen, auf denen sie läuft, der Linkart und den Rezepten. Die Rezepte, von denen sie abhängen, werden in Build-Reihenfolge hinzugefügt.

Eine App kann außerdem die Rezepte eines Projekts nehmen, das einen eigenen Ordner `recipes/` hat. Wählen Sie das
Projekt im Schritt **Pakete** unter **Rezepte aus einem Projekt**. Seine Rezepte stehen zuerst in der Liste, unter dem
Namen des Projekts und mit **Projekt** markiert, danach folgen die der Bibliothek. Ein Rezept des Projekts mit dem
Namen eines Rezepts der Bibliothek ersetzt dieses in der App und zeigt, welche Version der Bibliothek es ersetzt. Ein
Rezept, das das Projekt auslässt, wird mit dem Grund genannt. Ein gewähltes Rezept, das in der Liste des Projekts
fehlt, wird unter der Auswahl genannt und in der App ausgelassen; es kommt zurück, sobald Sie eine Liste wählen, die
es hat. Die Seite der App nennt das Projekt, und ein Entwickler öffnet von diesem Namen aus die Projektansicht. Ein
viewer sieht die Rezepte des letzten Builds der App in der Reihenfolge, die sein Protokoll nennt, jedes mit Zustand
und Protokoll; vor dem ersten Build gibt es keine Reihenfolge zu zeigen.

Jede Linkart ist eine Karte, mit einem Bild davon, wo die Programme ihre Bibliotheken finden, was die Art an Größe,
bei Updates und beim Ort, an dem die App läuft, kostet, und welche Rezepte sie unterstützen. Bevor Sie Rezepte wählen,
zählen die Karten die ganze Rezeptbibliothek; wenn Sie zum ersten Schritt zurückkehren, zählen sie die Rezepte Ihres
Builds. Für eine statische App wählen Sie, was mit Rezepten geschieht, die nicht statisch linken können: sie als
Bundle bauen oder weglassen.

- **Bauen** (oder **Neu bauen**) startet einen Job: Er lädt jede Quelle herunter und prüft ihre sha256, installiert
  die Build-Werkzeuge, die die Rezepte in der Umgebung brauchen, baut in einer Kopie des vorherigen Builds der App und
  prüft jedes Programm.
- Ein Build, der fehlschlägt, abgebrochen oder unterbrochen wird, lässt die App, wie sie war: Aktivierung, Download
  und Export verwenden weiter den neuesten Build, der bestanden hat.
- Von einer App läuft immer nur ein Build, und ein Build wartet, solange Programme der aktiven App laufen (stoppen Sie
  sie zuerst).
- Die Seite der App zeigt die Build-Reihenfolge, Zustand und Protokoll jedes Rezepts, die Dateien unter dem Präfix und
  das Archiv des neuesten erfolgreichen Builds (**Herunterladen**, eine `.tar.zst`).
- **Ändern** auf der Seite der App setzt eine andere Umgebung, die NAS-Modelle, auf denen sie läuft, die Linkart,
  das Projekt, aus dem sie Rezepte nimmt, oder andere Rezepte; der nächste Build übernimmt das.
- **Abhängigkeiten**, ein Tab der Seite der App, zeigt, was jede ELF-Datei des neuesten Builds braucht und wo der
  Loader des NAS es findet, gelesen, ohne etwas auszuführen. Eine fehlende Bibliothek, eine Version, die die
  Bibliothek nicht definiert, oder ein RUNPATH, der aus der App hinauszeigt, steht als Befund mit seiner Lösung in der
  Liste. Ein developer führt eine Lösung mit ihrer Schaltfläche aus: **… hinzufügen und bauen**, **Auf … umstellen und
  bauen**, **Erneut bauen**, **RUNPATH patchen**, **Interpreter setzen**, **Andere Umgebung wählen** oder
  **Nur für dieses NAS bauen**. Eine Lösung
  ohne Schaltfläche (ein Paket in eine Umgebung installieren, für eine andere Maschine bauen) ist ein Satz, nach dem
  Sie handeln. Unter den Befunden ist jede Datei mit dem, was sie braucht, als Graph gezeichnet (Programme, die
  Bibliotheken der App, die Bibliotheken des NAS und die fehlenden in Spalten; eine gestrichelte Linie ist eine
  Version, die der Bibliothek fehlt, eine gepunktete Linie eine Bibliothek, die nirgends gefunden wird) oder als Baum;
  wählen Sie eine Datei für ihre Angaben. Auf dem NAS gibt `bvs deps <app>` dasselbe aus.
- **ELF-Inspektor** (Toolchains, Rolle developer) prüft eine Datei: aus dem Build einer App, aus dem Projektordner
  oder aus einer Umgebung, und für einen Admin jede Datei auf dem NAS, so, wie sie auf diesem NAS oder in einer
  Umgebung laufen würde. `bvs ldd <file>` tut dasselbe auf dem NAS (`--env <name>` für eine Umgebung).
- **Jeder Build** führt dieselbe Prüfung über sein Ergebnis aus, bevor er die App ersetzt, und schlägt bei einem
  Problem fehl, das ein Programm auf diesem NAS stoppt, oder bei einem Programm, das eine neuere glibc braucht, als
  die App erlaubt, mit dem Problem und seiner Lösung in der Nachricht des Jobs.
- **Patch-Werkzeug** (der Tab Abhängigkeiten) setzt den RUNPATH (das `lib` der App, die Form mit `$ORIGIN` oder einen
  Ordner der App) oder den Programm-Interpreter ausgewählter Dateien des neuesten Builds, mit dem patchelf der
  Umgebung, auf einer Kopie, die den Build nur ersetzt, wenn es gelingt. **Vorschau** zeigt jede Datei vorher und
  nachher und welche Bibliotheken der neue RUNPATH in der App nicht findet; **Patch anwenden** führt es aus. Der
  nächste Build schreibt die RUNPATHs wieder in der Linkart der App.

Ein Qt-Build führt Qts eigene Werkzeuge aus (moc, rcc und für Übersetzungen lrelease von Linguist). In einem
Cross-Build bauen die Qt-Rezepte (`qtbase`, `qttools`) diese Werkzeuge deshalb zuerst für dieses NAS, nach `host/`
unter dem Präfix, und danach Qt für das Ziel; die Rezepte, die auf Qt aufbauen, führen die Werkzeuge von dort aus. Auf
dem Ziel führt nichts `host/` aus. Es gehört zum Build der App, daher enthalten auch das Archiv und
**Als QPKG exportieren** es.

### Eigene Rezepte

Hat die Rezeptbibliothek für ein Programm kein Rezept, kann ein Projekt ein eigenes mitbringen. Die Rezepte liegen im
Ordner `recipes/` des Projekts, im Format der Bibliothek, und nur die Apps, die das Projekt nennen, bauen sie. Ein
Paket entsteht so in fünf Schritten:

1. **Das Projekt anlegen.** In [Neues Projekt](#neues-projekt) legt die Vorlage **Package from source** (Paket aus
   Quellen) einen Ordner an, mit einem Startrezept, das wie das Projekt heißt, einem Makefile für zwei Apps (eine für
   den Prozessor dieses NAS, eine für aarch64, auf einem x86_64-NAS ein Cross-Build) und dem Start- und Stoppskript
   des Pakets aus dem Schritt **Dienst**.
2. **Das Rezept schreiben.** **New recipe** im Editor fügt ein weiteres Rezept aus demselben Startrezept hinzu
   ([Den Editor öffnen](#den-editor-öffnen)). Tragen Sie die echte Quelladresse des Programms und ihre sha256 in
   `recipe.json` ein (`curl -fsSL <Adresse> | sha256sum` im Terminal des Editors gibt die Summe aus) und die Bauschritte
   in `build.sh`. Ein Rezeptordner enthält `recipe.json`, `build.sh` und die Vendor-Datei, die `recipe.json` nennt,
   sonst nichts; ein Patch kommt deshalb als Here-Document in `build.sh`.
3. **Prüfen.** Jedes Speichern einer Datei eines Rezepts prüft die Rezepte des Projekts und listet die Befunde in der
   Ansicht **Problems**; `make lint` im Terminal und die Karte **Rezepte** der [Projektansicht](#die-projektansicht)
   liefern dieselben Befunde. Die Prüfung liest `recipe.json` und prüft die Syntax von `build.sh` mit `bash -n`. Sie
   führt das Rezept nie aus.
4. **Bauen.** **Bauen** in der Projektansicht legt die beiden Apps an, wenn sie fehlen, und baut sie nacheinander. Ein
   Build liest die Rezepte des Projekts einmal, beim Start, sodass eine später gespeicherte Datei auf den nächsten
   Build wartet. Ein Rezept des Projekts mit dem Namen eines Rezepts der Bibliothek ersetzt dieses nur in den Apps
   dieses Projekts, und das Protokoll des Builds sagt es.
5. **Packen und installieren.** `make qpkg` im Terminal des Editors packt den neuesten erfolgreichen Build jeder App mit
   QDK aus den Toolchains in das Paket und lässt eine App ohne einen solchen Build aus. Liegen die Dateien für die
   Code-Signatur in `qpkg/` neben `qpkg.cfg` oder in einem Ordner, den `QPKG_CERT_DIR` nennt, ist das Paket signiert
   ([Ein QPKG code-signieren](#ein-qpkg-code-signieren)), und **Testeinsatz** in der Projektansicht (admin)
   installiert es auf diesem NAS.

Für Rezepte eines Projekts gelten ein paar Regeln:

- Ein Rezept, das das Projekt auslässt (eine zusätzliche Datei in seinem Ordner, ein Link, eine Datei über ihrer
  Größengrenze), wird auf der Karte Rezepte und von der Prüfung mit dem Grund genannt, und ein Build, der es braucht,
  wird abgelehnt.
- Wird der Ordner des Projekts umbenannt oder gelöscht, bauen seine Apps nicht: Der Build antwortet „Es gibt kein Projekt
  ‚…‘“, bis **Ändern** auf der Seite der App den neuen Namen des Projekts oder **Keines: nur die Bibliothek** setzt.
- Die Rezepte eines Projekts laufen als root in der Build-Umgebung, wie die der Bibliothek. Jeder Entwickler kann sie
  schreiben und bauen, so wie jeder Entwickler das Makefile eines Projekts über Bauen ausführen kann. Ein viewer sieht
  die Namen der Rezepte einer App im Protokoll ihres Builds, nie die Rezeptdateien.
- Ein Rezept eines Projekts bleibt im Projekt. In die Rezeptbibliothek und in den signierten Katalog, den jedes NAS
  bezieht, kommt es nur über eine geprüfte Änderung an Bucket & Code Server IDE selbst.
- Ein Rezept, dessen Build Pakete von npm, Go oder Cargo braucht, listet sie in einer Vendor-Datei. Diese Liste
  schreiben `npm-vendor.py`, `go-vendor.py` und `cargo-vendor.py` in `/usr/bin/bucketvs/share/bucketvs/tools/`; sie
  brauchen eine Python-Laufzeit aus den [Toolchains](#toolchains-und-umgebungen).

### Auf welchen NAS-Modellen eine App läuft

Ein gegen glibc gebautes Programm startet nur auf einem NAS, dessen glibc mindestens so neu ist wie die neueste
Version, die das Programm verlangt. Das Paket rechnet auf jedem NAS, auf dem es läuft (QTS 6.0, QuTS hero 6.0 und
neuer), mit glibc 2.39 oder neuer: seine glibc-Untergrenze. 2.39 ist die glibc von Ubuntu 24.04 und von QuTS hero
6.0.1, gelesen aus dem eigenen `/lib/libc.so.6` dieser Firmware; QNAP nennt keine glibc-Version, und seine Versionshinweise führen kein QTS 6.0. Die Test-NAS mit QTS 6.1.0 und QuTS hero 6.1.0 haben 2.43.

**Läuft auf** in der neuen App und in **Ändern** wählt eines von zweien:

- **Jedes NAS, auf dem das Paket läuft** (Standard): Kein Programm darf eine glibc über 2.39 brauchen. Bauen Sie eine
  solche App in einer Umgebung mit glibc 2.39 oder älter, etwa im Ubuntu 24.04 builder (2.39) oder im Debian 12
  builder (2.36). Das ist die Wahl für **Als QPKG exportieren** und das Paket-Repository.
- **Nur dieses NAS**: Programme dürfen die glibc dieses NAS brauchen, für Software, die eine neuere braucht. Eine
  solche App wird für die Architektur dieses NAS gebaut und nie als QPKG exportiert.

Ein Cross-Build (aarch64 oder armv7) ist immer für jedes NAS. Die neue App warnt, wenn die glibc der gewählten
Umgebung über dem liegt, was die App erlaubt. Nach jedem Build liest bvsd die glibc-Version, die jedes Programm und
jede Bibliothek verlangt, auch bei einem Cross-Build. Eine Datei über der Grenze lässt den Build scheitern, und die
Lösungen werden genannt: eine andere Umgebung (**Andere Umgebung wählen**), die Linkart static oder
**Nur für dieses NAS bauen**. Die Seite der App sagt, welche glibc ihr neuester erfolgreicher Build braucht, und der
Tab **Abhängigkeiten** nennt jede Datei über der Grenze.

Auf dem NAS führt `bvs tree-deps <Präfix> --tree <Ordner> --glibc-max 2.39` dieselbe Prüfung über einen beliebigen
App-Baum aus.

### Eine App auf dem NAS aktivieren

**Aktivieren** (admin) macht das Präfix zu einem Link auf den neuesten erfolgreichen Build der App, sodass ihre
Programme auf dem NAS aus `/usr/app/<Name>/bin` laufen. Die Aktivierung ersetzt nie etwas, das sie nicht selbst
angelegt hat: Ein Ordner, eine Datei oder ein anderer Link am Präfix wird mit dem Grund abgelehnt. Die
QTS-Systemplatte wird bei jedem Start neu aufgebaut, daher legt das Paket die Links bei jedem Start neu an.
**Deaktivieren** (admin) entfernt den Link; das Löschen der App deaktiviert sie zuerst und verschiebt sie für 7 Tage
in den Papierkorb. Ein developer kann eine App nur löschen, solange sie nicht aktiv ist. Eine für eine andere
Architektur gebaute App lässt sich hier nicht aktivieren; laden Sie ihr Archiv für ein NAS dieser Art herunter.

### Eine App exportieren

**Als QPKG exportieren** (admin) packt den neuesten erfolgreichen Build als eigenes QPKG, das die App auf einem
anderen NAS ohne dieses Paket installiert. Es lehnt eine App ab, die nur für dieses NAS gebaut ist, und hält vor dem
Packen an, wenn ein Programm des Builds eine glibc über 2.39 braucht (siehe [Auf welchen NAS-Modellen eine App
läuft](#auf-welchen-nas-modellen-eine-app-läuft)). Dafür ist QDK nötig: Installieren Sie „QNAP QDK 2.5.0“ auf der
Seite Toolchains, Tab QNAP. Solange QDK nicht installiert ist, wird die Schaltfläche nicht angezeigt, und die Seite
der App sagt, woher man es bekommt. Der Export führt qbuild in der Umgebung der App aus, genauso wie [`bvs
qbuild`](#ein-qpkg-mit-bvs-qbuild-bauen). Jeder Build installiert dort `rsync`; bei einer App, die zuletzt mit einer
älteren Version des Pakets gebaut wurde, nennt die Ablehnung den Befehl, der es hinzufügt. Das exportierte Paket legt
beim Start den Link seines Präfixes an und entfernt ihn beim Stopp. Es ist nicht code-signiert: das Protokoll des
Auftrags nennt [Fall 3](#ein-qpkg-code-signieren), ebenso eine Zeile unter der Schaltfläche. In einer SSH-Sitzung
setzen `/etc/init.d/<Name>.sh disable` und `enable` den Schalter des exportierten Pakets im App Center und lassen den
Link des Präfixes, wie er ist. Bei jedem Start fügt das exportierte Paket außerdem eine Zeile in `/etc/profile` und
`/root/.profile` ein, die `bin` und `sbin` der App an den `PATH` einer SSH-Anmeldung anhängt, hinter die eigenen Befehle
des NAS, für admin und jedes andere Konto, das sich anmelden darf; beim Entfernen nimmt es die Zeile wieder heraus.

## Paket-Repository

Das Paket führt ein App-Center-Repository: Darin veröffentlichte QPKG-Pakete lassen sich auf anderen NAS-Geräten aus
dem App Center installieren, wie die Apps jedes Repositorys.

- **Die Adresse.** `bvs repo list` gibt sie aus (und `GET /api/v1/repo`): `<base>/repo/repo.xml`, wobei `<base>` der
  veröffentlichte Paket-Hostname ist (sonst der veröffentlichte Haupt-Hostname), solange das Paket im Internet veröffentlicht
  ist ([Im Internet veröffentlichen](#im-internet-veröffentlichen-cloudflare-tunnel)), sonst die öffentliche URL, wenn
  eine gesetzt ist, sonst `http://<NAS-Adresse>:9950` (`https://…:9951`, wenn reines HTTP nur umleitet). Auf dem
  anderen NAS: App Center, **Einstellungen**, **App-Repository**, **Hinzufügen** und diese Adresse.
- **Veröffentlichen.** `bvs repo publish <file.qpkg>` (auf dem NAS, als root) oder `POST /api/v1/repo/packages` mit
  der Datei als Body (Rolle developer). Die Architektur kommt aus dem QDK-Dateinamen `<name>_<version>_<arch>.qpkg`
  (`x86_64`, `arm_64`, `arm-x41`) oder aus `--arch`. Name, Version, Beschreibung und Autor kommen aus dem Paket
  selbst; `PUT /api/v1/repo/packages/<name>/meta` und `…/icon` (ein PNG, admin) ändern, was das App Center zeigt. Ein
  Paket darf nicht `default` heißen: So heißt das eigene Symbol des Repositorys.
- **Nur signierte Pakete.** Ein Paket ohne Codesignatur wird abgelehnt, weil das App Center solche Pakete nur auf
  einem NAS installiert, das sie erlaubt. Das Repository nimmt ein Paket als signiert an, wenn sein Codesignatur-Block
  eine PKCS#7-Signatur mit einem Zertifikat und einem Unterzeichner ist. Es prüft diese Signatur und ihre Zertifikate
  nicht gegen die von QNAP; das tut das App Center, wenn es das Paket installiert. Ein Admin kann
  `repo.accept_unsigned` in den Einstellungen für ein Netz einschalten, in dem das bekannt ist; solche Pakete sind in
  ihrer Beschreibung markiert. Pakete, die der App Builder exportiert, sind nicht signiert und brauchen daher diese
  Einstellung.
- **Versionen.** Jede Version bleibt herunterladbar, aber das App Center sieht eine Version pro Paket, die, die ein
  Admin listet. Die erste Version eines Pakets von einem Admin wird sofort gelistet; eine spätere, wenn der Admin sie
  dorthin setzt (`POST /api/v1/repo/packages/<name>/latest` oder `bvs repo publish --latest`). Die Version eines
  developers, auch die erste eines neuen Pakets, ist nirgends gelistet, bis ein Admin eine Version zur neuesten macht:
  Jedes NAS, das das Repository hinzugefügt hat, installiert die gelistete Version als root. Das Löschen einer Version
  (admin) entfernt alle ihre Architekturen; war sie die gelistete, ist keine Version gelistet, bis ein Admin eine
  andere wählt.
- **Uploads.** Zwei Uploads werden gleichzeitig kopiert; das Repository lehnt einen ab, der weniger als 256 MiB frei
  auf dem Datenvolume ließe. Das Repository ist nicht Teil einer Sicherung: Veröffentlichen Sie die Pakete nach einer
  Wiederherstellung erneut.
- **Im Panel.** Die Seite **Pakete** zeigt die Adresse zum Kopieren, die Pakete mit ihren Versionen und ob sie signiert
  sind; ein Entwickler lädt dort ein QPKG hoch, und ein Admin listet eine Version als neueste und löscht eine Version.
  Ihre Karte **Codesignatur** zeigt die [drei Fälle](#ein-qpkg-code-signieren), wie viele gelistete Versionen keine
  Signatur tragen, und den Schalter des Admins für unsignierte Pakete, der vor dem Einschalten nachfragt. Die Seite
  einer App zeigt ihre Pipeline (Bauen, als QPKG exportieren, veröffentlichen) mit einer Schaltfläche
  **Veröffentlichen**.

## Kernel-Werkstatt

Die Kernel-Werkstatt baut Kernel-Module für den Kernel, den das NAS ausführt, prüft jedes gegen diesen Kernel und lädt
es. QNAP hat die Quellen seiner aktuellen Kernel nicht veröffentlicht (seine GPL-Veröffentlichungen enden bei QTS
5.2.3), daher liest die Werkstatt, was der laufende Kernel über sich zeigt, und konfiguriert einen Kernelbaum passend
dazu.

### Was Sie brauchen

- Die Umgebung Kernel-Builder aus der Beispielgalerie (Ubuntu 24.04 mit GCC 13 und den Kernel-Build-Werkzeugen). Ein
  Modul muss mit derselben GCC-Hauptversion gebaut werden wie der Kernel; die Werkstatt vergleicht sie.
- Die Rolle admin und root in einem Terminal: Die Befehle `bvs kernel` verwenden den CLI-Socket von bvsd oder laden
  Module.

### Das Profil

`bvs kernel profile` zeigt, was der laufende Kernel ist: sein Release (`6.6.129-qnap`), der Compiler, mit dem QNAP ihn
gebaut hat, das vermagic der eigenen Module von QNAP, die Größe von `struct module` in ihnen und wo init und exit
liegen, ob Module signiert sein müssen, und die Einstellungen, die ein Kernelbaum bekommt, damit er passt.

### Der Arbeitsbereich

`bvs kernel prepare` legt den Arbeitsbereich des laufenden Kernels in `kernels/<release>` des Datenordners an:

1. Er nimmt die Kernel-Quellen, die der Katalog für diese Version hat (`bvs kernel sources`): den eigenen GPL-Baum von
   QNAP, wenn QNAP einen veröffentlicht hat, sonst den Baum von kernel.org, dem die eigenen Änderungen von QNAP
   fehlen. `--source` wählt einen anderen.
2. Er konfiguriert den Baum: mit der Konfiguration von QNAP für dieses Modell, wenn der Baum von QNAP ist, sonst mit
   den Einstellungen des Profils. `--option NAME=m` fügt eine Einstellung hinzu, zum Beispiel einen Treiber.
3. Er bereitet den Baum für Modul-Builds vor (`--full` baut stattdessen den ganzen Kernel, was viel länger dauert und
   die Prüfung die exportierten Symbole des Kernels sehen lässt).
4. Er prüft, dass der Baum genau für das Release des laufenden Kernels baut.

Er läuft als Job auf der Seite Jobs. Ein fehlgeschlagenes prepare behält den Baum des vorherigen.

### Ein Modul bauen

- Ihr eigenes Modul ist ein Ordner in `projects/` mit einer Datei `Kbuild`. `bvs kernel make` in diesem Ordner (oder
  `bvs kernel make --project <folder>`) baut es. Das Beispiel „Kernel module“ ist ein solches Projekt.
- Ein Treiber des Kernels, den QNAP nicht mitliefert, wird aus seinem Verzeichnis im Kernelbaum gebaut:
  `bvs kernel make --dir drivers/usb/serial`. Das Beispiel „USB serial driver ch341“ baut den Treiber ch341 auf diese
  Weise; seine Einstellung kommt zuerst mit `bvs kernel prepare --option USB_SERIAL=m --option USB_SERIAL_CH341=m`.

Die Module, die ein Build erzeugt, werden in `kernels/<release>/modules/` aufbewahrt. Ein Modul, das QNAP für diesen
Kernel mitliefert, wird nicht aufbewahrt: Die eigene Kopie von QNAP bleibt die, die geladen wird.
`bvs kernel make --build-only` bewahrt dort nichts auf: Das Modul bleibt, wo es gebaut wurde. Die CI-Workflows der
Kernel-Vorlagen von Neues Projekt bauen so.

### Die Prüfung und das Laden

`bvs kernel check <module>` vergleicht ein Modul mit dem laufenden Kernel: das vermagic, die Größe von `struct module`
und wo init und exit darin liegen (gegenüber den eigenen Modulen von QNAP), ob eine Signatur verlangt wird, die
Module, von denen es abhängt, und jedes Symbol, das es braucht. Jeder Befund sagt, was nicht passt und was zu tun ist.
Exit-Status 3 bedeutet, dass das Modul nicht geladen werden darf.

`bvs kernel load <module>` führt die Prüfung erneut aus und lädt das Modul, die Module, von denen es abhängt, zuerst
(die eigenen von QNAP vom NAS oder Ihre). Ein Modul mit einem Fehler in der Prüfung wird nie geladen.
`bvs kernel unload <module>` entfernt es; ein Modul in Gebrauch bleibt geladen. Jedes Laden und Entladen wird in
`log/kernel-modules.log` geschrieben.

Der Kernel von QNAP listet seine Datensymbole nicht, daher kann die Prüfung Symbole wie `jiffies` nicht nachschlagen;
sie nennt sie als Warnungen. Fehlt eines davon, lehnt der Kernel das Modul beim Laden ab.

### Module in einer App

Eine App kann Module des Arbeitsbereichs nennen. Das Aktivieren der App lädt sie, bevor ihr Link angelegt wird, und
jeder Start des Pakets lädt sie erneut, sobald das Panel läuft; die Seite Kernel zeigt ein Laden, das noch läuft. Das
Deaktivieren entlädt sie zuerst; ein Modul in Gebrauch hält die App aktiv.

- Wurde das NAS neu gestartet, während Module geladen wurden, lädt der nächste Start keines und zeigt eine
  Benachrichtigung: Laden Sie sie von Hand oder aktivieren Sie die App erneut.
- Hat das NAS ohne den geordneten Stopp des Pakets angehalten (ein Absturz, ein Stromausfall oder ein harter Reset) in
  einem Bootvorgang, in dem das Paket Module geladen hat, lädt der nächste Start diese Module nicht, da eines davon
  den Absturz verursacht haben kann. Die Benachrichtigung und die Seite Kernel nennen sie. Eines von Hand zu laden
  oder seine App erneut zu aktivieren, lässt der Start es wieder laden. Ein normales Herunterfahren oder ein Neustart
  des NAS stoppt das Paket geordnet und ändert nichts.
- Ein Modul, dessen Start nie endet (ein Treiber, der auf seine Hardware wartet), lässt das Panel funktionieren. Eine
  Benachrichtigung nennt es nach einer Minute; andere Lade- und Entladevorgänge warten darauf, und ein Neustart des
  NAS beendet es.

Nach einem QTS-Update auf einen anderen Kernel müssen die Module im Arbeitsbereich des neuen Kernels neu gebaut
werden. Dasselbe gilt nach einer Wiederherstellung in einen anderen Datenordner: `kernels/` ist nicht Teil einer
Sicherung.

### Im Panel

Die Seite **Kernel** zeigt das Profil des laufenden Kernels, die Kernel-Quellen des Katalogs, den Arbeitsbereich und
seine Module. Ein Admin bereitet dort den Arbeitsbereich vor (eine Quelle, die Umgebung, zusätzliche Einstellungen,
der ganze Kernel oder nicht) und baut Module aus einem Projektordner oder einem Verzeichnis des Baums; beides läuft
als Job. **Prüfen** (developer) zeigt jeden Befund mit seiner Lösung; **Laden** und **Entladen** (admin) tun, was
`bvs kernel load` und `unload` tun, und ein abgelehntes Laden zeigt die Prüfung, die es gestoppt hat. Eine `.ko`-Datei
irgendwo unter dem Datenordner lässt sich über ihren Pfad prüfen.

## Cron-Jobs und Vorgänge

Die Seite Jobs hat zwei Listen: geplante Jobs (Cron) und Vorgänge (die langen Jobs, die bvsd ausführt, etwa
Installationen und Umzüge).

### Geplante Jobs

Ein developer legt seine eigenen Jobs an und ändert sie; ein Admin ändert alle. Ein Job hat:

- Zeitplan: fünf Cron-Felder (`0 3 * * *`), sechs mit Sekunden zuerst, oder einen Deskriptor (`@daily`, `@hourly`,
  `@every 6h`). Ein Präfix `CRON_TZ=<zone>` wählt eine andere Zeitzone; ohne es läuft der Job in der Zeitzone des NAS,
  die oben auf der Seite steht.
- Befehl: läuft als root über `bash -lc` des Pakets. Mehrere Zeilen laufen als ein Skript, und seine Ausgabe wird zum
  Protokoll des Laufs.
- Wo er läuft: auf dem NAS oder in einer Umgebung (mit der `bash` der Umgebung, oder `/bin/sh`, wo sie keine hat).
- Arbeitsordner: relativ zu `projects/` im Datenordner oder ein vollständiger Pfad. In einer Umgebung ist nur
  `projects/` sichtbar, als `/projects`.
- Zeitlimit: in Sekunden (0: keiner). Ein Lauf, der danach noch läuft, wird gestoppt und als Zeitüberschreitung
  markiert.
- Während ein Lauf andauert: Der nächste wird übersprungen, oder er wartet (höchstens einer wartet).
- Bei Fehler: Ein fehlgeschlagener Lauf oder einer mit Zeitüberschreitung sendet eine
  [Benachrichtigung](#benachrichtigungen).

Jeder Job behält seine letzten 100 Läufe mit dem Ende ihrer Ausgabe (4 KB). Das vollständige Protokoll eines Laufs
wird 30 Tage aufbewahrt.

Ein Job läuft als root im Auftrag seines Besitzers, mit dessen Home-Ordner als `HOME`, und läuft deshalb nur, solange
sein Besitzer ein aktiver developer oder admin ist. Solange der Besitzer deaktiviert ist oder die Rolle member oder
viewer hat, wird jede Zeit des Jobs als übersprungener Lauf aufgezeichnet, der den Grund nennt, etwa „its owner ola
is disabled“, und **Jetzt ausführen** antwortet mit demselben Grund. Es wird keine Benachrichtigung gesendet. Ein
Lauf, der andauert, wenn der Besitzer deaktiviert wird oder eine Rolle unter developer bekommt, wird gestoppt. Der
Job läuft wieder, sobald das Konto aktiviert oder wieder developer ist. Das Löschen eines Kontos löscht seine Jobs.
Eingebaute Jobs laufen, was auch immer mit einem Konto geschieht.

Wenn ein Befehl endet, laufen Prozesse, die er im Hintergrund gestartet hat, weiter. Bei einem Timeout, dem Löschen
des Jobs oder einem Stopp des Pakets bekommt die ganze Prozessgruppe des Laufs SIGTERM, dann nach 5 Sekunden SIGKILL.

In den Nächten der Zeitumstellung folgen die Jobs der Regel von Debian-cron. Ein Job mit `*` am Anfang seines Minuten-
oder Stundenfelds (auch `*/N`), `@hourly` oder `@every` folgt der verstrichenen Zeit: Im Herbst läuft er in beiden
Durchgängen der wiederholten Stunde, und im Frühjahr kommt die übersprungene Stunde nicht. Jeder andere Job führt jede
seiner Zeiten einmal aus: Eine Zeit in der wiederholten Stunde läuft einmal, und eine Zeit in der übersprungenen
Stunde läuft direkt nach der Umstellung.

Jedes angemeldete Konto, auch ein `viewer`, liest die Befehle der Aufgaben und die Ausgabe ihrer Läufe. Schreiben
Sie Passwörter und Tokens nicht in einen Befehl: Lassen Sie ihn sie aus einer Datei unter `<data>/etc` lesen, die nur
root liest (`chmod 600`).

### Eingebaute Jobs

| Job | Wann | Was er tut |
|---|---|---|
| Wartung | täglich 03:30 | entfernt aus `tmp/` im Datenordner, was sich seit 10 Tagen nicht geändert hat; rotiert `log/audit.log` über 10 MB |
| Papierkorb leeren | täglich 03:45 | entfernt, was vor mehr als 7 Tagen nach `trash/` im Datenordner verschoben wurde (etwa den Home-Ordner eines gelöschten Kontos) |
| Aktualisierung des Toolchain-Katalogs | täglich 04:15 | holt den signierten Toolchain-Katalog |
| nginx-Protokolle rotieren | stündlich, in Minute 7 | benennt `nginx-access.log` oder `nginx-error.log` in `.1` um, sobald sie über 20 MB groß ist (fünf alte Dateien bleiben), und weist nginx an, seine Protokolle neu zu öffnen |

Ein Admin kann Zeitplan, Zeitlimit, Richtlinie und Benachrichtigung eines eingebauten Jobs ändern und ihn ausschalten.
Sein Name und sein Befehl bleiben.

### Vorgänge

Installationen, Umgebungs-Jobs, Datenumzüge und das Einschalten eines Moduls laufen als Vorgänge. Die Liste zeigt die
letzten 50, die neuesten zuerst, mit Fortschritt und Protokoll. Vorgänge und ihre Protokolle werden 30 Tage
aufbewahrt. Ein Vorgang, der lief, als das Paket stoppte, erscheint als `interrupted`. Ein developer bricht seine
eigenen Vorgänge ab; ein Admin bricht jeden ab.

## Sicherungen

Die Seite **Sicherungen** (admin) legt fest, was gesichert wird, wann, wohin die Kopien gehen und wie viele bleiben.
**Jetzt sichern** startet jederzeit eine Sicherung. Auf einer neuen Installation sind geplante Sicherungen aus; sobald
**Geplante Sicherungen** eingeschaltet ist, läuft standardmäßig täglich eine Sicherung (der eingebaute Cron-Job
`backup`). Sie behält den neuesten Lauf jedes der letzten 7 Tage, 4 Wochen und 6 Monate und immer den neuesten guten
Lauf. Es läuft immer nur eine Sicherung, Prüfung, Wiederherstellung oder Löschung auf einmal.

Zwei Läufe an einem Tag zählen als ein Tag: Wenn der neuere fertig ist, wird der frühere Lauf dieses Tages entfernt.
Ein **Jetzt sichern** am Nachmittag ersetzt daher den geplanten Lauf vom Morgen; kopieren Sie einen Lauf vorher
woandershin, wenn der Stand dieses Morgens zählt.

### Was eine Sicherung enthält

Ein Lauf ist ein Ordner `<Daten>/backups/<id>/` mit einer `manifest.json`, die jede Datei mit ihrer sha256 listet:

| Teil | Was |
|---|---|
| Einstellungen | `etc/`: Einstellungen, Konten, TOTP-Geheimnisse, der TLS-Schlüssel, die Sicherungsschlüssel |
| Git | der Dump von Forgejo: Repositorys, Wikis, LFS-Objekte, Anhänge und seine Datenbank |
| SVN, Buckets | eine Kopie jedes Repositorys, inkrementell erstellt: Eine schon kopierte Revision wird nicht erneut kopiert |
| VS-Code-Einstellungen | die VS-Code-Einstellungen jedes Kontos, die Tastenbelegungen, die Snippets und die Liste seiner Erweiterungen (nicht die Dateien der Erweiterungen: Die Ansicht Extensions installiert sie neu) |
| Umgebungen | die Umgebungen aus Toolchains (standardmäßig aus: Sie lassen sich neu anlegen) |
| Projekte | jeder Projektordner mit seiner Git-Historie und den noch nicht committeten Dateien, ohne `node_modules` |
| HOME-Ordner | der HOME-Ordner jedes Kontos, ohne Caches und die eigenen Installationen der KI-Werkzeuge (ihr Installieren-Knopf holt sie zurück) |
| App Builder | die Einstellungen jeder App und der Verlauf ihrer Builds; ein Build erzeugt die Dateien der App neu |
| Paket-Repository | die veröffentlichten Pakete, ihre Symbole und der Index, den App Center liest |

Diese vier Teile sind in jeder Sicherung, unabhängig davon, was auf der Seite angekreuzt ist. Ein Ordner, der sich
seit der vorigen Sicherung nicht geändert hat, wird mit deren Kopie verknüpft und belegt keinen Platz.

Der Lauf wird erst sichtbar, wenn jeder Teil geschrieben und geprüft ist; ein Fehler oder ein volles Volume entfernt
den unfertigen Ordner und lässt die früheren Läufe, wie sie waren. **Prüfen** liest einen Lauf vollständig zurück: die
sha256 jedes Archivs, `svnadmin verify` jeder Revision, `git fsck` jedes Repositorys und eine Integritätsprüfung der
Datenbank von Forgejo.

### Ziele

Jeder Lauf wird auf jedes Ziel der Seite kopiert:

- **Lokaler Ordner** auf diesem NAS: der Lauf, wie er ist, mit unveränderten Dateien als Links auf die vorherige
  Kopie, sodass jede Kopie nur kostet, was sich geändert hat. Nicht verschlüsselt. Auf denselben Platten wie die Daten
  schützt er vor Fehlern, nicht vor einem ausgefallenen Volume.
- **rsync** (`user@host:path`) über SSH. **Mit dem Sicherungsschlüssel des Pakets anmelden**: Die Seite zeigt den
  öffentlichen Schlüssel, der in `authorized_keys` des anderen Rechners gehört.
- **rclone** (`remote:path`, ein Remote aus `<Daten>/etc/rclone.conf`): Cloud-Speicher und alles, was rclone erreicht.

Eine Kopie, die das NAS verlässt (rsync, rclone), muss verschlüsselt sein: Schalten Sie **Kopien verschlüsseln, die
das NAS verlassen** ein und **Schlüssel erzeugen**. Die Kopien werden mit age für seine öffentliche Hälfte
verschlüsselt; **die geheime Hälfte wird einmal angezeigt**, und bvsd behält sie nicht. Ohne sie kann kein NAS, dieses
eingeschlossen, eine verschlüsselte Kopie wiederherstellen: Bewahren Sie sie abseits dieses NAS auf. Ein neuer
Schlüssel verschlüsselt die Läufe ab dann; ältere Kopien brauchen den alten geheimen Schlüssel.

Ein Ziel, das scheitert, wird im Ergebnis des Laufs genannt; der Lauf selbst bleibt gut.

`rclone.conf` enthält die Zugangsdaten der Remotes. Bevor eine Kopie oder ein **Test** sie benutzt, macht bvsd sie
(und den Sicherungsschlüssel) nur für root lesbar (Modus 600), wenn sie es nicht ist, und das Protokoll der Aufgabe sagt
das.

### Wiederherstellen

**Wiederherstellen** bei einem Lauf holt entweder alles zurück, ein Repository (ein SVN-Repository, einen Bucket
oder ein Git-Repository) oder einen Projektordner:

- Nichts wird ersetzt, bevor es geprüft ist: jedes Archiv gegen seine sha256, jede SVN-Kopie mit `svnadmin verify`.
  Eine Kopie, die durchfällt, stoppt die Wiederherstellung, bevor sich an den laufenden Daten etwas ändert.
- Die ersetzten laufenden Daten kommen für 7 Tage nach `<Daten>/trash`, sodass sich eine Wiederherstellung selbst
  rückgängig machen lässt.
- **Ein Repository** oder **ein Projektordner** wird zurückgeholt, während die Module weiterlaufen. Ein versehentlich
  gelöschter Projektordner kommt mit seiner Historie und seinen noch nicht committeten Dateien zurück.
- **Alles** stoppt die Module, tauscht die Daten aus und startet bvsd mit den wiederhergestellten Einstellungen neu:
  Jede Sitzung endet, und Konten und Passwörter sind die aus der Sicherung. Ein Projekt- oder HOME-Ordner, der nach
  der Sicherung angelegt wurde, bleibt.

## Benachrichtigungen

bvsd schreibt jede Benachrichtigung in die QTS-Systemprotokolle. Es kann sie außerdem über shoutrrr-URLs an Chat- und
Maildienste senden (Telegram, Discord, Slack, E-Mail, ein generischer Webhook und andere).

| Benachrichtigung | Wann | Schalter für die URLs |
|---|---|---|
| Job-Fehlschlag | ein Cron-Lauf eines Jobs mit Benachrichtigung bei Fehlschlag schlug fehl oder überschritt sein Zeitlimit; ein Vorgang schlug fehl | `notify.on_job_failure` |
| Sicherung | das Ergebnis jeder Sicherung, Prüfung und Wiederherstellung, auch der erfolgreichen; eine geplante Sicherung, die nicht starten konnte | `notify.on_backup` |
| Neustartschleife | ein Prozess begann, in einer Schleife neu zu starten | `notify.on_crashloop` |
| Zertifikat | ein hochgeladenes Zertifikat hat 30 Tage oder weniger; nginx ist auf das selbstsignierte Zertifikat zurückgefallen; ein neues selbstsigniertes Zertifikat wurde erzeugt | `notify.on_cert_expiry` |
| System | beim Start abgelehnte Einstellungen, ein unvollendeter Datenumzug gefunden, übrig gebliebene Prozesse beendet, die Konfiguration konnte nicht geschrieben werden | wird immer gesendet |

- Die Schalter entscheiden nur, was an die URLs geht. Die Systemprotokolle bekommen jede Benachrichtigung.
- Dieselbe Benachrichtigung geht höchstens einmal in 10 Minuten hinaus. Die nächste danach sagt, wie viele
  zurückgehalten wurden.
- Jede URL bekommt ihr eigenes `shoutrrr send` mit einer Grenze von 10 Sekunden. Ein Fehler wird nur mit dem Schema
  der URL protokolliert, weil der Rest einer URL ihr Token enthält.

Die URLs und die Schalter stehen unter **Benachrichtigungen** auf der Seite System; die Schalter sind standardmäßig
an. Ein Admin ändert sie dort. Andere Konten sehen die Schalter und von jeder URL nur das Schema, weil der Rest einer
URL ihr Token enthält. Der NAS-Admin kann sie auch über den CLI-Socket setzen:

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

## Protokolle und das Support-Paket

Jedes Protokoll ist `log/<name>.log` im Datenordner. Die Protokolle von bvsd und von jedem Prozess, den es
überwacht, werden bei 20 MB rotiert, und fünf alte Dateien bleiben. `nginx-access.log` und `nginx-error.log` haben
dieselben Grenzen, geprüft bei jedem Start von bvsd und jede Stunde durch den Job „nginx-Protokolle rotieren“, daher
kann eines davon 20 MB um bis zu eine Stunde Datenverkehr überschreiten. Der Job „Wartung“ rotiert `audit.log`, sobald
es über 10 MB groß ist, und behält drei alte Dateien. Die Seite Protokolle (admin) zeigt die letzten Zeilen jedes
Protokolls, filtert sie und folgt neuen Zeilen, während sie kommen:

| Protokoll | Wer es schreibt |
|---|---|
| `bvsd` | bvsd selbst: Starts, Stopps, angewendete Einstellungen, Probleme |
| `nginx` | die eigene Ausgabe von nginx |
| `nginx-access`, `nginx-error` | jede Anfrage und die Fehler von nginx |
| `audit` | jede Anfrage auf dem CLI-Socket |
| eines pro Modulprozess | das Programm jedes Moduls |

Die Seite System zeigt die Version, die Plattform, die Zeitzone und jeden Prozess, den bvsd überwacht. „Support-Paket
herunterladen“ (admin) erzeugt ein Archiv für einen Fehlerbericht:

- die Einstellungen mit maskiertem SMTP-Passwort und maskierter shoutrrr-URL;
- die gerenderte `nginx.conf`, die Details des Zertifikats ohne seinen Schlüssel, die Volumes;
- den Zustand jedes Moduls, jedes Prozesses und jedes neueren Vorgangs;
- die letzten 2000 Zeilen jedes Protokolls, in denen jeder Query-String durch `?(query removed)` ersetzt ist.

Es enthält nie Passwörter, Sitzungen, Token oder Schlüssel.

## Updates

bvsd liest `https://pub.qnaputilities.com/bucketvs/latest.json` beim Start und alle 24 Stunden. Nennt die Datei eine
neuere Version, nennt die Seite System sie, mit dem Link aus dem Feed. bvsd meldet die neue Version nur: Es lädt nie
ein Paket herunter und installiert keines. Eine fehlgeschlagene Prüfung ändert nichts im Panel.

### Eine neue Version installieren

Installieren Sie das neue Paket über das alte, aus dem App Center oder mit **Manuell installieren**. Die Installation
stoppt bvsd, ersetzt die Programmdateien und startet es wieder; der Datenordner, die Einstellungen, die Konten
und jedes Repository bleiben, ebenso die Sitzungen des Panels. Die Editor-Instanzen stoppen mit bvsd, daher enden die
offenen Terminals der Editoren; eine Instanz startet wieder, wenn ihr Konto den Editor öffnet.

**Von einer Version, die die Ports 6050, 6051 und 6052 verwendete.** Frühere Builds lauschten auf 6050 (HTTP), 6051
(HTTPS) und 6052 (Git über SSH). Der erste Start der neuen Version legt diese auf 9950, 9951 und 9952 um und sagt das
in einer Benachrichtigung; ein Port, den Sie selbst gewählt haben, bleibt. Ändern Sie, was die alten Ports nennt:
Lesezeichen, einen Plesk-Proxy auf 6050 und Git-Remotes wie `ssh://git@<NAS>:6052/…` (`git remote set-url`).

### Entfernen, neu installieren und ein beschädigtes Paket

Ihre Arbeit bleibt, wenn das Paket entfernt, neu installiert oder beschädigt wird. Alles, was Sie anlegen, liegt im
Datenordner: Konten und Einstellungen, Git- und SVN-Repositorys, Buckets, Projektordner, HOME-Ordner,
VS-Code-Einstellungen und -Erweiterungen, App-Builder-Apps, Umgebungen, das Paket-Repository und die Sicherungen. Der
Installationsordner (`<Volume>/.qpkg/bucketvs`) enthält nur die Programme und eine Datei, die den Datenordner
nennt.

- **Entfernen, dann neu installieren.** Das Entfernen des Pakets löscht den Installationsordner und behält den
  Datenordner und `/etc/config/bucketvs-data-root`, die Datei, die ihn nennt. Die nächste Installation liest
  diese Datei und startet auf demselben Datenordner: dieselben Konten und Passwörter, dieselben Projekte und
  Repositorys, angemeldete Sitzungen eingeschlossen.
- **Über das installierte Paket installieren.** Dieselbe oder eine neuere Version ersetzt die Programmdateien; der
  Datenordner bleibt unberührt.
- **Ein beschädigtes Paket.** Ist der Installationsordner beschädigt (ein Programm fehlt, der Ordner wurde von Hand
  gelöscht), installieren Sie das Paket erneut aus App Center oder mit **Manuell installieren**. Die Installation
  findet den Datenordner auf dieselbe Weise. Ein bvsd, der noch aus gelöschten Dateien läuft, wird beim Start
  gestoppt, und der installierte startet.
- **Was die Daten löscht.** Nur das Löschen des Datenordners selbst. Um absichtlich leer zu beginnen, entfernen
  Sie das Paket, löschen den Datenordner und installieren dann: Der erste Start legt einen neuen
  Datenordner an, wie unter [Wo der Datenordner beginnt](#wo-der-datenordner-beginnt) beschrieben.

Ein ausfallender Datenträger ist etwas anderes: Der Datenordner liegt auf den Datenträgern des NAS selbst.
Bewahren Sie Sicherungen auf einem anderen Rechner auf ([Sicherungen](#sicherungen)); jede Sicherung enthält Ihre
Projektordner, HOME-Ordner, die Einstellungen des App Builders und das Paket-Repository.

## Der Befehl bvs

`bvs` ist die Kommandozeile des Pakets, in `/usr/bin/bucketvs/bin`. Er liegt im `PATH` jedes Prozesses, den bvsd
startet, Cron-Jobs eingeschlossen. Solange das Paket läuft, verweist `/usr/bin/bvs` auf ihn, daher rufen Sie ihn auch
in einer SSH-Sitzung auf dem NAS beim Namen auf; wenn das Paket stoppt, entfernt es den Link.

Jeder Befehl, der über den CLI-Socket mit bvsd spricht, braucht root, weil der Socket nur root gehört: `user`, `jobs`
und, während bvsd läuft, `tc install`, `tc remove` sowie `env create`, `clone`, `export` und `delete`. `env shell`,
`env run` und `qbuild` brauchen ebenfalls root: Sie starten `bvs-chroot`. Als anderer Benutzer bricht ein Befehl, der
den Socket braucht, mit „bvsd's CLI socket … is open to root only: run bvs as root“ ab.

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

- Während bvsd läuft, gehen Installationen und Umgebungs-Jobs über seinen CLI-Socket an bvsd, sodass das Panel sie
  zeigt, und `bvs` folgt ihnen, bis sie enden. Ohne bvsd laufen sie im Prozess `bvs` selbst; `--local` vor dem Befehl
  tut das absichtlich.
- `bvs jobs` und die `user`-Befehle brauchen bvsd.
- `bvs guide` öffnet dieses Handbuch mit dem `less` des Pakets in einem Terminal und gibt sonst oder mit `--path` den
  Pfad seiner Datei aus. Ohne Sprache folgt es der ersten gesetzten Variable von `LC_ALL`, `LC_MESSAGES` und `LANG`:
  `de_DE.UTF-8` öffnet das deutsche Handbuch, `zh_TW` und `zh_HK` das traditionell-chinesische, und eine Sprache, in
  der das Handbuch nicht geschrieben ist (`C`, `zh_CN`), das englische.

## Dateien und Ordner

| Pfad | Was |
|---|---|
| `/usr/bin/bucketvs` | die Programme des Pakets (ein Link auf seinen Installationsordner) |
| `/usr/bin/bucketvs/etc/data-root` | der Pfad des Datenordners |
| `/usr/bin/bucketvs/var/run/bvsd.sock` | der API-Socket des Panels (nginx verwendet ihn) |
| `/usr/bin/bucketvs/var/run/bvsd-cli.sock` | der Socket des Befehls `bvs`, nur root |
| `<Datenordner>/etc/` | Einstellungen, Konten, Sitzungen, Token, die Zertifikate, die gerenderte `nginx.conf` (Geheimnisse mit Modus 600) |
| `<Datenordner>/log/` | die Protokolle |
| `<Datenordner>/tmp/` | temporäre Dateien jedes Prozesses |
| `<Datenordner>/home/<Benutzer>/` | der Home-Ordner jedes Kontos |
| `<Datenordner>/jobs/` | Vorgänge, Cron-Jobs und ihre Läufe, mit Protokollen |
| `<Datenordner>/toolchains/` | der Download-Cache, installierte Toolchains und Laufzeitumgebungen, Exporte |
| `<Datenordner>/envs/` | Umgebungen |
| `<Datenordner>/projects/` | Arbeitsbereiche, als `/projects` in Umgebungen sichtbar |
| `<Datenordner>/trash/` | gelöschte Elemente, 7 Tage aufbewahrt |

Die Module fügen eigene Ordner hinzu; ihre Abschnitte nennen sie.
