VCH-Plattformregeln einbinden
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
278d47c793
commit
8ed1396b1c
2 files changed
+469
-78
No files matched your search
@@ -0,0 +1,447 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
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 vier 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.
|
||||||
@@ -1,85 +1,29 @@
|
|||||||
# Project
|
@.vch/PLATFORM.md
|
||||||
|
|
||||||
## Environment
|
<!--
|
||||||
|
Die Regeln dieser Umgebung — Deployment, Datenbank, Audit, Veröffentlichen — stehen in
|
||||||
|
.vch/PLATFORM.md. Diese Datei wird von VCH gepflegt und überschrieben; lies sie, bevor du
|
||||||
|
deployst. Alles ab hier gehört dem Projekt: VCH fasst diese CLAUDE.md nie wieder an.
|
||||||
|
-->
|
||||||
|
|
||||||
You are working inside a VCH cloud development environment.
|
# FuerDieCuts
|
||||||
|
|
||||||
- **OS:** Debian-based LXC container
|
Wofür dieses Projekt gut ist, und was jemand wissen muss, der hier weiterarbeitet.
|
||||||
- **Editor:** code-server (VS Code in browser) on port 8080
|
|
||||||
- **CLI:** Claude Code is available as `claude` in terminal
|
|
||||||
- **Git:** Pre-configured. Gitea integration available through VCH.
|
|
||||||
- **Preview:** Start a dev server on any port — VCH detects listening ports automatically and provides preview URLs with SSL.
|
|
||||||
- **Publishing:** Assign a public subdomain to any port via the VCH Publish page (automatic SSL).
|
|
||||||
- **User:** `vchuser` with home at `/home/vchuser`
|
|
||||||
|
|
||||||
## Preview Access (IMPORTANT)
|
## Befehle
|
||||||
|
|
||||||
Apps are accessed through a **reverse proxy**, not directly. There are two modes:
|
- **Abhängigkeiten installieren:** `npm install`
|
||||||
|
- **Entwicklungsserver:** `npm run dev`
|
||||||
|
- **Tests:** `npm test`
|
||||||
|
- **Bauen:** `npm run build`
|
||||||
|
- **Linten:** `npm run lint`
|
||||||
|
|
||||||
1. **Subdomain proxy** (preferred): `https://lxc{VMID}-{PORT}.dev.example.com/` — your app is at the domain root, everything works normally.
|
## Konventionen
|
||||||
2. **Path-based proxy** (fallback): `https://host/proxy/{INSTANCE}/p/{PORT}/` — your app is behind a path prefix.
|
|
||||||
|
|
||||||
### Rules for portable apps that work in both modes:
|
- Aussagekräftige Commit-Nachrichten im Imperativ („Suche ergänzen", nicht „Suche ergänzt")
|
||||||
|
- Lieber kleine, abgeschlossene Commits als große
|
||||||
- **ALWAYS use relative paths** for assets, API calls, and links:
|
- Vor dem Commit linten und formatieren
|
||||||
- `css/style.css` ✅ — NOT `/css/style.css` ❌
|
- Tests für die tragende Fachlogik
|
||||||
- `api/users` ✅ — NOT `/api/users` ❌
|
- Wenig Abhängigkeiten — was Browser und Node schon können, reicht meistens
|
||||||
- `fetch('api/data')` ✅ — NOT `fetch('/api/data')` ❌
|
- Semantisches HTML, mobil zuerst, bedienbar per Tastatur, ausreichender Kontrast
|
||||||
- **Static file serving:** Configure your server to serve from the request path, not the filesystem root.
|
- Konfiguration über Umgebungsvariablen; keine Zugangsdaten im Quelltext
|
||||||
- **`<base>` tag:** If you must use absolute paths, add `<base href="./">` in `<head>`.
|
|
||||||
|
|
||||||
### Framework-specific configuration:
|
|
||||||
|
|
||||||
- **Express:** `app.use(express.static('public'))` works — just ensure HTML references are relative.
|
|
||||||
- **Vite:** Set `base: './'` in `vite.config.ts`.
|
|
||||||
- **Next.js:** Set `basePath` in `next.config.js` if using path-based proxy, or leave default for subdomain proxy.
|
|
||||||
- **Create React App:** Set `"homepage": "."` in `package.json`.
|
|
||||||
|
|
||||||
## Development Conventions
|
|
||||||
|
|
||||||
- Write clear, descriptive commit messages (imperative mood: "Add feature", not "Added feature")
|
|
||||||
- Prefer small, focused commits over large ones
|
|
||||||
- Run linters and formatters before committing
|
|
||||||
- Write tests for critical business logic
|
|
||||||
- Keep dependencies minimal — use native browser/Node APIs where possible
|
|
||||||
|
|
||||||
## README Requirement (MANDATORY)
|
|
||||||
|
|
||||||
Every project MUST have a meaningful `README.md` in the project root. This is enforced by the VCH audit system — projects without a proper README cannot be published.
|
|
||||||
|
|
||||||
Your README must include at minimum:
|
|
||||||
- **Project description** — what the project does (not just the template placeholder)
|
|
||||||
- **Installation instructions** — how to install dependencies
|
|
||||||
- **Development instructions** — how to start the dev server
|
|
||||||
- **Tech stack** — what technologies are used
|
|
||||||
|
|
||||||
Update the README whenever you add features, change setup steps, or modify the tech stack. The audit will reject READMEs that are still just the default template.
|
|
||||||
|
|
||||||
## Project Description (MANDATORY)
|
|
||||||
|
|
||||||
Every project has a `.vch-description` file in the project root. Keep this file up to date with a clear, non-technical description of your project:
|
|
||||||
- What the project does
|
|
||||||
- What problem it solves or what it's used for
|
|
||||||
- Who it's for
|
|
||||||
|
|
||||||
Do NOT include technical details (tech stack, dependencies, setup instructions) — those belong in the README. The `.vch-description` content is shown in the VCH Showcase and is synced automatically when an audit runs.
|
|
||||||
|
|
||||||
Update `.vch-description` whenever the project's purpose or scope changes significantly.
|
|
||||||
|
|
||||||
## Web Best Practices
|
|
||||||
|
|
||||||
- Use semantic HTML elements (`nav`, `main`, `article`, `section`, etc.)
|
|
||||||
- Mobile-first responsive design
|
|
||||||
- Follow accessibility guidelines (ARIA labels, keyboard navigation, color contrast)
|
|
||||||
- Optimize images and assets for performance
|
|
||||||
- Use environment variables for configuration — never hardcode secrets
|
|
||||||
|
|
||||||
## Commands
|
|
||||||
|
|
||||||
Fill in project-specific commands below:
|
|
||||||
|
|
||||||
- **Install dependencies:** `npm install`
|
|
||||||
- **Start dev server:** `npm run dev`
|
|
||||||
- **Run tests:** `npm test`
|
|
||||||
- **Build for production:** `npm run build`
|
|
||||||
- **Lint:** `npm run lint`
|
|
||||||
Reference in new issue
Block a user