Käfer-Button unten rechts: nimmt den sichtbaren Bildschirm mit html2canvas (selbst gehostet) auf, Vorschau mit roten Rechtecken, Beschreibung, Name optional, Senden ohne Anmeldung. Aus offenen Dialogen heraus wird der Dialog selbst aufgenommen. Bearbeiter sehen die Meldungen unter „Fehler" mit Zähler, kopieren einen fertigen Claude-Prompt und den Screenshot. Serverseitig: Pfade nur in exakter Schreibweise, /api standardmäßig nur für Bearbeiter mit ausdrücklicher Freigabeliste, Bremse zählt jeden Versuch vor dem Lesen großer Bodies, X-Forwarded-For nur von vertrauten Proxys, Nutzertext im Prompt eingezäunt. HTTP-Tests für die Sperren. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
465 lines
25 KiB
Markdown
465 lines
25 KiB
Markdown
# VCH-Plattform — die Regeln dieser Umgebung
|
||
|
||
Diese Datei gehört VCH und wird bei jedem Abgleich **überschrieben**. Projektwissen gehört
|
||
in die `CLAUDE.md` daneben, nicht hierher.
|
||
|
||
Sie beschreibt, was VCH mit diesem Projekt macht — vor allem die Stellen, an denen ein
|
||
Verstoß **nicht als Fehler auffällt, sondern als falsches Ergebnis**: ein Deploy, der grün
|
||
meldet und nichts ausliefert, eine Datenbank, die niemand verbindet, ein Audit, das etwas
|
||
prüft, das du gar nicht gepusht hast.
|
||
|
||
## 1. Zwei Maschinen, nicht eine
|
||
|
||
- **Hier (DEV):** ein Debian-LXC. Nutzer `vchuser`, Heimat `/home/vchuser`, Projekte unter
|
||
`/home/vchuser/projects/<name>`. `vchuser` hat `sudo` ohne Passwort und ist in der
|
||
Docker-Gruppe. Vorhanden: Node 20 (npm; pnpm und yarn sind NICHT installiert — `corepack
|
||
enable` holt sie, wenn dein Projekt sie braucht), Python samt venv, Docker, tmux, git,
|
||
code-server auf Port 8080, Claude Code als `claude`, OpenCode als `opencode`; Git gegen Gitea
|
||
vorkonfiguriert. Das Terminal läuft in tmux — ein Dev-Server im Vordergrund blockiert die
|
||
Sitzung, starte ihn in einem eigenen tmux-Fenster oder mit `&`.
|
||
- **Dort (PROD):** eine **andere** Maschine mit demselben Werkzeugsatz. VCH klont dein
|
||
Repository dorthin und startet es. Der Build läuft dort mit **gelöschtem `NODE_ENV`**, damit
|
||
devDependencies installiert werden; ein `NODE_ENV=production` aus `.env.example` gilt erst
|
||
zur Laufzeit.
|
||
|
||
Daraus folgt die wichtigste Regel überhaupt: **VCH deployt und auditiert deinen gepushten
|
||
Commit, nie dein Arbeitsverzeichnis.** Nicht committete Änderungen existieren für Deploy,
|
||
Audit und Veröffentlichung nicht — auch dann nicht, wenn hier alles läuft.
|
||
|
||
## 2. Wegweiser — welcher Weg in welcher Lage
|
||
|
||
Bevor du baust: such deine Lage, nimm den Weg, kenn die Folgen. Die Abschnitte dahinter
|
||
erklären das Wie.
|
||
|
||
**Eine Datenbank, Schlüssel in `.env.example`.** Der Mensch stellt sie im Projektarbeitsplatz
|
||
bereit, VCH trägt die Werte in genau deine Schlüssel ein (Abschnitt 8). Folgen: Datenbank und
|
||
Benutzer heißen `app`, sofern beim Anlegen nichts anderes gewählt wurde; Migrationen laufen in
|
||
deinem `build` oder `start`, VCH führt keine aus; DB-Zeilen im Env-Editor werden gestrichen,
|
||
solange die Datenbank läuft; erst der nächste Deploy trägt die Werte ein.
|
||
<!-- Beleg: prodDatabase.ts provision (DB_DEFAULT_NAME), prodEnvFiles.ts stripManagedLines -->
|
||
|
||
**Zwei Datenbanken — oder Redis, Mongo, Elasticsearch.** Compose mit eigenen Diensten
|
||
(Abschnitt 13). Folgen: ein Compose mit eigener Datenbank deployt **nicht blau-grün, sondern
|
||
in place** — kurze Auszeit beim Umschalten, das Audit läuft als Torprüfung vor dem Start; VCH
|
||
sichert diese Datenbank nicht (kein Backup über die Karte); jeder Datenbankdienst braucht ein
|
||
Named Volume; den DB-Port **nicht** als festen Host-Port veröffentlichen. An der verwalteten
|
||
Datenbank landen zwei deklarierte Verbindungen (`DATABASE_URL` und `ANALYTICS_DATABASE_URL`)
|
||
auf **derselben** — deshalb Compose.
|
||
<!-- Beleg: deploy.ts skipBlueGreen (composeHasDbService), dbEnvRecognizer.ts recognizeDbEnv -->
|
||
|
||
**Nur Dateien oder SQLite.** Keine Datenbank; alles, was bleiben soll, nach `VCH_DATA_DIR`
|
||
(Abschnitt 9). Folgen: `HISTORY_DB=/pfad.sqlite` ist bewusst keine Verbindung und wird nicht
|
||
überschrieben; ein Pfad relativ zum Release ist beim nächsten Deploy weg.
|
||
<!-- Beleg: dbEnvRecognizer.ts classify (nameSuffix-Regel) -->
|
||
|
||
**Eine Datenbank zum Entwickeln, hier auf DEV.** VCH stellt hier keine bereit. Starte selbst
|
||
eine — Docker ist da, `vchuser` ist in der Docker-Gruppe:
|
||
`docker run -d --name dev-db -p 127.0.0.1:5432:5432 -e POSTGRES_PASSWORD=dev postgres:16`.
|
||
Lege eine lokale `.env` an, deklariere die Schlüssel in `.env.example`. Folgen: die Bindung an
|
||
`127.0.0.1` ist Pflicht — ohne Adresse vor dem Port lauscht die Datenbank für jeden im LAN; die
|
||
`.env` gehört nicht ins Repository; für PROD trägt VCH die verwaltete Datenbank ein.
|
||
<!-- Beleg: template-build.ts vchuser (usermod -aG docker, sudoers NOPASSWD) -->
|
||
|
||
**Ein zweiter Prozess neben der App** (Worker, Cron, Queue). Compose (Abschnitt 13). Folgen:
|
||
systemd startet genau **einen** Befehl, den aus `start`; im Compose läuft jeder Dienst mit,
|
||
Dienste hinter `profiles:` bleiben aus.
|
||
<!-- Beleg: deployCommands.ts renderSystemdUnit (ein ExecStart) -->
|
||
|
||
**Reines Frontend ohne Server.** Statisch ausliefern, irgendetwas muss auf `$PORT` lauschen
|
||
(Abschnitt 6.2, „Statische Seite"). Folgen: relative Pfade und Vite `base: './'` (Abschnitt 3),
|
||
sonst bricht die Vorschau hinter dem Pfad-Proxy.
|
||
<!-- Beleg: Abschnitt 6.2 dieses Textes; routes/proxy.ts Pfadpräfix -->
|
||
|
||
**Code in einem Unterordner.** `cd` in `build` und `start` (Abschnitt 6.2, „Monorepo"), die
|
||
`.env.example` in den Ordner, der sie liest. Folgen: jede Beispieldatei bestimmt ihr eigenes
|
||
`.env`-Ziel (Abschnitt 7).
|
||
<!-- Beleg: prodEnvFiles.ts envFilePathForExample -->
|
||
|
||
**Die Vorschau zeigt nichts, der Port wird aber erkannt.** Dein Server lauscht auf `127.0.0.1`.
|
||
Lausche auf `0.0.0.0`: `vite --host`, `next dev -H 0.0.0.0`, `HOST=0.0.0.0`. Folgen: die
|
||
Porterkennung sieht auch lokale Lauscher, der Proxy erreicht sie nicht — ein erkannter Port
|
||
beweist keine erreichbare Vorschau.
|
||
<!-- Beleg: routes/lxc.ts GET /instances/:id/ports (ss -tlnp ohne Bindeadresse), routes/proxy.ts target instance.ip -->
|
||
|
||
**Ein Geheimnis wird gebraucht** (API-Schlüssel, Token). In `.env.example` **leer** deklarieren
|
||
(`API_KEY=`); den Wert hinterlegt der Mensch im Env-Editor (Abschnitt 7). Folgen: leer ohne
|
||
Vorgabe blockiert den Deploy, bis der Wert da ist — gewollt; die Prüfung `secrets` findet
|
||
eingecheckte Werte (Abschnitt 11).
|
||
<!-- Beleg: Abschnitt 7 dieses Textes (blockierende Schlüssel); AUDIT_RUNNER_MANIFEST secrets -->
|
||
|
||
**Der Deploy schlägt fehl.** Du siehst weder das Deploy-Log noch Logs der PROD-Maschine — lass
|
||
dir den Fehlertext aus dem Projektarbeitsplatz geben. Dann prüfen: lauscht die App auf `PORT`,
|
||
antwortet der `health`-Pfad innerhalb von 30 Sekunden, liest sie ihre Werte aus der Umgebung?
|
||
Folgen: im Deploy-Log stehen die letzten 50 Logzeilen der App — logge nach stdout, nicht in
|
||
Dateien.
|
||
<!-- Beleg: deployCommands.ts buildJournalTailCmd (-n 50) -->
|
||
|
||
**Dem Projekt ist eine Integration zugeordnet.** Die Zugangsdaten liegen in
|
||
`.vch-integrations.env` im Projektstamm: `<NAME>_API_URL`, `<NAME>_USER`, `<NAME>_PASSWORD`,
|
||
`<NAME>_TOKEN`. Folgen: die Datei liegt **nur hier auf DEV**, im Klartext, wird nicht deployt
|
||
und ist nicht ignoriert — trag sie in `.gitignore` ein und committe sie nie; für PROD dieselben
|
||
Schlüssel in `.env.example` deklarieren, die Werte in den Env-Editor.
|
||
<!-- Beleg: routes/integrations.ts buildEnvFile/deployEnvFile; kein Verweis in deploy.ts (gemessen 2026-09-17) -->
|
||
|
||
## 3. Vorschau im Browser läuft über einen Proxy
|
||
|
||
Deine Entwicklungs-Server sind nicht direkt erreichbar. VCH erkennt lauschende Ports und
|
||
stellt zwei Wege bereit:
|
||
|
||
1. **Subdomain** (bevorzugt): `https://lxc<VMID>-<PORT>.<domain>/` — deine App liegt auf der
|
||
Wurzel, alles verhält sich normal.
|
||
2. **Pfad-Proxy** (Rückfallweg): `https://<host>/proxy/<instanz>/p/<port>/` — deine App liegt
|
||
hinter einem Pfadpräfix.
|
||
|
||
Damit beides funktioniert: **relative Pfade verwenden.**
|
||
|
||
- `css/style.css` ✅ — nicht `/css/style.css` ❌
|
||
- `fetch('api/daten')` ✅ — nicht `fetch('/api/daten')` ❌
|
||
- Wenn absolute Pfade unvermeidbar sind: `<base href="./">` in den `<head>`.
|
||
|
||
Rahmenwerke: **Vite** `base: './'` · **Create React App** `"homepage": "."` in der
|
||
`package.json` · **Next.js** Vorgabe belassen (Subdomain) oder `basePath` setzen (Pfad-Proxy)
|
||
· **Express** `express.static` genügt, solange das HTML relativ verweist.
|
||
|
||
## 4. Dateien, die VCH im Projektstamm liest
|
||
|
||
| Datei | Pflicht | Wozu |
|
||
|---|---|---|
|
||
| `README.md` | ja | Audit-Prüfung `readme` — blockiert die Veröffentlichung |
|
||
| `.vch-description` | ja | Audit-Prüfung `description`; der Text steht später im Showcase |
|
||
| `.env.example` | wenn die App Umgebungsvariablen liest | die **Deklaration** aller Variablen (Abschnitt 7) |
|
||
| `.vch/deploy.yaml` | wenn die Autoerkennung nicht reicht | wie gebaut und gestartet wird (Abschnitt 6) |
|
||
| `CLAUDE.md` | — | dein Projektwissen; VCH fasst sie nicht mehr an |
|
||
|
||
`README.md` muss beschreiben, **was** das Projekt tut, wie man es installiert, wie man es
|
||
startet und worauf es aufbaut. Die unveränderte Vorlage besteht die Prüfung nicht.
|
||
|
||
`.vch-description` ist **nicht technisch**: was das Projekt tut, welches Problem es löst,
|
||
für wen. Kein Technologie-Stapel, keine Installationsanleitung — die stehen im README.
|
||
Halte beide aktuell, wenn sich Zweck oder Aufbau ändern.
|
||
|
||
`.vch-description` legt VCH beim Anlegen als **Platzhalter** an, der die Prüfung nicht besteht —
|
||
dass die Datei existiert, heißt nicht, dass sie fertig ist.
|
||
|
||
**VCH-eigen, nicht anfassen:** `.vch/PLATFORM.md`, `~/.vch_env`, `~/.config/vch/` und
|
||
`/etc/profile.d/vch-opencode.sh`. Wer „die Shell-Umgebung aufräumt", kappt sich den AI-Server.
|
||
|
||
## 5. Der Port gehört VCH
|
||
|
||
VCH weist jedem Projekt auf der PROD-Maschine einen festen Port zu (Bereich `13000–13999`)
|
||
und hält ihn über alle Deploys hinweg stabil. Du wählst ihn nicht und stimmst dich mit
|
||
niemandem ab.
|
||
|
||
**Deine App MUSS auf `PORT` aus der Umgebung lauschen:**
|
||
|
||
- Node/Express: `app.listen(process.env.PORT || 3000)`
|
||
- Next.js: `next start` beachtet `PORT` von selbst
|
||
- Python: `gunicorn app:app -b 0.0.0.0:$PORT` bzw. `uvicorn main:app --port $PORT`
|
||
- Go/andere: den Wert aus der Umgebung lesen, nicht hartkodieren
|
||
|
||
Ein fest verdrahteter Port läuft hier und scheitert dort am Healthcheck. `PORT` und
|
||
`VCH_DATA_DIR` sind **VCH-eigene** Schlüssel: setzt du sie selbst — in `.env.example`, im
|
||
Env-Editor oder im Compose — wird dein Wert verworfen.
|
||
|
||
## 6. `.vch/deploy.yaml` — die vollständige Referenz
|
||
|
||
Ohne diese Datei versucht VCH, das Projekt selbst zu erkennen (Abschnitt 6.3). Reicht das
|
||
nicht, bricht der Deploy ab **und legt dir eine fertige Vorlage vor**, die du nur noch
|
||
committen musst.
|
||
|
||
### 6.1 Alle Felder
|
||
|
||
| Feld | Typ | Vorgabe | Bedeutung |
|
||
|---|---|---|---|
|
||
| `runtime` | `auto` \| `systemd` \| `docker` | `auto` | Wie gestartet wird. `auto`: Dockerfile oder Compose vorhanden → `docker`, sonst `systemd`. |
|
||
| `build` | Zeichenkette | — | Shell-Befehl im Release-Verzeichnis. **Nur `systemd`** — bei `docker` baut das Dockerfile. |
|
||
| `start` | Zeichenkette | — | Startbefehl des Dienstes. **Nur `systemd`.** Muss `$PORT` binden. |
|
||
| `port` | Ganzzahl 1–65535 | `3000` | **Nur `docker`:** der Port, auf dem deine App **im Container** lauscht. VCH bildet den zugewiesenen Host-Port darauf ab. |
|
||
| `health` | Pfad | `/` | Pfad, der 2xx oder 3xx liefert, sobald die App bereit ist. |
|
||
| `env` | Liste von Zeichenketten | `[]` | Zusätzliche Variablennamen, die VCH ausliefern soll. Wird mit den Schlüsseln aus `.env.example` vereinigt. |
|
||
| `compose` | Pfad | — | Welche Compose-Datei gilt, wenn mehrere gefunden werden. |
|
||
|
||
Unbekannte Felder und Tippfehler in `runtime` brechen den Deploy mit einer benannten
|
||
Fehlermeldung ab — sie werden nicht still ignoriert.
|
||
|
||
### 6.2 Beispiele
|
||
|
||
Node mit eigenem Build (systemd):
|
||
|
||
```yaml
|
||
runtime: systemd
|
||
build: "npm ci && npm run build"
|
||
start: "npm start"
|
||
health: /
|
||
```
|
||
|
||
Python (systemd) — beachte `$PORT` im Startbefehl:
|
||
|
||
```yaml
|
||
runtime: systemd
|
||
build: "python3 -m venv .venv && .venv/bin/pip install -r requirements.txt"
|
||
start: ".venv/bin/gunicorn app:app -b 0.0.0.0:$PORT"
|
||
health: /
|
||
```
|
||
|
||
Statische Seite (systemd) — irgendetwas muss lauschen:
|
||
|
||
```yaml
|
||
runtime: systemd
|
||
build: "npm ci && npm run build"
|
||
start: "python3 -m http.server $PORT --directory dist"
|
||
```
|
||
|
||
Monorepo, Code in einem Unterordner:
|
||
|
||
```yaml
|
||
runtime: systemd
|
||
build: "cd backend && npm ci && npm run build"
|
||
start: "cd backend && npm start"
|
||
```
|
||
|
||
Dockerfile:
|
||
|
||
```yaml
|
||
runtime: docker
|
||
port: 3000 # der Port INNERHALB des Containers
|
||
health: /
|
||
```
|
||
|
||
Mehrere Compose-Dateien im Repository:
|
||
|
||
```yaml
|
||
runtime: docker
|
||
compose: docker/compose.yaml
|
||
```
|
||
|
||
### 6.3 Was ohne die Datei passiert
|
||
|
||
In dieser Reihenfolge:
|
||
|
||
1. Ein echtes `Dockerfile` gewinnt → `runtime: docker` mit dem Image.
|
||
2. Eine Compose-Datei, die `${VCH_PORT}` veröffentlicht → Compose-Deploy.
|
||
3. Eine Compose-Datei, die **nur Infrastruktur** startet (Postgres, Redis, … und keinen
|
||
App-Dienst), wird als Entwicklungs-Beiwerk erkannt und **nicht** deployt — VCH sucht
|
||
weiter nach der Sprache.
|
||
4. Eine Compose-Datei mit App-Dienst, die `${VCH_PORT}` **nicht** veröffentlicht → Abbruch
|
||
mit Vorlage (Abschnitt 13).
|
||
5. `package.json` im Stamm: `build`-Skript → Build, `start`-Skript bzw. `main` → Start.
|
||
Paketmanager wird am Lockfile erkannt (`pnpm`/`yarn`/`npm`).
|
||
6. Alles andere — Python, PHP/Laravel, Go, reines Frontend ohne `start`, Code nur in
|
||
Unterordnern — endet mit einer passenden Vorlage zum Committen.
|
||
|
||
## 7. Umgebungsvariablen: `.env.example` ist die Deklaration
|
||
|
||
VCH liest keine Umgebungsvariablen aus deinem Code. **Was in einer Beispieldatei steht,
|
||
existiert; alles andere nicht.**
|
||
|
||
- Erkannte Dateinamen: `.env.example`, `.env.sample`, `.env.template` sowie dieselben ohne
|
||
führenden Punkt (`env.example`, …). Der erste Fund je Schlüssel gewinnt.
|
||
- Gesucht wird **im ganzen Repository** (ohne `node_modules`, `.git`, `dist`, `build`,
|
||
`.next`, `out`, `vendor`, `coverage`, `.turbo`), Stammdateien zuerst, höchstens 25 Dateien.
|
||
- Jede gefundene Beispieldatei bestimmt ein Ziel: `backend/.env.example` → VCH schreibt die
|
||
Produktionswerte nach `backend/.env`. Für Monorepos ist das der Weg, mehrere `.env` zu
|
||
bekommen.
|
||
|
||
**Was den Deploy blockiert:** ein Schlüssel, der **keinen Wert nennt** (`KEY=`) und für den
|
||
noch kein Produktionswert hinterlegt ist. Nicht blockierend sind:
|
||
|
||
```dotenv
|
||
API_URL=https://beispiel.test # Vorgabewert vorhanden
|
||
OPTIONAL_TOKEN="" # ausdrücklich leer erlaubt
|
||
FEATURE_FLAG= # optional
|
||
```
|
||
|
||
Der Marker muss direkt hinter dem `#` stehen (`# optional`); Fließtext, in dem das Wort
|
||
vorkommt, zählt nicht. Die Vorgabewerte sind **Hinweise für den Menschen** — in Produktion
|
||
wird nichts davon verwendet.
|
||
|
||
Im Zweifel:
|
||
|
||
```bash
|
||
grep -RhoE "process\.env\.[A-Z_]+" src/ | sort -u
|
||
```
|
||
|
||
und jeden Treffer in die `.env.example` schreiben.
|
||
|
||
**Bei Docker:** nur Variablen mit dem Präfix `NEXT_PUBLIC_`, `VITE_` oder `PUBLIC_` gehen als
|
||
`--build-arg` in den Build (sie sind ohnehin im Auslieferungsstand sichtbar) — dafür brauchst
|
||
du passende `ARG`-Zeilen im Dockerfile. Alle übrigen Werte kommen erst zur Laufzeit über
|
||
`--env-file` und landen nie im Image.
|
||
|
||
## 8. Datenbank
|
||
|
||
Deklariert deine App Verbindungsvariablen, bietet VCH eine verwaltete Datenbank an und trägt
|
||
die Werte **automatisch** in genau die Schlüssel ein, die du deklariert hast. Du musst deine
|
||
Variablennamen also nicht ändern — VCH erkennt sie.
|
||
|
||
**Was du bekommst:** genau **eine** verwaltete Datenbank je Projekt — Postgres 16 oder MySQL 8
|
||
als eigener Container auf der PROD-Maschine, Host `127.0.0.1`, Port aus `15000–15999`, nicht
|
||
aus dem LAN erreichbar. Datenbank und Benutzer heißen `app`; der Mensch kann beim Anlegen unter
|
||
„Erweitert" eigene Werte wählen (Kleinbuchstaben, Ziffern, Unterstrich, Buchstabe vorne;
|
||
reservierte Namen und das Präfix `pg_` sind gesperrt) — **nur beim Anlegen**,
|
||
danach nicht mehr. Das Passwort wird erzeugt oder gewählt und beim Deploy in deine Schlüssel
|
||
eingetragen; deklarierst du keine erkennbaren Schlüssel, zeigt die Karte im Projektarbeitsplatz
|
||
die Verbindung samt Passwort zum Kopieren. Auf DEV gibt es diese Datenbank nicht (Wegweiser).
|
||
Migrationen macht deine App im `build` oder `start`. Backup: der Abzug liegt im Projekt-Backup,
|
||
vor dem Löschen zieht VCH einen Pflicht-Abzug. Solange die Datenbank läuft, werden DB-Zeilen im
|
||
Env-Editor gestrichen — dein eigener Wert hält nicht. Deklarierst du **zwei** Verbindungen,
|
||
landen beide auf dieser einen Datenbank: dann Compose (Wegweiser).
|
||
|
||
**Erkannt wird** (Groß-/Kleinschreibung egal):
|
||
|
||
- Verbindungszeichenketten: `DATABASE_URL`, `DB_URL`, jeder Name auf `_DSN`
|
||
- Einzelteile mit Familienwort (`DB`, `DATABASE`, `POSTGRES`, `MYSQL`) plus Rolle:
|
||
`..._HOST`, `..._PORT`, `..._USER`/`..._USERNAME`, `..._PASSWORD`/`..._PASS`,
|
||
`..._NAME`/`..._DATABASE`/`..._DB`
|
||
- Die üblichen nackten Namen: `PGHOST`, `PGPORT`, `PGUSER`, `PGPASSWORD`, `PGDATABASE`,
|
||
`MYSQL_HOST`, `MYSQL_USER`, `MYSQL_PASSWORD`, `MYSQL_DATABASE`, `POSTGRES_DB`
|
||
|
||
**Bewusst nicht erkannt** (und deshalb nie überschrieben):
|
||
|
||
- alles mit `ROOT` im Namen (`POSTGRES_ROOT_PASSWORD`, `MYSQL_ROOT_PASSWORD`) — das sind die
|
||
Zugangsdaten des Datenbankservers, nicht die deiner App
|
||
- `SENTRY_DSN` und verwandte Fehlermelder (`GLITCHTIP`, `BUGSNAG`, `ROLLBAR`, `AIRBRAKE`,
|
||
`RAYGUN`, `APPSIGNAL`) — ein DSN ist dort keine Datenbank
|
||
- `HISTORY_DB=/data/verlauf.sqlite` und Ähnliches: ein `..._DB` ohne Datenbank-Familienwort
|
||
im übrigen Namen gilt nicht als Verbindung
|
||
|
||
Die **Engine** (Postgres oder MySQL) leitet VCH aus deinen Schlüsselnamen und dem Schema in
|
||
`DATABASE_URL` ab; ist das mehrdeutig, entscheidest du im Projektarbeitsplatz.
|
||
|
||
**Bringt dein Compose seine eigene Datenbank mit**, startet VCH sie mit und bietet **keine**
|
||
zweite an. Achte dann selbst auf ein dauerhaftes Volume — ein Datenbankdienst ohne Volume
|
||
verliert seine Daten beim nächsten Neubau, und VCH warnt nur. Erkannt wird der Dienst **am
|
||
Image-Namen**: `postgres`, `postgresql`, `pgvector`, `postgis`, `timescaledb`, `mysql`,
|
||
`mariadb`, `percona`/`percona-server`. Ein anderes Image (eigener Build, fremder Name) gilt
|
||
nicht als Datenbank — dann bietet VCH eine verwaltete an, und der Deploy läuft blau-grün mit
|
||
einem zweiten Stapel.
|
||
<!-- Beleg: deployManifest.ts PG_IMAGE_RE/MYSQL_IMAGE_RE, deploy.ts skipBlueGreen -->
|
||
|
||
Eine Compose-Datei, die **nur** eine Datenbank startet, ist Entwicklungs-Beiwerk: sie läuft in
|
||
Produktion nicht mit. Nimm dort die verwaltete Datenbank.
|
||
|
||
## 9. Dauerhafte Daten: `VCH_DATA_DIR`
|
||
|
||
Jedes Release liegt in einem eigenen Verzeichnis und verschwindet wieder. Was überleben soll —
|
||
SQLite-Datei, Uploads, erzeugte Dateien — gehört in das Verzeichnis aus **`VCH_DATA_DIR`**.
|
||
|
||
```js
|
||
const datenVerzeichnis = process.env.VCH_DATA_DIR || './data';
|
||
```
|
||
|
||
VCH setzt die Variable bei allen Betriebsarten (bei Docker als Einhängepunkt `/data`). **Lies
|
||
sie zur Laufzeit**, schreibe den Pfad nirgends fest und trage sie nicht in `.env`-Dateien ein.
|
||
Ein Verzeichnis relativ zum Release (`./data`) ist beim nächsten Deploy weg.
|
||
Bei Compose gilt das nur im Container des `${VCH_PORT}`-Dienstes — in der Compose-Datei selbst
|
||
steht die Variable nicht zur Verfügung (Abschnitt 13, Regel 5).
|
||
|
||
Der Prüfling während des Audits (Abschnitt 10) bekommt diesen Speicher **nicht** — er läuft
|
||
gegen einen leeren. Das ist Absicht: zwei Prozesse dürfen nicht gleichzeitig auf dieselbe
|
||
SQLite-Datei schreiben.
|
||
|
||
## 10. Wie ein Deploy abläuft
|
||
|
||
```
|
||
Klonen → Bauen → Healthcheck (Prüfling) → Audit → Umschalten → Live
|
||
```
|
||
|
||
1. **Klonen.** VCH klont den Deploy-Branch auf die PROD-Maschine — genau den gepushten Stand.
|
||
2. **Torprüfung.** Fehlt ein blockierender Wert aus `.env.example` (Abschnitt 7), endet es hier,
|
||
bevor irgendetwas läuft.
|
||
3. **Bauen** nach `.vch/deploy.yaml` bzw. Autoerkennung.
|
||
4. **Healthcheck.** Der neue Stand startet als **Prüfling** auf einem freien Port aus dem
|
||
Bereich `14000–14999`, neben der weiterhin laufenden alten Fassung. VCH ruft dort deinen
|
||
`health`-Pfad auf: rund 30 Sekunden lang, alle 1,5 s ein Versuch, Erfolg beim ersten
|
||
2xx/3xx. Bis dahin muss die App antworten.
|
||
5. **Audit** (Abschnitt 11) gegen den deployten Commit.
|
||
6. **Umschalten.** Erst wenn das Audit besteht, wird der Prüfling zur laufenden Fassung auf
|
||
dem stabilen Port. **Ein durchgefallenes Audit heißt: der alte Stand bleibt live.**
|
||
|
||
Vor dem Umschalten legt VCH einen Datenschnappschuss an. Geht das Umschalten schief, werden
|
||
erst die Daten und dann der Code zurückgerollt. Frühere Releases lassen sich im
|
||
Projektarbeitsplatz per Rollback zurückholen.
|
||
|
||
**Auto-Deploy:** ist er eingeschaltet, löst jeder Push auf den konfigurierten Deploy-Branch
|
||
einen Deploy aus. Pushes auf andere Branches werden ignoriert.
|
||
|
||
- **Das Ergebnis jedes Deploys liegt in `.vch/deploy-report.md`** (nicht im Git — VCH trägt
|
||
die Datei wie den Audit-Bericht in `.git/info/exclude` ein). Er nennt Commit, Stufe,
|
||
Fehlermeldung, das Ende des Logs (Geheimnisse geschwärzt) und eine Einschätzung:
|
||
„probably fixable in this repository" heißt, die Ursache liegt vermutlich im Code — beheben,
|
||
committen, pushen. „platform error" heißt: **nicht** im Code suchen, sondern dem Nutzer sagen,
|
||
was dort steht. Nach einem erfolgreichen Deploy steht dort genau das; ein alter Fehlerbericht
|
||
bleibt nicht liegen. Nennt der Bericht einen anderen Commit als den, nach dem du gefragt
|
||
wirst, ist er noch nicht aktualisiert.
|
||
<!-- Beleg: services/deployReportFile.ts -->
|
||
|
||
## 11. Audit — was geprüft wird
|
||
|
||
Das Audit läuft nicht hier, sondern in einem eigenen Runner gegen einen **sauberen Checkout
|
||
des deployten Commits**. Nicht Gepushtes wird nicht geprüft — und ein `.gitignore`-Eintrag
|
||
macht eine Datei für das Audit unsichtbar.
|
||
|
||
| Prüfung | Schwere | Darf entfallen |
|
||
|---|---|---|
|
||
| `readme` | Fehler | nein |
|
||
| `secrets` | Fehler | nein |
|
||
| `description` | Warnung | nein |
|
||
| `lint` | Fehler | ja |
|
||
| `typescript` | Fehler | ja |
|
||
| `npm-audit` | Fehler | ja |
|
||
|
||
- Eine **Warnung** blockiert nie. `description` hält dich also nicht auf, kostet dich aber den
|
||
Platz im Showcase.
|
||
- „Darf entfallen" heißt: die Prüfung meldet `skipped`, wenn sie auf dein Projekt nicht passt
|
||
(kein TypeScript, kein npm). Eine Prüfung, die **nicht** entfallen darf, aber trotzdem
|
||
`skipped` meldet, gilt als **durchgefallen** — sie ist nie gelaufen.
|
||
- `secrets` sucht eingecheckte Zugangsdaten. Echte Werte gehören nie ins Repository: in die
|
||
`.env.example` nur Beispiele, die echten Werte in den Env-Editor von VCH.
|
||
- **Das Ergebnis liegt nach jedem Audit in `.vch/audit-report.md`** (nicht im Git — VCH trägt
|
||
die Datei in `.git/info/exclude` ein). Bei einem Fehlschlag: lesen, beheben, committen,
|
||
pushen — erst der nächste Deploy auditiert den neuen Commit. Der Kopf nennt Commit und
|
||
Zeitpunkt; lief die Instanz beim Audit nicht, ist die Datei älter oder fehlt, dann steht der
|
||
aktuelle Bericht im Audit-Fenster von VCH zum Kopieren.
|
||
|
||
## 12. Veröffentlichen
|
||
|
||
- **Intern erreichbar** ist dein Projekt, sobald ein Deploy läuft — ohne Audit, ohne weiteren
|
||
Schritt, unter `http://<prod-ip>:<port>`.
|
||
- **Öffentlich** unter einer Subdomain mit SSL wird es erst mit laufendem Deploy **und**
|
||
bestandenem Audit für genau diesen Deploy. Die Subdomain kommt oben drauf; entfernst du sie
|
||
wieder, bleibt das Projekt intern erreichbar.
|
||
- Im **Showcase** erscheinen veröffentlichte Projekte mit bestandenem Audit und einer
|
||
eingecheckten `.vch-description`.
|
||
|
||
## 13. Compose — die fünf Regeln, an denen es sonst scheitert
|
||
|
||
1. **Der App-Dienst muss seinen Port als `ports: ["${VCH_PORT}:<intern>"]` veröffentlichen.**
|
||
VCH setzt `VCH_PORT` und kann so einen Prüfling neben deiner laufenden Fassung starten.
|
||
Ohne diese Zeile bricht der Deploy ab.
|
||
2. **VCH führt genau EINE Compose-Datei aus — keine `-f`-Ketten.** Gesucht wird
|
||
`compose.yaml`/`compose.yml`/`docker-compose.yaml`/`docker-compose.yml` im Stamm **oder in
|
||
einem Unterordner**. Eine Override-Datei (`docker-compose.ports.yml`, `*.override.yml`)
|
||
wird **ignoriert** — die `${VCH_PORT}`-Zeile gehört in die Hauptdatei. Bei mehreren
|
||
Fundstellen: `compose: <pfad>` in die `.vch/deploy.yaml`.
|
||
3. **Jeder Dienst, der Produktionswerte braucht, bekommt `env_file: [.env]`.** VCH schreibt
|
||
deine Werte in eine `.env` neben der Compose-Datei.
|
||
4. **Der ganze Stapel startet.** Datenbanken, Caches und Ähnliches aus deinem Compose laufen
|
||
automatisch mit — eine zusätzliche verwaltete Datenbank brauchst du dafür nicht. Dienste
|
||
hinter einem `profiles:`-Schlüssel bleiben aus.
|
||
5. **Gebaute Dienste bekommen kein `image:`, dauerhafte Daten liegen unter `/data` des
|
||
`${VCH_PORT}`-Dienstes.** Für jeden Dienst mit `build:` vergibt VCH den Image-Namen selbst,
|
||
je Commit — so starten Prüfling, Live-Stand und Rollback jeweils genau ihren Code. Ein
|
||
eigenes `image:` bei einem gebauten Dienst teilt sich ein Image über alle Releases; ein
|
||
Rollback startet dann den zuletzt gebauten Code. `VCH_DATA_DIR` gibt es **nur im Container**
|
||
des `${VCH_PORT}`-Dienstes (als `/data`), nicht beim Einlesen der Compose-Datei:
|
||
`${VCH_DATA_DIR:-.}/data` wird dort zu `./data` im Release-Ordner und ist beim nächsten
|
||
Deploy weg. Ein zweiter Dienst, der dieselben Daten braucht, bekommt `volumes_from: [<app>]`;
|
||
eigene Daten legt er in ein benanntes Volume (`volumes: [daten:/var/lib/…]`) — das überlebt
|
||
Deploys, sichert VCH aber nicht mit. `vch-images.yml` wird bei jedem Build neu
|
||
geschrieben oder entfernt, `vch-override.yml` bei jedem Start des Live-Stands geschrieben
|
||
(sofern es einen `${VCH_PORT}`-Dienst gibt) — beide gehören VCH, keine eigenen Dateien dieses
|
||
Namens.
|
||
|
||
<!-- Beleg: dockerCommands.ts composeImageRef/renderComposeImages, runtimeOps.ts composeStableUpCmds (#291); deployManifest.ts classifyServiceDataMount -->
|