"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>
7.9 KiB
7.9 KiB
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/sessionsmit 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 ininternal/<domain>/service.go, DB ininternal/<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 mitfmt.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.gonebenauth.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:
: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
dangerouslySetInnerHTMLmit User-Input (XSS) - API-Calls nur über
src/api/-Funktionen, nie direktfetchin 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, nicht403: 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
inlineausliefern (sonst läuft fremdes HTML im eigenen Ursprung)
Datenintegrität (nicht verhandelbar)
- Atomares Schreiben: Temp-Datei →
fsync→rename. Niemals in-place. - SHA-256: beim Upload berechnen + in DB; beim Download verifizieren.
- Soft-Delete only: kein hartes Löschen. Bytes erst per GC entfernen.
- 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) UNDbackend/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 hashingtest(storage): add checksum verification testsecurity(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-Protowerden nur vonTRUSTED_PROXIES(IPs/CIDRs) akzeptiert.- Echte Client-IP immer über
middleware.RealIP(r)abrufen – niemalsr.RemoteAddrdirekt. Secure-Cookie-Flag: übermiddleware.IsHTTPS(r)setzen (berücksichtigt Forwarded-Proto).PUBLIC_URLfü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 ...), niego run. Der Host hat kein Go-Toolchain installiert; Builds/Compile-Checks laufen über dasgolang: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
.envist in.gitignore;.env.examplezeigt alle Keys ohne Werte
Phasen-Stopp
Nach Phase 1: STOPP – Zusammenfassung + Phase-2-Vorschlag, erst nach OK weitermachen.