"deploy" klang nach Ausrollen, drin steht das Gegenteil: das Bau-Rezept und das Compose, das aus dem Quelltext baut. Ausgerollt wird mit docker-compose.example.yml im Wurzelverzeichnis. Marc ist genau darüber gestolpert. Zwei Dinge mussten dafür vorher fest werden, sonst hätte das Umbenennen die Entwicklungsdatenbank gekostet: Compose leitet den Projektnamen aus dem Ordnernamen ab und hätte unter "build" ein neues, leeres Projekt mit neuem, leerem Volume angelegt. Der Projektname steht deshalb jetzt im Compose (name: aether), und das Volume trägt seinen alten Namen ausdrücklich weiter (deploy_postgres_data) – unschön, aber die Daten bleiben. Ebenfalls angepasst: .gitignore. Dort stand deploy/.env; nach dem Umbenennen wäre build/.env nur noch von der allgemeinen .env-Zeile gedeckt gewesen. Nachgeprüft nach dem Umzug: Abbild baut, Stapel startet, 113 Dateien in der Datenbank wie vorher, Volume unverändert deploy_postgres_data, GET / → 200. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
167 lines
7.9 KiB
Markdown
167 lines
7.9 KiB
Markdown
# Aether – Projektkonventionen für Claude Code
|
||
|
||
## Projekt-Überblick
|
||
|
||
Self-hosted Cloud-Alternative zu Nextcloud. Go-Backend + React-Frontend, modularer Monolith.
|
||
Projektplan: `cloud-projekt-plan.md`
|
||
|
||
## Monorepo-Struktur
|
||
|
||
```
|
||
aether/
|
||
├── backend/ # Go – modularer Monolith
|
||
│ ├── cmd/aether/ # main.go – Einstiegspunkt
|
||
│ ├── internal/ # Domänen-Pakete (nie von außen importieren)
|
||
│ │ ├── auth/ # Login, Sessions, CSRF, Argon2id, OIDC-User-Anlage
|
||
│ │ ├── storage/ # Storage-Interface + LocalFS (atomar, SHA-256)
|
||
│ │ ├── files/ # Datei-/Ordner-CRUD, Trash, Versionen, Favoriten
|
||
│ │ ├── sharing/ # Öffentliche Share-Links
|
||
│ │ ├── admin/ # Admin-Panel: User, Gruppen(-mitgliedschaft), Settings/Theming
|
||
│ │ ├── oidc/ # OIDC-Login (optional, via .env)
|
||
│ │ ├── avatar/ # Profilbild: zuschneiden, ablegen, ausliefern
|
||
│ │ ├── webdav/ # WebDAV-Endpoint /dav/ (Basic Auth – nur App-Passwörter)
|
||
│ │ ├── acl/ # Interne Freigaben an User/Gruppen (Rechteauflösung)
|
||
│ │ ├── audit/ # Audit-Log (Schreibpfad; Endpunkte liegen in admin/)
|
||
│ │ ├── gc/ # Blob-GC: Platte gegen DB abgleichen
|
||
│ │ ├── thumbnails/ # Vorschaubild-Generierung
|
||
│ │ ├── groups/ # LEERER STUB – Gruppenordner liegen in files/groupfolder.go
|
||
│ │ ├── documents/ # Volltext: OCR-Warteschlange + Arbeiter (Phase 6, Teil 1)
|
||
│ │ ├── tags/ # Ordnungsmerkmale einer Datei: Etiketten, Belegdatum, Absender
|
||
│ │ └── office/ # WOPI-Host: Collabora/OnlyOffice, ein Adapter fuer beide
|
||
│ ├── db/migrations/ # SQL-Migrations – Spiegelkopie (siehe Migrations-Regeln!)
|
||
│ └── pkg/ # Hilfspakete: config, middleware, response, db (embed migrations)
|
||
├── frontend/ # React + TS + Vite + Tailwind
|
||
│ └── src/
|
||
│ ├── components/
|
||
│ ├── pages/
|
||
│ ├── api/ # Typed API-Client
|
||
│ └── hooks/
|
||
├── build/ # Dockerfile + Compose zum Bauen aus dem Quelltext
|
||
└── CLAUDE.md
|
||
```
|
||
|
||
## Backend-Konventionen (Go)
|
||
|
||
- **Module-Pfad**: `aether`
|
||
- **Go-Version**: 1.25 (siehe `go.mod`)
|
||
- **Router**: `github.com/go-chi/chi/v5`
|
||
- **DB-Treiber**: `github.com/jackc/pgx/v5` (pgxpool für Connection-Pool)
|
||
- **Migrations**: `github.com/golang-migrate/migrate/v4`
|
||
- **Logging**: `github.com/rs/zerolog` – strukturiert, immer mit Kontext (`log.Ctx(ctx)`)
|
||
- **Hashing**: `golang.org/x/crypto/argon2` (Argon2id) – App-Passwörter dagegen SHA-256 (zufälliges Token, jeder WebDAV-Request authentifiziert einzeln)
|
||
- **Sessions**: `github.com/gorilla/sessions` mit securecookie
|
||
|
||
### Paket-Regeln
|
||
|
||
- Alle Domänen-Pakete unter `internal/` – kein Import von außen
|
||
- Keine zirkulären Abhängigkeiten; Dependency-Richtung: `cmd → internal/* → pkg/*`
|
||
- Handler in `internal/<domain>/handler.go`, Business-Logic in `internal/<domain>/service.go`, DB in `internal/<domain>/store.go`
|
||
- Interfaces für alle externen Abhängigkeiten (Storage, DB) → leicht mockbar
|
||
|
||
### Code-Stil
|
||
|
||
- `gofmt` + `goimports` – immer formatiert
|
||
- Fehler nie ignorieren (`_`), immer wrappen mit `fmt.Errorf("context: %w", err)`
|
||
- Keine Magic-Numbers – Konstanten mit sprechenden Namen
|
||
- HTTP-Handler signieren: `func (h *Handler) MethodName(w http.ResponseWriter, r *http.Request)`
|
||
- Middleware-Chain: Auth → CSRF → RateLimit → Handler
|
||
|
||
### Tests
|
||
|
||
- Testdatei neben der Quelldatei: `auth_test.go` neben `auth.go`
|
||
- Integrationstests mit echtem Postgres via `testcontainers-go`
|
||
- Pflicht-Tests: Path-Traversal, Upload-Abbruch, Checksum-Verifikation, Soft-Delete-Recovery
|
||
- `go test ./...` muss grün sein vor jedem Commit
|
||
|
||
## Frontend-Konventionen (React + TS)
|
||
|
||
- **Bundler**: Vite
|
||
- **Styling**: Tailwind CSS + CSS-Variablen für Design-Tokens
|
||
- **Testing**: Vitest + Testing Library; E2E: Playwright
|
||
- **API-Client**: typed fetch-Wrapper unter `src/api/`
|
||
|
||
### Design-Tokens (CSS-Variablen)
|
||
|
||
Alle Farben laufen über CSS-Custom-Properties:
|
||
|
||
```css
|
||
:root {
|
||
--color-primary: ...;
|
||
--color-accent: ...;
|
||
--color-background: ...;
|
||
--color-surface: ...;
|
||
--color-text: ...;
|
||
}
|
||
```
|
||
|
||
Admin setzt globale Tokens; User-Override nur für eigene Palette (Scope: `[data-user-theme]`).
|
||
|
||
### Komponenten-Regeln
|
||
|
||
- Komponenten in `PascalCase.tsx`
|
||
- Kein `dangerouslySetInnerHTML` mit User-Input (XSS)
|
||
- API-Calls nur über `src/api/`-Funktionen, nie direkt `fetch` in Komponenten
|
||
|
||
## Sicherheits-Pflichten
|
||
|
||
- Path-Traversal: jeden Dateipfad kanonisieren und gegen Storage-Root prüfen
|
||
- CSRF: Double-Submit-Cookie oder synchronizer-Token
|
||
- Rate-Limiting: Login-Endpunkt + Admin-Login strenger
|
||
- Auth-Check: jede Route serverseitig prüfen – nie Frontend vertrauen
|
||
- SQL: ausschließlich parametrisierte Queries (pgx unterstützt das nativ)
|
||
- Uploads: außerhalb Webroot, keine ausführbaren Rechte, MIME-Prüfung
|
||
- WebDAV: **nur App-Passwörter**, nie das Kontopasswort – es ist zugleich der Admin-Zugang
|
||
- Kein Zugriff auf fremde IDs → `404`, nicht `403`: sonst verrät die Antwort die Existenz
|
||
- Listen und Suche schneiden den erreichbaren Bereich **in der Abfrage** zu, nie nachträglich im Handler
|
||
- Hochgeladene Dateien nur für eine Positivliste harmloser Typen `inline` ausliefern (sonst läuft fremdes HTML im eigenen Ursprung)
|
||
|
||
## Datenintegrität (nicht verhandelbar)
|
||
|
||
1. **Atomares Schreiben**: Temp-Datei → `fsync` → `rename`. Niemals in-place.
|
||
2. **SHA-256**: beim Upload berechnen + in DB; beim Download verifizieren.
|
||
3. **Soft-Delete only**: kein hartes Löschen. Bytes erst per GC entfernen.
|
||
4. **DB-Commit nach Blob-Write**: Metadaten erst committen, wenn Blob erfolgreich.
|
||
|
||
## Migrations-Regeln
|
||
|
||
- Dateien: `000001_init.up.sql` / `000001_init.down.sql`
|
||
- **Jede Migration in BEIDE Verzeichnisse legen:** `backend/pkg/db/migrations/` (wird ins Binary embedded und beim Start ausgeführt) UND `backend/db/migrations/` (Spiegelkopie). Beide müssen identisch sein.
|
||
- Keine destruktiven Änderungen ohne explizites Down-Migration
|
||
- Migration beim App-Start automatisch ausführen (via `migrate.Up()`)
|
||
- Aktueller Stand: bis `000019_user_avatar` (Profilbild eines Kontos)
|
||
|
||
## Commit-Regeln
|
||
|
||
Format: `<typ>(<scope>): <kurze Beschreibung>`
|
||
|
||
Typen: `feat`, `fix`, `test`, `refactor`, `docs`, `chore`, `security`
|
||
|
||
Beispiele:
|
||
- `feat(auth): add argon2id password hashing`
|
||
- `test(storage): add checksum verification test`
|
||
- `security(admin): enforce server-side admin check on all routes`
|
||
|
||
Kein Commit ohne grüne Tests.
|
||
|
||
## Reverse Proxy & Proxy-Awareness
|
||
|
||
- **Kein Proxy-Container** in docker-compose.yml – TLS und Domain gehören zum Wirt,
|
||
nicht ins Projekt. Nichts hier soll einen mitgelieferten Proxy voraussetzen.
|
||
- App lauscht auf reinem HTTP (`APP_LISTEN_ADDR`). TLS = externer Proxy.
|
||
- `X-Forwarded-For` / `X-Forwarded-Proto` werden **nur** von `TRUSTED_PROXIES` (IPs/CIDRs) akzeptiert.
|
||
- Echte Client-IP immer über `middleware.RealIP(r)` abrufen – niemals `r.RemoteAddr` direkt.
|
||
- `Secure`-Cookie-Flag: über `middleware.IsHTTPS(r)` setzen (berücksichtigt Forwarded-Proto).
|
||
- `PUBLIC_URL` für OIDC-Redirects, WOPI-Callbacks und Share-Links verwenden.
|
||
- HSTS gehört auf den Edge-Proxy, **nicht** in die App-Header.
|
||
|
||
## Docker & Secrets
|
||
|
||
- **Backend immer via Docker starten/laufen lassen (`docker compose -f build/docker-compose.yml ...`), nie `go run`.** Der Host hat kein Go-Toolchain installiert; Builds/Compile-Checks laufen über das `golang:1.25-alpine`-Image. Migrations + DB-Verbindung sind auf die Compose-Umgebung ausgelegt.
|
||
- Secrets **nur** über `.env` / Compose-Env – nie im Image oder Repo
|
||
- Multi-Stage-Build: Builder-Stage (Go-Compiler) → Alpine final
|
||
- `.env` ist in `.gitignore`; `.env.example` zeigt alle Keys ohne Werte
|
||
|
||
## Phasen-Stopp
|
||
|
||
Nach Phase 1: **STOPP** – Zusammenfassung + Phase-2-Vorschlag, erst nach OK weitermachen.
|