mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-17 18:49:39 +02:00
Feat: New project chat, uload refactor (postgress), hosting plans, uload landingpage
This commit is contained in:
parent
559eb08d8c
commit
fcf3a344b1
123 changed files with 7106 additions and 3715 deletions
946
docs/BACKEND_ARCHITECTURE.md
Normal file
946
docs/BACKEND_ARCHITECTURE.md
Normal file
|
|
@ -0,0 +1,946 @@
|
|||
# Backend-Architektur im Manacore Monorepo
|
||||
|
||||
Diese Dokumentation beschreibt die Backend-Implementierungen aller Projekte im Manacore Monorepo.
|
||||
|
||||
## Übersicht
|
||||
|
||||
Das Monorepo enthält 6 Hauptprojekte mit unterschiedlichen Backend-Architekturen:
|
||||
|
||||
| Projekt | Backend-Typ | Datenbank | Status |
|
||||
|---------|-------------|-----------|--------|
|
||||
| **Maerchenzauber** | NestJS v10 | Supabase (PostgreSQL) | Aktiv |
|
||||
| **Manadeck** | NestJS v11 | PostgreSQL + Drizzle ORM | Aktiv |
|
||||
| **Uload** | NestJS v11 | PostgreSQL + Drizzle ORM | Aktiv |
|
||||
| **Picture** | Kein Backend | - | Frontend-only |
|
||||
| **Memoro** | Kein Backend | - | Frontend-only |
|
||||
| **Manacore** | Kein Backend (extern) | - | Externes Backend |
|
||||
|
||||
---
|
||||
|
||||
## 1. Maerchenzauber
|
||||
|
||||
**Pfad:** `/maerchenzauber/apps/backend`
|
||||
|
||||
**Zweck:** KI-gestützte Kindergeschichten-Generierung mit benutzerdefinierten Charakteren.
|
||||
|
||||
### Technologie-Stack
|
||||
|
||||
- **Framework:** NestJS 10.0.0
|
||||
- **Datenbank:** Supabase (PostgreSQL)
|
||||
- **ORM:** `@supabase/supabase-js` v2.81.1
|
||||
- **AI-Services:** Azure OpenAI, Google Gemini, Replicate
|
||||
|
||||
### Architektur
|
||||
|
||||
```
|
||||
apps/backend/
|
||||
├── src/
|
||||
│ ├── character/ # Charakter-Modul
|
||||
│ │ ├── character.controller.ts
|
||||
│ │ ├── character.service.ts
|
||||
│ │ └── character.repository.ts
|
||||
│ ├── story/ # Story-Modul
|
||||
│ │ ├── story.controller.ts
|
||||
│ │ ├── story.service.ts
|
||||
│ │ └── pipelines/ # Story-Generierung-Pipelines
|
||||
│ ├── core/ # Kern-Services
|
||||
│ │ └── services/
|
||||
│ │ └── prompting.service.ts
|
||||
│ ├── settings/ # Benutzereinstellungen
|
||||
│ ├── health/ # Health-Checks
|
||||
│ └── feedback/ # Feedback-Modul
|
||||
```
|
||||
|
||||
### Datenbank-Schema
|
||||
|
||||
**Tabellen:**
|
||||
- `characters` - Benutzercharaktere
|
||||
- `stories` - Generierte Geschichten
|
||||
- `story_collections` - Sammlungen von Geschichten
|
||||
- `user_settings` - Benutzereinstellungen
|
||||
|
||||
**Sicherheit:** Row-Level Security (RLS) für Datenzugriffskontrolle
|
||||
|
||||
### Authentifizierung
|
||||
|
||||
Mana Core Integration via `@mana-core/nestjs-integration`:
|
||||
|
||||
```typescript
|
||||
// Beispiel: Geschützter Endpoint
|
||||
@UseGuards(AuthGuard)
|
||||
@Get('characters')
|
||||
async getCharacters(@CurrentUser() user: User) {
|
||||
return this.characterService.findByUser(user.id);
|
||||
}
|
||||
```
|
||||
|
||||
**Auth-Endpoint:** `https://mana-core-middleware-111768794939.europe-west3.run.app`
|
||||
|
||||
### AI-Services
|
||||
|
||||
| Service | Verwendung | API |
|
||||
|---------|------------|-----|
|
||||
| Azure OpenAI (GPT-4) | Story-Generierung | `MAERCHENZAUBER_AZURE_OPENAI_ENDPOINT` |
|
||||
| Google Gemini | Charakter-Generierung | `GOOGLE_GEMINI_API_KEY` |
|
||||
| Replicate (Flux) | Bildgenerierung | `REPLICATE_API_TOKEN` |
|
||||
|
||||
### File Storage
|
||||
|
||||
- **Provider:** Supabase Storage
|
||||
- **Bucket:** `maerchenzauber`
|
||||
- **Verwendung:** Charakter- und Story-Bilder
|
||||
|
||||
### Deployment
|
||||
|
||||
- **Plattform:** Google Cloud Run
|
||||
- **Region:** europe-west3
|
||||
- **URL:** `https://storyteller-backend-111768794939.europe-west3.run.app`
|
||||
- **Port:** 3002 (Development)
|
||||
|
||||
---
|
||||
|
||||
## 2. Manadeck
|
||||
|
||||
**Pfad:** `/manadeck/apps/backend`
|
||||
|
||||
**Zweck:** KI-gestützte Lernkarten-Generierung (Flashcards, Quizzes, Mixed).
|
||||
|
||||
### Technologie-Stack
|
||||
|
||||
- **Framework:** NestJS 11.0.1
|
||||
- **Datenbank:** PostgreSQL 16
|
||||
- **ORM:** Drizzle ORM
|
||||
- **AI-Service:** Google Gemini API
|
||||
|
||||
### Architektur
|
||||
|
||||
```
|
||||
apps/backend/
|
||||
├── src/
|
||||
│ ├── api.controller.ts # Haupt-API-Endpoints
|
||||
│ ├── public.controller.ts # Öffentliche Endpoints
|
||||
│ ├── health.controller.ts # Health-Checks
|
||||
│ ├── ai.service.ts # AI-Generierung
|
||||
│ └── repositories/
|
||||
│ ├── deck.repository.ts
|
||||
│ ├── card.repository.ts
|
||||
│ ├── user-stats.repository.ts
|
||||
│ └── deck-template.repository.ts
|
||||
```
|
||||
|
||||
### Datenbank-Package
|
||||
|
||||
Das Datenbank-Schema ist in einem separaten Package ausgelagert:
|
||||
|
||||
**Pfad:** `/packages/manadeck-database`
|
||||
|
||||
```typescript
|
||||
// Verwendung im Backend
|
||||
import { db, schema } from '@manacore/manadeck-database';
|
||||
|
||||
const decks = await db.query.decks.findMany({
|
||||
where: eq(schema.decks.userId, userId)
|
||||
});
|
||||
```
|
||||
|
||||
**Drizzle-Konfiguration:**
|
||||
```typescript
|
||||
// drizzle.config.ts
|
||||
export default {
|
||||
schema: './src/schema/*',
|
||||
out: './migrations',
|
||||
driver: 'pg',
|
||||
dbCredentials: {
|
||||
connectionString: process.env.DATABASE_URL
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Authentifizierung
|
||||
|
||||
```typescript
|
||||
import { AuthGuard, CurrentUser } from '@mana-core/nestjs-integration';
|
||||
|
||||
@Controller('api')
|
||||
@UseGuards(AuthGuard)
|
||||
export class ApiController {
|
||||
@Post('decks')
|
||||
async createDeck(@CurrentUser() user: User, @Body() dto: CreateDeckDto) {
|
||||
// Credit-Prüfung und Deck-Erstellung
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Credit-System
|
||||
|
||||
Integration mit Mana Core Credit Service:
|
||||
|
||||
```typescript
|
||||
import { CreditClientService } from '@mana-core/nestjs-integration';
|
||||
|
||||
@Injectable()
|
||||
export class AiService {
|
||||
constructor(private creditClient: CreditClientService) {}
|
||||
|
||||
async generateDeck(userId: string, input: GenerateInput) {
|
||||
// 1. Credit-Balance prüfen
|
||||
const hasCredits = await this.creditClient.checkBalance(userId, 'DECK_CREATION');
|
||||
|
||||
// 2. Deck generieren
|
||||
const deck = await this.generateWithGemini(input);
|
||||
|
||||
// 3. Credits abziehen
|
||||
await this.creditClient.deduct(userId, 'DECK_CREATION');
|
||||
|
||||
return deck;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### AI-Generierung
|
||||
|
||||
**Unterstützte Kartentypen:**
|
||||
- `text` - Textbasierte Karten
|
||||
- `flashcard` - Klassische Lernkarten
|
||||
- `quiz` - Multiple-Choice Quiz
|
||||
- `mixed` - Gemischte Inhalte
|
||||
|
||||
**Schwierigkeitsgrade:**
|
||||
- `beginner`
|
||||
- `intermediate`
|
||||
- `advanced`
|
||||
|
||||
### Docker-Setup
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml (Lokale Entwicklung)
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16
|
||||
ports:
|
||||
- "5433:5432"
|
||||
environment:
|
||||
POSTGRES_DB: manadeck
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
|
||||
pgadmin:
|
||||
image: dpage/pgadmin4
|
||||
ports:
|
||||
- "5050:80"
|
||||
```
|
||||
|
||||
### Deployment
|
||||
|
||||
- **Docker Image:** Multi-stage Build (Node 18-alpine)
|
||||
- **Port:** 8080
|
||||
- **Health-Check:** `/health`
|
||||
|
||||
---
|
||||
|
||||
## 3. Uload
|
||||
|
||||
**Pfad:** `/uload/apps/backend`
|
||||
|
||||
**Zweck:** URL-Shortener mit Link-Analytics.
|
||||
|
||||
### Technologie-Stack
|
||||
|
||||
- **Framework:** NestJS 11.0.1
|
||||
- **Datenbank:** PostgreSQL 16
|
||||
- **ORM:** Drizzle ORM
|
||||
- **Cache:** Redis (optional)
|
||||
|
||||
### Architektur
|
||||
|
||||
```
|
||||
uload/apps/backend/
|
||||
├── src/
|
||||
│ ├── main.ts
|
||||
│ ├── app.module.ts
|
||||
│ ├── config/
|
||||
│ │ └── validation.schema.ts
|
||||
│ ├── controllers/
|
||||
│ │ ├── redirect.controller.ts # GET /:code (public redirect)
|
||||
│ │ ├── links.controller.ts # CRUD /api/links
|
||||
│ │ ├── analytics.controller.ts # GET /api/analytics
|
||||
│ │ └── health.controller.ts
|
||||
│ ├── services/
|
||||
│ │ ├── links.service.ts
|
||||
│ │ ├── redirect.service.ts
|
||||
│ │ └── analytics.service.ts
|
||||
│ └── database/
|
||||
│ ├── database.module.ts
|
||||
│ └── repositories/
|
||||
│ ├── link.repository.ts
|
||||
│ └── click.repository.ts
|
||||
├── Dockerfile
|
||||
└── package.json
|
||||
```
|
||||
|
||||
### Datenbank-Package
|
||||
|
||||
**Pfad:** `/packages/uload-database`
|
||||
|
||||
```typescript
|
||||
// Verwendung im Backend
|
||||
import { db, links, clicks, eq, desc } from '@manacore/uload-database';
|
||||
|
||||
const userLinks = await db.query.links.findMany({
|
||||
where: eq(links.userId, userId),
|
||||
orderBy: desc(links.createdAt)
|
||||
});
|
||||
```
|
||||
|
||||
### API Endpoints
|
||||
|
||||
| Endpoint | Method | Auth | Beschreibung |
|
||||
|----------|--------|------|--------------|
|
||||
| `/:code` | GET | Public | Redirect zu Original-URL |
|
||||
| `/api/links` | GET | Protected | Liste aller Links |
|
||||
| `/api/links` | POST | Protected | Link erstellen |
|
||||
| `/api/links/:id` | GET | Protected | Link Details |
|
||||
| `/api/links/:id` | PATCH | Protected | Link aktualisieren |
|
||||
| `/api/links/:id` | DELETE | Protected | Link löschen |
|
||||
| `/api/analytics/:linkId` | GET | Protected | Link-Statistiken |
|
||||
| `/health` | GET | Public | Health Check |
|
||||
|
||||
### Authentifizierung
|
||||
|
||||
Mana Core Integration via `@mana-core/nestjs-integration`:
|
||||
|
||||
```typescript
|
||||
import { AuthGuard, CurrentUser } from '@mana-core/nestjs-integration';
|
||||
|
||||
@Controller('api/links')
|
||||
@UseGuards(AuthGuard)
|
||||
export class LinksController {
|
||||
@Get()
|
||||
async getLinks(@CurrentUser() user: any) {
|
||||
return this.linksService.getLinks(user.sub);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Deployment
|
||||
|
||||
- **Docker Image:** Multi-stage Build (Node 20-alpine)
|
||||
- **Port:** 3003
|
||||
- **Health-Check:** `/health`
|
||||
|
||||
---
|
||||
|
||||
## 4. Picture
|
||||
|
||||
**Pfad:** `/picture`
|
||||
|
||||
**Zweck:** Bild- und Medienverwaltung.
|
||||
|
||||
### Architektur
|
||||
|
||||
**Kein dediziertes Backend.** Picture verwendet:
|
||||
|
||||
- SvelteKit Server-Routes für Backend-Logik
|
||||
- Mana Core für Authentifizierung
|
||||
- Shared Packages aus `/packages`
|
||||
|
||||
```
|
||||
picture/
|
||||
├── apps/
|
||||
│ ├── mobile/ # React Native Expo
|
||||
│ ├── web/ # SvelteKit
|
||||
│ └── landing/ # Astro
|
||||
└── packages/
|
||||
├── design-tokens/ # Design System
|
||||
├── mobile-ui/ # Mobile UI Components
|
||||
└── shared/ # Utilities
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Memoro
|
||||
|
||||
**Pfad:** `/memoro`
|
||||
|
||||
**Zweck:** Legacy-Content und Memory-Preservation.
|
||||
|
||||
### Architektur
|
||||
|
||||
**Kein dediziertes Backend.** Memoro verwendet:
|
||||
|
||||
- SvelteKit Server-Routes
|
||||
- Mana Core für Authentifizierung
|
||||
- Supabase (Legacy-Konfiguration vorhanden)
|
||||
|
||||
```
|
||||
memoro/
|
||||
├── apps/
|
||||
│ ├── mobile/
|
||||
│ ├── web/
|
||||
│ └── landing/
|
||||
└── supabase/ # Legacy Supabase Config
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Manacore
|
||||
|
||||
**Pfad:** `/manacore`
|
||||
|
||||
**Zweck:** Core-Authentifizierung und Credit-System.
|
||||
|
||||
### Architektur
|
||||
|
||||
Das Manacore-Backend ist **extern gehostet** und nicht Teil des Monorepos:
|
||||
|
||||
- **URL:** `https://mana-core-middleware-111768794939.europe-west3.run.app`
|
||||
- **Integration:** Via `@mana-core/nestjs-integration` Package
|
||||
|
||||
```
|
||||
manacore/
|
||||
├── apps/
|
||||
│ ├── mobile/ # Auth-Flow UI
|
||||
│ ├── web/ # Dashboard
|
||||
│ └── landing/ # Marketing
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Shared Packages für Backend
|
||||
|
||||
### @manacore/manadeck-database
|
||||
|
||||
PostgreSQL-Datenbankschema für Manadeck.
|
||||
|
||||
```
|
||||
packages/manadeck-database/
|
||||
├── src/
|
||||
│ ├── schema/ # Drizzle Schema
|
||||
│ ├── client.ts # DB Client
|
||||
│ └── index.ts # Exports
|
||||
├── drizzle.config.ts
|
||||
└── docker-compose.yml
|
||||
```
|
||||
|
||||
### @manacore/uload-database
|
||||
|
||||
PostgreSQL-Datenbankschema für Uload URL-Shortener.
|
||||
|
||||
```
|
||||
packages/uload-database/
|
||||
├── src/
|
||||
│ ├── schema/
|
||||
│ │ ├── users.ts
|
||||
│ │ ├── links.ts
|
||||
│ │ ├── clicks.ts
|
||||
│ │ ├── tags.ts
|
||||
│ │ ├── workspaces.ts
|
||||
│ │ ├── accounts.ts
|
||||
│ │ └── relations.ts
|
||||
│ ├── client.ts # DB Client
|
||||
│ └── index.ts # Exports
|
||||
├── drizzle.config.ts
|
||||
└── docker-compose.yml
|
||||
```
|
||||
|
||||
### @mana-core/nestjs-integration
|
||||
|
||||
Externe Dependency für Backend-Integration:
|
||||
|
||||
```typescript
|
||||
// Installation via git
|
||||
"@mana-core/nestjs-integration": "git+https://github.com/mana-core/nestjs-integration.git"
|
||||
```
|
||||
|
||||
**Bereitgestellte Features:**
|
||||
- `AuthGuard` - JWT-Authentifizierung
|
||||
- `@CurrentUser()` - User-Context Decorator
|
||||
- `CreditClientService` - Credit-Operationen
|
||||
- Konfigurationsmodule
|
||||
|
||||
---
|
||||
|
||||
## Authentifizierungs-Pattern
|
||||
|
||||
Alle Projekte nutzen zentrale **Mana Core Authentifizierung**:
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ Frontend │────▶│ Project Backend │────▶│ Mana Core │
|
||||
│ (Web/Mobile) │ │ (NestJS/etc.) │ │ Middleware │
|
||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||
│ │ │
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
shared-auth AuthGuard JWT Validation
|
||||
shared-auth-ui @CurrentUser Credit Service
|
||||
shared-auth-stores CreditClient User Management
|
||||
```
|
||||
|
||||
### Frontend-Integration
|
||||
|
||||
```typescript
|
||||
// Shared Auth Store (Svelte)
|
||||
import { authStore } from '@manacore/shared-auth-stores';
|
||||
|
||||
// Login
|
||||
await authStore.login(email, password);
|
||||
|
||||
// Token für API-Requests
|
||||
const token = authStore.getAccessToken();
|
||||
```
|
||||
|
||||
### Backend-Integration
|
||||
|
||||
```typescript
|
||||
// NestJS Module Setup
|
||||
@Module({
|
||||
imports: [
|
||||
ManaCoreModule.forRoot({
|
||||
serviceKey: process.env.MANA_CORE_SERVICE_KEY,
|
||||
baseUrl: process.env.MANA_CORE_URL,
|
||||
}),
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Datenbank-Migrationen
|
||||
|
||||
### Manadeck (Drizzle)
|
||||
|
||||
```bash
|
||||
# Migration generieren
|
||||
pnpm --filter @manacore/manadeck-database drizzle-kit generate
|
||||
|
||||
# Migration ausführen
|
||||
pnpm --filter @manacore/manadeck-database drizzle-kit push
|
||||
```
|
||||
|
||||
### Maerchenzauber (Supabase)
|
||||
|
||||
```bash
|
||||
# Supabase CLI
|
||||
supabase migration new <name>
|
||||
supabase db push
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Umgebungsvariablen
|
||||
|
||||
### Maerchenzauber Backend
|
||||
|
||||
```env
|
||||
# Supabase
|
||||
SUPABASE_URL=
|
||||
SUPABASE_SERVICE_ROLE_KEY=
|
||||
|
||||
# Mana Core
|
||||
MANA_CORE_URL=https://mana-core-middleware-111768794939.europe-west3.run.app
|
||||
MANA_CORE_SERVICE_KEY=
|
||||
|
||||
# AI Services
|
||||
MAERCHENZAUBER_AZURE_OPENAI_ENDPOINT=
|
||||
AZURE_OPENAI_API_KEY=
|
||||
GOOGLE_GEMINI_API_KEY=
|
||||
REPLICATE_API_TOKEN=
|
||||
```
|
||||
|
||||
### Manadeck Backend
|
||||
|
||||
```env
|
||||
# Database
|
||||
DATABASE_URL=postgresql://postgres:postgres@localhost:5433/manadeck
|
||||
|
||||
# Mana Core
|
||||
MANA_CORE_URL=
|
||||
MANA_CORE_SERVICE_KEY=
|
||||
|
||||
# AI
|
||||
GOOGLE_GEMINI_API_KEY=
|
||||
|
||||
# Server
|
||||
PORT=8080
|
||||
```
|
||||
|
||||
### Uload
|
||||
|
||||
```env
|
||||
# Database
|
||||
DATABASE_URL=postgresql://...
|
||||
|
||||
# Redis
|
||||
REDIS_URL=redis://localhost:6379
|
||||
|
||||
# PocketBase
|
||||
POCKETBASE_URL=
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Lokale Entwicklung
|
||||
|
||||
### Maerchenzauber Backend
|
||||
|
||||
```bash
|
||||
cd maerchenzauber/apps/backend
|
||||
pnpm install
|
||||
pnpm run start:dev
|
||||
# Läuft auf Port 3002
|
||||
```
|
||||
|
||||
### Manadeck Backend
|
||||
|
||||
```bash
|
||||
# 1. Datenbank starten
|
||||
cd packages/manadeck-database
|
||||
docker-compose up -d
|
||||
|
||||
# 2. Backend starten
|
||||
cd manadeck/apps/backend
|
||||
pnpm install
|
||||
pnpm run start:dev
|
||||
# Läuft auf Port 8080
|
||||
```
|
||||
|
||||
### Uload
|
||||
|
||||
```bash
|
||||
cd uload
|
||||
docker-compose up -d # PostgreSQL + Redis
|
||||
pnpm install
|
||||
pnpm run dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
Das Manacore Monorepo verwendet verschiedene Backend-Strategien:
|
||||
|
||||
1. **Full Backend (NestJS):** Maerchenzauber, Manadeck - Für komplexe Geschäftslogik und AI-Integration
|
||||
2. **Embedded Database (PocketBase):** Uload - Für einfache CRUD-Operationen
|
||||
3. **Frontend-only:** Picture, Memoro - Server-Routes in SvelteKit
|
||||
4. **External Backend:** Manacore - Zentrale Auth/Credit-Services
|
||||
|
||||
Alle Projekte teilen sich:
|
||||
- Gemeinsame Authentifizierung via Mana Core
|
||||
- Shared Packages für UI, Auth, Types
|
||||
- Einheitliches Deployment-Pattern (Docker + Cloud Run)
|
||||
|
||||
---
|
||||
|
||||
## Vereinheitlichungs-Roadmap
|
||||
|
||||
### Aktuelle Fragmentierung
|
||||
|
||||
| Aspekt | Maerchenzauber | Manadeck | Uload |
|
||||
|--------|----------------|----------|-------|
|
||||
| Framework | NestJS v10 | NestJS v11 | PocketBase |
|
||||
| Datenbank | Supabase | PostgreSQL | PocketBase + PG |
|
||||
| ORM | @supabase/js | Drizzle | Drizzle |
|
||||
| Auth | Mana Core | Mana Core | PocketBase + Mana Core |
|
||||
|
||||
---
|
||||
|
||||
### Strategie 1: Shared NestJS Backend Package
|
||||
|
||||
**Ziel:** Ein gemeinsames `@manacore/shared-backend` Package mit wiederverwendbaren Modulen.
|
||||
|
||||
```
|
||||
packages/shared-backend/
|
||||
├── src/
|
||||
│ ├── auth/
|
||||
│ │ ├── auth.module.ts
|
||||
│ │ ├── auth.guard.ts
|
||||
│ │ └── current-user.decorator.ts
|
||||
│ ├── database/
|
||||
│ │ ├── database.module.ts
|
||||
│ │ ├── drizzle.provider.ts
|
||||
│ │ └── base.repository.ts
|
||||
│ ├── health/
|
||||
│ │ └── health.module.ts
|
||||
│ ├── credits/
|
||||
│ │ └── credits.module.ts
|
||||
│ └── common/
|
||||
│ ├── filters/
|
||||
│ ├── interceptors/
|
||||
│ └── pipes/
|
||||
```
|
||||
|
||||
**Vorteile:**
|
||||
- Einheitliche Auth-Integration
|
||||
- Wiederverwendbare Module
|
||||
- Konsistente Error-Handling
|
||||
|
||||
**Aufwand:** Mittel
|
||||
|
||||
---
|
||||
|
||||
### Strategie 2: Einheitliche Datenbank-Strategie
|
||||
|
||||
#### Option A: Alles auf Drizzle + PostgreSQL (Empfohlen)
|
||||
|
||||
```typescript
|
||||
// packages/shared-database/src/base-schema.ts
|
||||
export const baseColumns = {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
createdAt: timestamp('created_at').defaultNow(),
|
||||
updatedAt: timestamp('updated_at').defaultNow(),
|
||||
userId: text('user_id').notNull(),
|
||||
};
|
||||
|
||||
// Projekt-spezifische Erweiterung
|
||||
// maerchenzauber/database/schema/characters.ts
|
||||
import { baseColumns } from '@manacore/shared-database';
|
||||
|
||||
export const characters = pgTable('characters', {
|
||||
...baseColumns,
|
||||
name: text('name').notNull(),
|
||||
traits: jsonb('traits'),
|
||||
});
|
||||
```
|
||||
|
||||
**Migration von Supabase:**
|
||||
- Supabase ist PostgreSQL → Schema kann übernommen werden
|
||||
- RLS-Policies in Application-Layer verschieben
|
||||
- Storage → S3/Cloudflare R2
|
||||
|
||||
#### Option B: Alles auf Supabase
|
||||
|
||||
```typescript
|
||||
// packages/shared-supabase/src/client.ts
|
||||
export const createProjectClient = (project: 'maerchenzauber' | 'manadeck' | 'uload') => {
|
||||
return createClient(
|
||||
process.env[`${project.toUpperCase()}_SUPABASE_URL`],
|
||||
process.env[`${project.toUpperCase()}_SUPABASE_KEY`]
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
**Vorteile Supabase:**
|
||||
- Eingebaute Auth (optional nutzbar)
|
||||
- Storage inklusive
|
||||
- Realtime-Subscriptions
|
||||
- Edge Functions möglich
|
||||
|
||||
**Nachteile Supabase:**
|
||||
- Vendor Lock-in
|
||||
- Weniger Kontrolle über Schema
|
||||
|
||||
**Empfehlung:** Drizzle + PostgreSQL wegen Type-Safety, moderner API und keinem Vendor Lock-in.
|
||||
|
||||
---
|
||||
|
||||
### Strategie 3: Einheitliche Monorepo Backend Struktur
|
||||
|
||||
**Ziel-Architektur:**
|
||||
|
||||
```
|
||||
packages/
|
||||
├── shared-backend/ # Gemeinsame NestJS Module
|
||||
│ ├── auth/
|
||||
│ ├── database/
|
||||
│ ├── health/
|
||||
│ └── credits/
|
||||
├── shared-database/ # Drizzle Basis-Schema
|
||||
│ ├── base-schema.ts
|
||||
│ ├── migrations/
|
||||
│ └── client.ts
|
||||
├── maerchenzauber-database/ # Projekt-Schema
|
||||
├── manadeck-database/ # ✓ Existiert bereits
|
||||
└── uload-database/ # Neu
|
||||
|
||||
apps/
|
||||
├── maerchenzauber-backend/ # Nutzt shared-backend
|
||||
├── manadeck-backend/ # Nutzt shared-backend
|
||||
└── uload-backend/ # Neues NestJS Backend (ersetzt PocketBase)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Strategie 4: Shared Backend als Service-Layer
|
||||
|
||||
**Ziel:** Gemeinsamer Service-Layer, projekt-spezifische Controller.
|
||||
|
||||
```typescript
|
||||
// packages/shared-backend/src/services/ai.service.ts
|
||||
@Injectable()
|
||||
export class BaseAiService {
|
||||
constructor(
|
||||
private gemini: GeminiClient,
|
||||
private credits: CreditService,
|
||||
) {}
|
||||
|
||||
protected async generateWithCredits<T>(
|
||||
userId: string,
|
||||
operation: string,
|
||||
generator: () => Promise<T>
|
||||
): Promise<T> {
|
||||
await this.credits.check(userId, operation);
|
||||
const result = await generator();
|
||||
await this.credits.deduct(userId, operation);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// maerchenzauber/backend/src/story/story.service.ts
|
||||
@Injectable()
|
||||
export class StoryService extends BaseAiService {
|
||||
async generateStory(userId: string, input: StoryInput) {
|
||||
return this.generateWithCredits(userId, 'STORY_GENERATION', async () => {
|
||||
// Projekt-spezifische Logik
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Strategie 5: API-Gateway Pattern (Optional)
|
||||
|
||||
**Ziel:** Ein zentrales Gateway vor allen Backends.
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ API Gateway │
|
||||
│ (Kong/Traefik) │
|
||||
└────────┬────────┘
|
||||
│
|
||||
┌────────────────────┼────────────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
|
||||
│ Maerchenzauber│ │ Manadeck │ │ Uload │
|
||||
│ Backend │ │ Backend │ │ Backend │
|
||||
└───────────────┘ └───────────────┘ └───────────────┘
|
||||
```
|
||||
|
||||
**Vorteile:**
|
||||
- Zentrale Auth-Validierung
|
||||
- Rate Limiting
|
||||
- Request Logging
|
||||
- Einheitliche API-Struktur
|
||||
|
||||
**Aufwand:** Hoch - Empfohlen erst bei Skalierungsbedarf
|
||||
|
||||
---
|
||||
|
||||
### Empfohlene Implementierungsreihenfolge
|
||||
|
||||
#### Phase 1: Shared Backend Package
|
||||
|
||||
**Priorität:** Hoch
|
||||
**Aufwand:** 2-3 Wochen
|
||||
|
||||
Neues Package `packages/shared-backend/` mit:
|
||||
- Auth Module (wraps @mana-core/nestjs-integration)
|
||||
- Health Module
|
||||
- Credits Module
|
||||
- Base Repository Pattern
|
||||
- Common Decorators, Guards, Filters
|
||||
|
||||
#### Phase 2: Datenbank-Vereinheitlichung
|
||||
|
||||
**Priorität:** Hoch
|
||||
**Aufwand:** 3-4 Wochen
|
||||
|
||||
1. `packages/shared-database/` mit Drizzle Basis-Schema erstellen
|
||||
2. Maerchenzauber von Supabase auf Drizzle migrieren
|
||||
3. Uload PocketBase durch PostgreSQL + Drizzle ersetzen
|
||||
|
||||
#### Phase 3: Uload Backend Neubau (Optional)
|
||||
|
||||
**Priorität:** Mittel
|
||||
**Aufwand:** 2-3 Wochen
|
||||
|
||||
PocketBase → NestJS Migration:
|
||||
- Konsistenz mit anderen Projekten
|
||||
- Bessere Integration mit Mana Core
|
||||
- Einheitliches Deployment
|
||||
|
||||
---
|
||||
|
||||
### Optionen-Vergleich
|
||||
|
||||
| Option | Aufwand | Benefit | Empfehlung |
|
||||
|--------|---------|---------|------------|
|
||||
| Shared Backend Package | Mittel | Hoch | ✅ Priorität 1 |
|
||||
| Drizzle überall | Mittel-Hoch | Hoch | ✅ Priorität 2 |
|
||||
| Uload auf NestJS | Hoch | Mittel | ⚡ Optional |
|
||||
| API Gateway | Sehr Hoch | Mittel | ⏳ Später |
|
||||
|
||||
---
|
||||
|
||||
### Quick Wins (sofort umsetzbar)
|
||||
|
||||
1. **NestJS Version angleichen** → Alle auf v11
|
||||
2. **Einheitliche Health-Endpoints** → `/health`, `/health/ready`
|
||||
3. **Gemeinsame ESLint/Prettier Config** → `@manacore/eslint-config-backend`
|
||||
4. **Einheitliche Error-Response-Struktur:**
|
||||
|
||||
```typescript
|
||||
// Einheitliches Error-Format für alle Backends
|
||||
interface ApiError {
|
||||
statusCode: number;
|
||||
error: string;
|
||||
message: string;
|
||||
timestamp: string;
|
||||
path: string;
|
||||
}
|
||||
```
|
||||
|
||||
5. **Einheitliche Logging-Struktur:**
|
||||
|
||||
```typescript
|
||||
// packages/shared-backend/src/logging/logger.service.ts
|
||||
@Injectable()
|
||||
export class AppLogger {
|
||||
log(context: string, message: string, meta?: Record<string, any>) {
|
||||
console.log(JSON.stringify({
|
||||
level: 'info',
|
||||
context,
|
||||
message,
|
||||
timestamp: new Date().toISOString(),
|
||||
...meta,
|
||||
}));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Ziel-Architektur nach Vereinheitlichung
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Shared Packages │
|
||||
├─────────────────┬─────────────────┬─────────────────────────┤
|
||||
│ shared-backend │ shared-database │ shared-types │
|
||||
│ - AuthModule │ - baseColumns │ - ApiError │
|
||||
│ - HealthModule │ - drizzleClient │ - User │
|
||||
│ - CreditsModule │ - migrations │ - CreditOperation │
|
||||
│ - BaseRepo │ │ │
|
||||
└────────┬────────┴────────┬────────┴────────┬────────────────┘
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ Maerchenzauber │ │ Manadeck │ │ Uload │
|
||||
│ Backend │ │ Backend │ │ Backend │
|
||||
├─────────────────┤ ├─────────────────┤ ├─────────────────┤
|
||||
│ NestJS v11 │ │ NestJS v11 │ │ NestJS v11 │
|
||||
│ PostgreSQL │ │ PostgreSQL │ │ PostgreSQL │
|
||||
│ Drizzle ORM │ │ Drizzle ORM │ │ Drizzle ORM │
|
||||
│ Port: 3002 │ │ Port: 8080 │ │ Port: 3003 │
|
||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||
│ │ │
|
||||
└─────────────────┼─────────────────┘
|
||||
▼
|
||||
┌─────────────────────┐
|
||||
│ Mana Core │
|
||||
│ (Auth + Credits) │
|
||||
└─────────────────────┘
|
||||
```
|
||||
226
docs/I18N.md
Normal file
226
docs/I18N.md
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
# Internationalization (i18n) im Manacore Monorepo
|
||||
|
||||
Alle Web-Projekte im Monorepo verwenden **svelte-i18n** für die Internationalisierung. Diese Dokumentation beschreibt die einheitliche Implementierung.
|
||||
|
||||
## Übersicht
|
||||
|
||||
| Projekt | Sprachen | Default |
|
||||
|---------|----------|---------|
|
||||
| maerchenzauber | de, en, es, fr, it | de |
|
||||
| manacore | de, en, es, fr, it | de |
|
||||
| manadeck | de, en, es, fr, it | de |
|
||||
| memoro | de, en, es, fr, it | de |
|
||||
| picture | de, en | de |
|
||||
| uload | de, en, es, fr, it | en |
|
||||
|
||||
## Projektstruktur
|
||||
|
||||
Jedes Web-Projekt hat die folgende i18n-Struktur:
|
||||
|
||||
```
|
||||
apps/web/
|
||||
└── src/
|
||||
└── lib/
|
||||
└── i18n/
|
||||
├── index.ts # Initialisierung & Konfiguration
|
||||
└── locales/
|
||||
├── de.json # Deutsche Übersetzungen
|
||||
├── en.json # Englische Übersetzungen
|
||||
├── es.json # Spanische Übersetzungen (optional)
|
||||
├── fr.json # Französische Übersetzungen (optional)
|
||||
└── it.json # Italienische Übersetzungen (optional)
|
||||
```
|
||||
|
||||
## Implementierung
|
||||
|
||||
### 1. index.ts - Konfiguration
|
||||
|
||||
```typescript
|
||||
import { browser } from '$app/environment';
|
||||
import { init, register, locale, waitLocale } from 'svelte-i18n';
|
||||
|
||||
// Sprachen registrieren
|
||||
register('de', () => import('./locales/de.json'));
|
||||
register('en', () => import('./locales/en.json'));
|
||||
// ... weitere Sprachen
|
||||
|
||||
// Unterstützte Sprachen
|
||||
export const supportedLocales = ['de', 'en', 'es', 'fr', 'it'] as const;
|
||||
export type SupportedLocale = (typeof supportedLocales)[number];
|
||||
|
||||
const defaultLocale = 'de';
|
||||
|
||||
// Initiale Sprache ermitteln
|
||||
function getInitialLocale(): SupportedLocale {
|
||||
if (browser) {
|
||||
// 1. localStorage prüfen
|
||||
const stored = localStorage.getItem('locale');
|
||||
if (stored && supportedLocales.includes(stored as SupportedLocale)) {
|
||||
return stored as SupportedLocale;
|
||||
}
|
||||
|
||||
// 2. Browser-Sprache prüfen
|
||||
const browserLang = navigator.language.split('-')[0];
|
||||
if (supportedLocales.includes(browserLang as SupportedLocale)) {
|
||||
return browserLang as SupportedLocale;
|
||||
}
|
||||
}
|
||||
return defaultLocale;
|
||||
}
|
||||
|
||||
// i18n initialisieren
|
||||
init({
|
||||
fallbackLocale: defaultLocale,
|
||||
initialLocale: getInitialLocale()
|
||||
});
|
||||
|
||||
// Sprache ändern und speichern
|
||||
export function setLocale(newLocale: SupportedLocale) {
|
||||
locale.set(newLocale);
|
||||
if (browser) {
|
||||
localStorage.setItem('locale', newLocale);
|
||||
}
|
||||
}
|
||||
|
||||
export { waitLocale };
|
||||
```
|
||||
|
||||
### 2. Locale-Dateien (JSON)
|
||||
|
||||
Die Übersetzungen werden als flache oder verschachtelte JSON-Objekte gespeichert:
|
||||
|
||||
```json
|
||||
{
|
||||
"nav_login": "Anmelden",
|
||||
"nav_register": "Registrieren",
|
||||
"common": {
|
||||
"save": "Speichern",
|
||||
"cancel": "Abbrechen"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Verwendung in Svelte-Komponenten
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
import { t } from 'svelte-i18n';
|
||||
</script>
|
||||
|
||||
<!-- Einfacher Key -->
|
||||
<button>{$t('nav_login')}</button>
|
||||
|
||||
<!-- Verschachtelter Key -->
|
||||
<button>{$t('common.save')}</button>
|
||||
|
||||
<!-- Mit Parametern -->
|
||||
<p>{$t('welcome', { values: { name: 'Max' } })}</p>
|
||||
```
|
||||
|
||||
### 4. Language Switcher
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
import { locale } from 'svelte-i18n';
|
||||
import { setLocale, supportedLocales } from '$lib/i18n';
|
||||
|
||||
const languages = [
|
||||
{ code: 'de', name: 'Deutsch', flag: '🇩🇪' },
|
||||
{ code: 'en', name: 'English', flag: '🇬🇧' }
|
||||
];
|
||||
</script>
|
||||
|
||||
{#each languages as lang}
|
||||
<button
|
||||
onclick={() => setLocale(lang.code)}
|
||||
class:active={$locale === lang.code}
|
||||
>
|
||||
{lang.flag} {lang.name}
|
||||
</button>
|
||||
{/each}
|
||||
```
|
||||
|
||||
## Neues Projekt einrichten
|
||||
|
||||
1. **svelte-i18n installieren:**
|
||||
```bash
|
||||
pnpm add svelte-i18n
|
||||
```
|
||||
|
||||
2. **i18n-Ordner erstellen:**
|
||||
```bash
|
||||
mkdir -p src/lib/i18n/locales
|
||||
```
|
||||
|
||||
3. **index.ts kopieren** von einem bestehenden Projekt und anpassen
|
||||
|
||||
4. **Locale-Dateien erstellen** (de.json, en.json, etc.)
|
||||
|
||||
5. **In +layout.svelte importieren:**
|
||||
```svelte
|
||||
<script>
|
||||
import '$lib/i18n';
|
||||
</script>
|
||||
```
|
||||
|
||||
## Neue Übersetzung hinzufügen
|
||||
|
||||
1. Key in allen Locale-Dateien hinzufügen:
|
||||
```json
|
||||
// de.json
|
||||
{ "new_key": "Neue Übersetzung" }
|
||||
|
||||
// en.json
|
||||
{ "new_key": "New translation" }
|
||||
```
|
||||
|
||||
2. In Komponente verwenden:
|
||||
```svelte
|
||||
{$t('new_key')}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
- **Keys:** Snake_case verwenden (`nav_login`, `home_title`)
|
||||
- **Namespacing:** Präfixe für Bereiche (`auth_`, `nav_`, `home_`, `toast_`)
|
||||
- **Fallback:** Immer `fallbackLocale` setzen
|
||||
- **SSR:** `waitLocale()` in Server-Load-Funktionen verwenden
|
||||
- **Konsistenz:** Gleiche Keys in allen Projekten für gemeinsame Elemente
|
||||
|
||||
## Fehlerbehebung
|
||||
|
||||
### Übersetzung wird nicht angezeigt
|
||||
|
||||
1. Prüfen ob Key in allen Locale-Dateien existiert
|
||||
2. Prüfen ob `$lib/i18n` importiert wurde
|
||||
3. Browser-Cache leeren
|
||||
|
||||
### SSR-Fehler
|
||||
|
||||
```typescript
|
||||
// In +layout.ts oder +page.ts
|
||||
import { waitLocale } from '$lib/i18n';
|
||||
|
||||
export const load = async () => {
|
||||
await waitLocale();
|
||||
return {};
|
||||
};
|
||||
```
|
||||
|
||||
### Sprache wechselt nicht
|
||||
|
||||
Prüfen ob `locale.set()` aufgerufen wird und localStorage Zugriff erlaubt ist.
|
||||
|
||||
## Shared i18n Package
|
||||
|
||||
Für gemeinsame Übersetzungen zwischen Projekten kann das Package `@manacore/shared-i18n` verwendet werden (wenn vorhanden).
|
||||
|
||||
```typescript
|
||||
// In index.ts
|
||||
import sharedTranslations from '@manacore/shared-i18n/de.json';
|
||||
|
||||
register('de', async () => {
|
||||
const local = await import('./locales/de.json');
|
||||
return { ...sharedTranslations, ...local.default };
|
||||
});
|
||||
```
|
||||
684
docs/SELF-HOSTING-GUIDE.md
Normal file
684
docs/SELF-HOSTING-GUIDE.md
Normal file
|
|
@ -0,0 +1,684 @@
|
|||
# Self-Hosting Guide - Manacore Monorepo
|
||||
|
||||
Komplette Anleitung zum Hosten aller Projekte auf eigener Infrastruktur (VPS).
|
||||
|
||||
## Projektübersicht
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ MANACORE MONOREPO │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ MAERCHENZAUBER│ │ MANACORE │ │ MANADECK │ │ MEMORO │ │
|
||||
│ │ (Storyteller)│ │ (Auth Hub) │ │ (Deck App) │ │ (Voice App) │ │
|
||||
│ ├──────────────┤ ├──────────────┤ ├──────────────┤ ├──────────────┤ │
|
||||
│ │ • Web │ │ • Web │ │ • Web │ │ • Web │ │
|
||||
│ │ • Mobile │ │ • Mobile │ │ • Mobile │ │ • Mobile │ │
|
||||
│ │ • Landing │ │ • Landing │ │ • Landing │ │ • Landing │ │
|
||||
│ │ • Backend │ │ │ │ • Backend │ │ │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ PICTURE │ │ ULOAD │ │
|
||||
│ │ (Canvas App) │ │(URL Shortener)│ │
|
||||
│ ├──────────────┤ ├──────────────┤ │
|
||||
│ │ • Web │ │ • Web │ │
|
||||
│ │ • Mobile │ │ │ │
|
||||
│ │ • Landing │ │ │ │
|
||||
│ └──────────────┘ └──────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Technologie-Stack pro Projekt
|
||||
|
||||
| Projekt | Web App | Landing | Backend | Mobile | Datenbank |
|
||||
|---------|---------|---------|---------|--------|-----------|
|
||||
| **Maerchenzauber** | SvelteKit | Astro | NestJS | Expo | Supabase |
|
||||
| **Manacore** | SvelteKit | Astro | - | Expo | Supabase |
|
||||
| **Manadeck** | SvelteKit | Astro | NestJS | Expo | PostgreSQL |
|
||||
| **Memoro** | SvelteKit | Astro | - | Expo | Supabase |
|
||||
| **Picture** | SvelteKit | Astro | - | Expo | Supabase |
|
||||
| **uLoad** | SvelteKit | - | - | - | PostgreSQL + Redis |
|
||||
|
||||
---
|
||||
|
||||
## Deployment-Optionen im Überblick
|
||||
|
||||
### Option A: Single VPS mit Coolify (Empfohlen für Start)
|
||||
- **Kosten:** ~€15-30/Monat
|
||||
- **Komplexität:** Niedrig
|
||||
- **Skalierung:** Begrenzt
|
||||
|
||||
### Option B: Multi-VPS mit Coolify
|
||||
- **Kosten:** ~€50-100/Monat
|
||||
- **Komplexität:** Mittel
|
||||
- **Skalierung:** Gut
|
||||
|
||||
### Option C: Kubernetes (K3s)
|
||||
- **Kosten:** ~€30-80/Monat
|
||||
- **Komplexität:** Hoch
|
||||
- **Skalierung:** Sehr gut
|
||||
|
||||
### Option D: Hybrid (Self-Hosted + Managed)
|
||||
- **Kosten:** ~€20-50/Monat + Supabase
|
||||
- **Komplexität:** Niedrig-Mittel
|
||||
- **Skalierung:** Flexibel
|
||||
|
||||
---
|
||||
|
||||
# Option A: Single VPS mit Coolify
|
||||
|
||||
Die einfachste Lösung für den Start. Alle Services auf einem Server.
|
||||
|
||||
## Architektur
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Hetzner VPS (CX31+) │
|
||||
│ 4 vCPU, 8GB RAM, 80GB │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ COOLIFY │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ TRAEFIK │ │
|
||||
│ │ (Reverse Proxy + SSL) │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ │ │ │ │ │
|
||||
│ ┌──────┴──────┐ ┌─────┴─────┐ ┌─────┴─────┐ ┌─────┴─────┐ │
|
||||
│ │ Web Apps │ │ Backends │ │ Databases │ │ Landing │ │
|
||||
│ │ (Node.js) │ │ (NestJS) │ │ (PG+Redis)│ │ (Astro) │ │
|
||||
│ │ :3000-3005 │ │ :4000-4001│ │ :5432,6379│ │ :8080+ │ │
|
||||
│ └─────────────┘ └───────────┘ └───────────┘ └───────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Ressourcen-Anforderungen
|
||||
|
||||
| Komponente | RAM | CPU | Disk |
|
||||
|------------|-----|-----|------|
|
||||
| PostgreSQL | 1GB | 0.5 | 10GB |
|
||||
| Redis | 256MB | 0.2 | 1GB |
|
||||
| Coolify | 512MB | 0.3 | 5GB |
|
||||
| Traefik | 128MB | 0.1 | - |
|
||||
| Pro Web App | 256MB | 0.3 | - |
|
||||
| Pro Backend | 512MB | 0.5 | - |
|
||||
| Pro Landing | 64MB | 0.1 | - |
|
||||
| **Gesamt (alle)** | **~6GB** | **~4** | **~30GB** |
|
||||
|
||||
**Empfohlener Server:** Hetzner CX31 (4 vCPU, 8GB RAM, 80GB) - €8.98/Monat
|
||||
|
||||
## Schritt-für-Schritt Setup
|
||||
|
||||
### 1. VPS bestellen und Coolify installieren
|
||||
|
||||
```bash
|
||||
# SSH zum Server
|
||||
ssh root@YOUR-IP
|
||||
|
||||
# System updaten
|
||||
apt update && apt upgrade -y
|
||||
|
||||
# Coolify installieren
|
||||
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
|
||||
```
|
||||
|
||||
### 2. Datenbank-Services erstellen
|
||||
|
||||
**PostgreSQL:**
|
||||
```
|
||||
Name: shared-postgres
|
||||
Version: 16-alpine
|
||||
Databases: manacore, manadeck, uload
|
||||
```
|
||||
|
||||
**Redis:**
|
||||
```
|
||||
Name: shared-redis
|
||||
Version: 7-alpine
|
||||
```
|
||||
|
||||
### 3. Projekte deployen
|
||||
|
||||
#### Deployment-Reihenfolge (wichtig!)
|
||||
|
||||
1. **Datenbanken** (PostgreSQL, Redis)
|
||||
2. **Backends** (Maerchenzauber, Manadeck)
|
||||
3. **Web Apps** (alle)
|
||||
4. **Landing Pages** (alle)
|
||||
|
||||
#### Konfiguration pro Projekt
|
||||
|
||||
**Alle SvelteKit Web Apps:**
|
||||
```
|
||||
Base Directory: /
|
||||
Dockerfile: {projekt}/apps/web/Dockerfile # Falls vorhanden
|
||||
oder
|
||||
Build Pack: Nixpacks
|
||||
Build Command: cd {projekt}/apps/web && pnpm build
|
||||
Start Command: cd {projekt}/apps/web && node build
|
||||
Port: 3000
|
||||
```
|
||||
|
||||
**Alle Astro Landing Pages:**
|
||||
```
|
||||
Build Pack: Static
|
||||
Base Directory: {projekt}/apps/landing
|
||||
Build Command: pnpm build
|
||||
Publish Directory: dist
|
||||
```
|
||||
|
||||
**NestJS Backends:**
|
||||
```
|
||||
Base Directory: /
|
||||
Dockerfile: {projekt}/apps/backend/Dockerfile
|
||||
oder
|
||||
Dockerfile: {projekt}/backend/Dockerfile
|
||||
Port: 4000
|
||||
```
|
||||
|
||||
### 4. Domain-Mapping
|
||||
|
||||
| Service | Domain | Port |
|
||||
|---------|--------|------|
|
||||
| uload-web | ulo.ad | 3000 |
|
||||
| maerchenzauber-web | app.maerchenzauber.de | 3001 |
|
||||
| maerchenzauber-landing | maerchenzauber.de | 8080 |
|
||||
| maerchenzauber-backend | api.maerchenzauber.de | 4000 |
|
||||
| manacore-web | app.manacore.io | 3002 |
|
||||
| manacore-landing | manacore.io | 8081 |
|
||||
| manadeck-web | app.manadeck.de | 3003 |
|
||||
| manadeck-landing | manadeck.de | 8082 |
|
||||
| manadeck-backend | api.manadeck.de | 4001 |
|
||||
| memoro-web | app.memoro.ai | 3004 |
|
||||
| memoro-landing | memoro.ai | 8083 |
|
||||
| picture-web | app.picture.io | 3005 |
|
||||
| picture-landing | picture.io | 8084 |
|
||||
|
||||
---
|
||||
|
||||
# Option B: Multi-VPS mit Coolify
|
||||
|
||||
Bessere Isolation und Skalierung durch mehrere Server.
|
||||
|
||||
## Architektur
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ DNS / CDN │
|
||||
│ (Cloudflare) │
|
||||
└────────┬────────┘
|
||||
│
|
||||
┌───────────────────────────┼───────────────────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
|
||||
│ VPS 1 │ │ VPS 2 │ │ VPS 3 │
|
||||
│ (Apps) │ │ (Backends) │ │ (Databases) │
|
||||
│ CX21 │ │ CX21 │ │ CX31 │
|
||||
├───────────────┤ ├───────────────┤ ├───────────────┤
|
||||
│ • Web Apps │ ◄─────► │ • NestJS APIs │ ◄─────► │ • PostgreSQL │
|
||||
│ • Landing │ │ • Workers │ │ • Redis │
|
||||
│ Pages │ │ │ │ • Backups │
|
||||
└───────────────┘ └───────────────┘ └───────────────┘
|
||||
```
|
||||
|
||||
## Server-Aufteilung
|
||||
|
||||
### VPS 1: Frontend (CX21 - €4.49/Monat)
|
||||
- Alle SvelteKit Web Apps
|
||||
- Alle Astro Landing Pages
|
||||
- Traefik Reverse Proxy
|
||||
|
||||
### VPS 2: Backends (CX21 - €4.49/Monat)
|
||||
- Maerchenzauber NestJS Backend
|
||||
- Manadeck NestJS Backend
|
||||
- Background Workers
|
||||
|
||||
### VPS 3: Datenbanken (CX31 - €8.98/Monat)
|
||||
- PostgreSQL (shared)
|
||||
- Redis (shared)
|
||||
- Automated Backups
|
||||
|
||||
**Gesamtkosten:** ~€18/Monat
|
||||
|
||||
## Einrichtung
|
||||
|
||||
### VPS 3 (Datenbanken) zuerst
|
||||
|
||||
```bash
|
||||
# Coolify installieren
|
||||
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
|
||||
|
||||
# PostgreSQL mit externem Zugriff
|
||||
# In Coolify: Network → Enable External Access
|
||||
```
|
||||
|
||||
### VPS 2 (Backends)
|
||||
|
||||
```bash
|
||||
# Coolify installieren
|
||||
# Backends deployen mit DATABASE_URL zu VPS 3
|
||||
```
|
||||
|
||||
### VPS 1 (Frontends)
|
||||
|
||||
```bash
|
||||
# Coolify installieren
|
||||
# Web Apps deployen mit API_URL zu VPS 2
|
||||
```
|
||||
|
||||
## Netzwerk-Sicherheit
|
||||
|
||||
```bash
|
||||
# Auf VPS 3 (Datenbanken): Nur VPS 1+2 erlauben
|
||||
ufw allow from VPS1-IP to any port 5432
|
||||
ufw allow from VPS2-IP to any port 5432
|
||||
ufw allow from VPS1-IP to any port 6379
|
||||
ufw allow from VPS2-IP to any port 6379
|
||||
ufw deny 5432
|
||||
ufw deny 6379
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Option C: Kubernetes mit K3s
|
||||
|
||||
Für maximale Skalierung und Automatisierung.
|
||||
|
||||
## Architektur
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ K3s Cluster │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌────────────────────────────────────────────────────────────┐ │
|
||||
│ │ INGRESS (Traefik) │ │
|
||||
│ └────────────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌───────────────────────────┼───────────────────────────────┐ │
|
||||
│ │ │ │ │
|
||||
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
|
||||
│ │ │ Namespace │ │ Namespace │ │ Namespace │ │ │
|
||||
│ │ │ uload │ │ maerchen- │ │ manadeck │ │ │
|
||||
│ │ │ │ │ zauber │ │ │ │ │
|
||||
│ │ │ ┌─────────┐ │ │ ┌─────────┐ │ │ ┌─────────┐ │ │ │
|
||||
│ │ │ │ web:3 │ │ │ │ web:2 │ │ │ │ web:2 │ │ │ │
|
||||
│ │ │ │ replicas│ │ │ │ backend │ │ │ │ backend │ │ │ │
|
||||
│ │ │ └─────────┘ │ │ │ landing │ │ │ │ landing │ │ │ │
|
||||
│ │ └─────────────┘ │ └─────────┘ │ │ └─────────┘ │ │ │
|
||||
│ │ └─────────────┘ └─────────────┘ │ │
|
||||
│ │ │ │
|
||||
│ │ ┌─────────────────────────────────────────────────┐ │ │
|
||||
│ │ │ Shared Services │ │ │
|
||||
│ │ │ PostgreSQL (StatefulSet) │ Redis (StatefulSet) │ │ │
|
||||
│ │ └─────────────────────────────────────────────────┘ │ │
|
||||
│ └───────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Node 1 (CX21) Node 2 (CX21) Node 3 (CX21) │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## K3s Setup
|
||||
|
||||
### Master Node installieren
|
||||
|
||||
```bash
|
||||
# Auf Node 1
|
||||
curl -sfL https://get.k3s.io | sh -
|
||||
|
||||
# Token für Worker holen
|
||||
cat /var/lib/rancher/k3s/server/node-token
|
||||
```
|
||||
|
||||
### Worker Nodes hinzufügen
|
||||
|
||||
```bash
|
||||
# Auf Node 2 und 3
|
||||
curl -sfL https://get.k3s.io | K3S_URL=https://NODE1-IP:6443 K3S_TOKEN=TOKEN sh -
|
||||
```
|
||||
|
||||
### Helm Charts deployen
|
||||
|
||||
```yaml
|
||||
# values-uload.yaml
|
||||
replicaCount: 2
|
||||
image:
|
||||
repository: ghcr.io/your-org/uload-web
|
||||
tag: latest
|
||||
service:
|
||||
port: 3000
|
||||
ingress:
|
||||
enabled: true
|
||||
hosts:
|
||||
- host: ulo.ad
|
||||
paths:
|
||||
- path: /
|
||||
env:
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: db-credentials
|
||||
key: url
|
||||
```
|
||||
|
||||
```bash
|
||||
helm install uload ./charts/sveltekit -f values-uload.yaml
|
||||
```
|
||||
|
||||
## Vorteile K8s
|
||||
|
||||
- Auto-Scaling bei Last
|
||||
- Rolling Updates ohne Downtime
|
||||
- Self-Healing bei Ausfällen
|
||||
- Resource Limits pro App
|
||||
|
||||
## Nachteile K8s
|
||||
|
||||
- Höhere Komplexität
|
||||
- Mehr Overhead (RAM für K8s selbst)
|
||||
- Lernkurve
|
||||
|
||||
---
|
||||
|
||||
# Option D: Hybrid (Self-Hosted + Managed)
|
||||
|
||||
Kombination aus Self-Hosting und Managed Services für beste Balance.
|
||||
|
||||
## Architektur
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ MANAGED SERVICES │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ SUPABASE │ │ CLOUDFLARE │ │ VERCEL/ │ │
|
||||
│ │ (Database) │ │ (CDN) │ │ NETLIFY │ │
|
||||
│ │ PostgreSQL │ │ DNS, Cache │ │ (Landing) │ │
|
||||
│ │ Auth, Store │ │ DDoS Prot. │ │ Static │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
│ │ │ │ │
|
||||
└─────────┼──────────────────┼──────────────────┼──────────────────┘
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ SELF-HOSTED (VPS) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ Hetzner CX21 │ │
|
||||
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
|
||||
│ │ │ Web Apps │ │ Backends │ │ uLoad │ │ │
|
||||
│ │ │ (SvelteKit)│ │ (NestJS) │ │ + DB │ │ │
|
||||
│ │ └────────────┘ └────────────┘ └────────────┘ │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Was wo hosten?
|
||||
|
||||
### Managed Services (empfohlen)
|
||||
|
||||
| Service | Anbieter | Kosten | Grund |
|
||||
|---------|----------|--------|-------|
|
||||
| Datenbank | Supabase | Free-$25/M | Auth + Realtime inklusive |
|
||||
| Landing Pages | Vercel/Netlify | Free | CDN + Edge |
|
||||
| CDN | Cloudflare | Free | DDoS + Caching |
|
||||
| Email | Resend | Free-$20/M | Deliverability |
|
||||
| Payments | Stripe | % per Tx | Compliance |
|
||||
|
||||
### Self-Hosted (VPS)
|
||||
|
||||
| Service | Grund |
|
||||
|---------|-------|
|
||||
| Web Apps | Volle Kontrolle, günstiger bei Traffic |
|
||||
| Backends | Custom Code, API Keys |
|
||||
| uLoad | Komplett eigene Infra gewünscht |
|
||||
| Redis | Falls benötigt |
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. Supabase Projekt erstellen
|
||||
|
||||
Für: Maerchenzauber, Manacore, Memoro, Picture
|
||||
|
||||
```bash
|
||||
# Supabase CLI
|
||||
supabase init
|
||||
supabase db push
|
||||
```
|
||||
|
||||
### 2. Landing Pages auf Vercel
|
||||
|
||||
```bash
|
||||
# In jedem Landing-Projekt
|
||||
cd maerchenzauber/apps/landing
|
||||
vercel deploy --prod
|
||||
```
|
||||
|
||||
### 3. VPS für Web Apps + Backends
|
||||
|
||||
```bash
|
||||
# Coolify auf Hetzner CX21
|
||||
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
|
||||
|
||||
# Apps deployen mit Supabase URLs
|
||||
```
|
||||
|
||||
## Kosten-Vergleich
|
||||
|
||||
| Komponente | Full Self-Hosted | Hybrid |
|
||||
|------------|------------------|--------|
|
||||
| VPS | €9-18/Monat | €4.50/Monat |
|
||||
| Supabase | - | Free-€25/Monat |
|
||||
| Vercel | - | Free |
|
||||
| Cloudflare | - | Free |
|
||||
| **Gesamt** | **€9-18/Monat** | **€4.50-30/Monat** |
|
||||
|
||||
---
|
||||
|
||||
# Dockerfiles für alle Projekte
|
||||
|
||||
## SvelteKit Web Apps (Template)
|
||||
|
||||
Erstelle für jedes Projekt ohne Dockerfile:
|
||||
|
||||
```dockerfile
|
||||
# {projekt}/apps/web/Dockerfile
|
||||
|
||||
FROM node:20-alpine AS builder
|
||||
RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
|
||||
WORKDIR /app
|
||||
|
||||
# Monorepo files
|
||||
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml ./
|
||||
COPY {projekt}/apps/web/ ./{projekt}/apps/web/
|
||||
COPY packages/ ./packages/
|
||||
|
||||
# Install and build
|
||||
RUN pnpm install --filter @{projekt}/web... --shamefully-hoist
|
||||
WORKDIR /app/{projekt}/apps/web
|
||||
RUN pnpm build
|
||||
|
||||
# Runner
|
||||
FROM node:20-alpine
|
||||
RUN adduser -D sveltekit
|
||||
WORKDIR /app
|
||||
COPY --from=builder /app/{projekt}/apps/web/build ./build
|
||||
COPY --from=builder /app/{projekt}/apps/web/package.json ./
|
||||
COPY --from=builder /app/node_modules ./node_modules
|
||||
|
||||
USER sveltekit
|
||||
ENV NODE_ENV=production PORT=3000
|
||||
EXPOSE 3000
|
||||
CMD ["node", "build"]
|
||||
```
|
||||
|
||||
## Astro Landing Pages
|
||||
|
||||
```dockerfile
|
||||
# {projekt}/apps/landing/Dockerfile
|
||||
|
||||
FROM node:20-alpine AS builder
|
||||
RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml ./
|
||||
COPY {projekt}/apps/landing/ ./{projekt}/apps/landing/
|
||||
COPY packages/ ./packages/
|
||||
|
||||
RUN pnpm install --filter @{projekt}/landing... --shamefully-hoist
|
||||
WORKDIR /app/{projekt}/apps/landing
|
||||
RUN pnpm build
|
||||
|
||||
# Nginx for static files
|
||||
FROM nginx:alpine
|
||||
COPY --from=builder /app/{projekt}/apps/landing/dist /usr/share/nginx/html
|
||||
EXPOSE 80
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
```
|
||||
|
||||
## NestJS Backends
|
||||
|
||||
Bereits vorhanden in:
|
||||
- `maerchenzauber/apps/backend/Dockerfile`
|
||||
- `manadeck/backend/Dockerfile`
|
||||
|
||||
---
|
||||
|
||||
# Environment Variables
|
||||
|
||||
## Gemeinsame Variablen (alle Projekte)
|
||||
|
||||
```env
|
||||
NODE_ENV=production
|
||||
```
|
||||
|
||||
## Supabase-basierte Projekte
|
||||
|
||||
```env
|
||||
# Maerchenzauber, Manacore, Memoro, Picture
|
||||
PUBLIC_SUPABASE_URL=https://xxx.supabase.co
|
||||
PUBLIC_SUPABASE_ANON_KEY=eyJxx...
|
||||
SUPABASE_SERVICE_ROLE_KEY=eyJxx... # Nur Backend
|
||||
```
|
||||
|
||||
## PostgreSQL-basierte Projekte
|
||||
|
||||
```env
|
||||
# Manadeck, uLoad
|
||||
DATABASE_URL=postgresql://user:pass@host:5432/db
|
||||
```
|
||||
|
||||
## Projekt-spezifische Variablen
|
||||
|
||||
### Maerchenzauber Backend
|
||||
```env
|
||||
AZURE_OPENAI_ENDPOINT=https://xxx.openai.azure.com
|
||||
AZURE_OPENAI_API_KEY=xxx
|
||||
GOOGLE_GEMINI_API_KEY=xxx
|
||||
REPLICATE_API_TOKEN=xxx
|
||||
GOOGLE_APPLICATION_CREDENTIALS=/path/to/credentials.json
|
||||
```
|
||||
|
||||
### Manadeck Backend
|
||||
```env
|
||||
GOOGLE_GEMINI_API_KEY=xxx
|
||||
```
|
||||
|
||||
### uLoad
|
||||
```env
|
||||
REDIS_URL=redis://localhost:6379
|
||||
STRIPE_SECRET_KEY=sk_live_xxx
|
||||
STRIPE_WEBHOOK_SECRET=whsec_xxx
|
||||
RESEND_API_KEY=re_xxx
|
||||
R2_ACCESS_KEY_ID=xxx
|
||||
R2_SECRET_ACCESS_KEY=xxx
|
||||
R2_BUCKET_NAME=xxx
|
||||
R2_ENDPOINT=https://xxx.r2.cloudflarestorage.com
|
||||
AUTH_SECRET=xxx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Checkliste: Komplettes Self-Hosting
|
||||
|
||||
## Infrastruktur
|
||||
- [ ] VPS bestellt (Hetzner CX21/CX31)
|
||||
- [ ] SSH-Zugang eingerichtet
|
||||
- [ ] Coolify installiert
|
||||
- [ ] Firewall konfiguriert
|
||||
|
||||
## Datenbanken
|
||||
- [ ] PostgreSQL läuft
|
||||
- [ ] Redis läuft (falls benötigt)
|
||||
- [ ] Backups eingerichtet
|
||||
- [ ] Connection Strings notiert
|
||||
|
||||
## Projekte (für jedes)
|
||||
- [ ] Dockerfile erstellt/geprüft
|
||||
- [ ] Environment Variables gesetzt
|
||||
- [ ] Domain konfiguriert
|
||||
- [ ] SSL-Zertifikat aktiv
|
||||
- [ ] Health-Check funktioniert
|
||||
|
||||
## DNS (für jede Domain)
|
||||
- [ ] A-Record auf Server-IP
|
||||
- [ ] www CNAME (optional)
|
||||
- [ ] Propagation geprüft
|
||||
|
||||
## Monitoring
|
||||
- [ ] Logs erreichbar
|
||||
- [ ] Alerting eingerichtet (optional)
|
||||
- [ ] Uptime-Monitoring (optional)
|
||||
|
||||
## Backups
|
||||
- [ ] Datenbank-Backup automatisiert
|
||||
- [ ] Backup-Test durchgeführt
|
||||
- [ ] Offsite-Backup (optional)
|
||||
|
||||
---
|
||||
|
||||
# Empfehlung
|
||||
|
||||
## Für den Start: Option D (Hybrid)
|
||||
|
||||
1. **Supabase** für Datenbank + Auth (Free Tier)
|
||||
2. **Vercel/Netlify** für Landing Pages (Free)
|
||||
3. **Hetzner CX21** für Web Apps + Backends (€4.50/Monat)
|
||||
4. **Cloudflare** für DNS + CDN (Free)
|
||||
|
||||
**Vorteile:**
|
||||
- Schneller Start
|
||||
- Geringe Kosten
|
||||
- Managed Auth & Realtime
|
||||
- Einfache Skalierung später
|
||||
|
||||
## Für Wachstum: Option B (Multi-VPS)
|
||||
|
||||
Wenn Traffic steigt:
|
||||
1. Datenbanken auf eigenen VPS migrieren
|
||||
2. Frontend/Backend trennen
|
||||
3. Load Balancing hinzufügen
|
||||
|
||||
## Für Enterprise: Option C (Kubernetes)
|
||||
|
||||
Wenn benötigt:
|
||||
- Auto-Scaling
|
||||
- Zero-Downtime Deployments
|
||||
- Multi-Region
|
||||
|
||||
---
|
||||
|
||||
# Support & Links
|
||||
|
||||
- **Coolify Docs:** https://coolify.io/docs
|
||||
- **Hetzner:** https://www.hetzner.com/cloud
|
||||
- **Supabase:** https://supabase.com/docs
|
||||
- **K3s:** https://k3s.io
|
||||
- **Traefik:** https://doc.traefik.io/traefik/
|
||||
446
docs/ULOAD-DEPLOYMENT.md
Normal file
446
docs/ULOAD-DEPLOYMENT.md
Normal file
|
|
@ -0,0 +1,446 @@
|
|||
# uload Deployment Guide
|
||||
|
||||
Schritt-für-Schritt Anleitung zum Deployment von uload mit Coolify auf Hetzner VPS.
|
||||
|
||||
## Architektur
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Hetzner VPS │
|
||||
│ ┌───────────────────────────────────────────────────────────┐ │
|
||||
│ │ Coolify │ │
|
||||
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │ │
|
||||
│ │ │ uload │ │ PostgreSQL │ │ Redis │ │ │
|
||||
│ │ │ (Node) │ │ (16) │ │ (7) │ │ │
|
||||
│ │ │ :3000 │ │ :5432 │ │ :6379 │ │ │
|
||||
│ │ └─────────────┘ └─────────────┘ └─────────────────┘ │ │
|
||||
│ │ │ │ │ │ │
|
||||
│ │ └────────────────┴──────────────────┘ │ │
|
||||
│ │ Traefik (SSL/Proxy) │ │
|
||||
│ │ :80 / :443 │ │
|
||||
│ └───────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
https://ulo.ad
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
- [ ] Hetzner VPS (mindestens CX21: 2 vCPU, 4GB RAM, 40GB SSD)
|
||||
- [ ] Domain mit DNS-Zugang (z.B. ulo.ad)
|
||||
- [ ] GitHub Account mit Zugriff auf das Repository
|
||||
- [ ] Accounts für externe Services:
|
||||
- Resend (Email)
|
||||
- Stripe (Payments)
|
||||
- Cloudflare R2 (Storage)
|
||||
|
||||
---
|
||||
|
||||
## Schritt 1: Hetzner VPS einrichten
|
||||
|
||||
### 1.1 Server erstellen
|
||||
|
||||
1. Gehe zu [Hetzner Cloud Console](https://console.hetzner.cloud)
|
||||
2. Erstelle neues Projekt oder wähle bestehendes
|
||||
3. Klicke **Add Server**
|
||||
4. Wähle:
|
||||
- **Location:** Falkenstein oder Nürnberg (DE)
|
||||
- **Image:** Ubuntu 22.04
|
||||
- **Type:** CX21 (2 vCPU, 4GB RAM) oder größer
|
||||
- **SSH Key:** Füge deinen öffentlichen SSH-Key hinzu
|
||||
5. Klicke **Create & Buy Now**
|
||||
6. Notiere die **IP-Adresse**
|
||||
|
||||
### 1.2 Mit Server verbinden
|
||||
|
||||
```bash
|
||||
ssh root@DEINE-SERVER-IP
|
||||
```
|
||||
|
||||
### 1.3 System updaten
|
||||
|
||||
```bash
|
||||
apt update && apt upgrade -y
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Schritt 2: Coolify installieren
|
||||
|
||||
### 2.1 Installation
|
||||
|
||||
```bash
|
||||
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
|
||||
```
|
||||
|
||||
Die Installation dauert ca. 2-5 Minuten.
|
||||
|
||||
### 2.2 Coolify öffnen
|
||||
|
||||
1. Öffne im Browser: `http://DEINE-SERVER-IP:8000`
|
||||
2. Erstelle Admin-Account (E-Mail + Passwort)
|
||||
3. Wähle **Self-hosted** als Instance Type
|
||||
4. Der Server "localhost" wird automatisch hinzugefügt
|
||||
|
||||
---
|
||||
|
||||
## Schritt 3: PostgreSQL Datenbank erstellen
|
||||
|
||||
### 3.1 In Coolify
|
||||
|
||||
1. Klicke **+ New Resource**
|
||||
2. Wähle **Database**
|
||||
3. Wähle **PostgreSQL**
|
||||
4. Konfiguriere:
|
||||
- **Name:** `uload-postgres`
|
||||
- **Version:** `16-alpine`
|
||||
- **Database Name:** `uload`
|
||||
- **Database User:** `uload`
|
||||
- **Password:** (automatisch generiert oder eigenes)
|
||||
5. Klicke **Start**
|
||||
|
||||
### 3.2 Connection String notieren
|
||||
|
||||
Nach dem Start findest du unter **Connect** die Internal URL:
|
||||
|
||||
```
|
||||
postgresql://uload:PASSWORT@uload-postgres:5432/uload
|
||||
```
|
||||
|
||||
**Wichtig:** Kopiere diese URL - du brauchst sie später!
|
||||
|
||||
---
|
||||
|
||||
## Schritt 4: Redis erstellen (optional, aber empfohlen)
|
||||
|
||||
### 4.1 In Coolify
|
||||
|
||||
1. Klicke **+ New Resource**
|
||||
2. Wähle **Database**
|
||||
3. Wähle **Redis**
|
||||
4. Konfiguriere:
|
||||
- **Name:** `uload-redis`
|
||||
- **Version:** `7-alpine`
|
||||
5. Klicke **Start**
|
||||
|
||||
### 4.2 Connection String notieren
|
||||
|
||||
```
|
||||
redis://uload-redis:6379
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Schritt 5: GitHub Repository verbinden
|
||||
|
||||
### 5.1 GitHub App erstellen
|
||||
|
||||
1. In Coolify: Gehe zu **Sources** (linke Sidebar)
|
||||
2. Klicke **+ Add**
|
||||
3. Wähle **GitHub App**
|
||||
4. Klicke **Register GitHub App**
|
||||
5. Du wirst zu GitHub weitergeleitet
|
||||
6. Gib der App einen Namen (z.B. "coolify-uload")
|
||||
7. Klicke **Create GitHub App**
|
||||
8. Installiere die App für dein Repository
|
||||
|
||||
### 5.2 Repository-Zugriff gewähren
|
||||
|
||||
1. Wähle **Only select repositories**
|
||||
2. Wähle `manacore-monorepo`
|
||||
3. Klicke **Install**
|
||||
|
||||
---
|
||||
|
||||
## Schritt 6: uload Application erstellen
|
||||
|
||||
### 6.1 Neue Application
|
||||
|
||||
1. Klicke **+ New Resource**
|
||||
2. Wähle **Application**
|
||||
3. Wähle deine **GitHub App** als Source
|
||||
4. Wähle das Repository `manacore-monorepo`
|
||||
5. Wähle Branch: `main`
|
||||
|
||||
### 6.2 Build-Konfiguration (WICHTIG!)
|
||||
|
||||
Da uload Teil eines Monorepos ist, muss die Build-Konfiguration genau so sein:
|
||||
|
||||
| Einstellung | Wert |
|
||||
|-------------|------|
|
||||
| **Base Directory** | `/` (leer lassen oder `/`) |
|
||||
| **Build Pack** | Dockerfile |
|
||||
| **Dockerfile Location** | `uload/Dockerfile` |
|
||||
| **Port Exposes** | `3000` |
|
||||
|
||||
**Warum `/` als Base Directory?**
|
||||
Das Dockerfile benötigt Zugriff auf:
|
||||
- `uload/apps/web/` (die App)
|
||||
- `packages/shared-*` (gemeinsame Packages)
|
||||
- `pnpm-workspace.yaml` und `pnpm-lock.yaml` (Workspace-Config)
|
||||
|
||||
---
|
||||
|
||||
## Schritt 7: Environment Variables setzen
|
||||
|
||||
### 7.1 In Coolify
|
||||
|
||||
Gehe zu deiner Application → **Environment Variables** → **Add Variable**
|
||||
|
||||
### 7.2 Erforderliche Variablen
|
||||
|
||||
```env
|
||||
# === APP ===
|
||||
NODE_ENV=production
|
||||
PORT=3000
|
||||
HOST=0.0.0.0
|
||||
ORIGIN=https://ulo.ad
|
||||
|
||||
# === DATABASE ===
|
||||
# Von Schritt 3.2 - PostgreSQL Internal URL
|
||||
DATABASE_URL=postgresql://uload:DEIN-PASSWORT@uload-postgres:5432/uload
|
||||
|
||||
# === REDIS (optional) ===
|
||||
# Von Schritt 4.2
|
||||
REDIS_URL=redis://uload-redis:6379
|
||||
|
||||
# === AUTH ===
|
||||
# Generiere mit: openssl rand -base64 32
|
||||
AUTH_SECRET=GENERIERE-EINEN-SICHEREN-STRING-HIER
|
||||
|
||||
# === EMAIL (Resend) ===
|
||||
RESEND_API_KEY=re_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
|
||||
# === PAYMENTS (Stripe) ===
|
||||
STRIPE_SECRET_KEY=sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
|
||||
# === STORAGE (Cloudflare R2) ===
|
||||
R2_ACCESS_KEY_ID=xxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
R2_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
R2_BUCKET_NAME=uload-uploads
|
||||
R2_ENDPOINT=https://xxxxxxxxxx.r2.cloudflarestorage.com
|
||||
```
|
||||
|
||||
### 7.3 AUTH_SECRET generieren
|
||||
|
||||
Auf deinem lokalen Rechner:
|
||||
|
||||
```bash
|
||||
openssl rand -base64 32
|
||||
```
|
||||
|
||||
Kopiere das Ergebnis als `AUTH_SECRET`.
|
||||
|
||||
---
|
||||
|
||||
## Schritt 8: Domain konfigurieren
|
||||
|
||||
### 8.1 DNS-Einträge setzen
|
||||
|
||||
Bei deinem DNS-Provider (z.B. Cloudflare, Namecheap):
|
||||
|
||||
| Type | Name | Value | TTL |
|
||||
|------|------|-------|-----|
|
||||
| A | @ | DEINE-SERVER-IP | 3600 |
|
||||
| A | www | DEINE-SERVER-IP | 3600 |
|
||||
|
||||
### 8.2 Domain in Coolify hinzufügen
|
||||
|
||||
1. Gehe zu deiner Application → **Settings**
|
||||
2. Unter **Domains** klicke **+ Add**
|
||||
3. Gib ein: `ulo.ad`
|
||||
4. Aktiviere: **Generate SSL Certificate** (Let's Encrypt)
|
||||
5. Optional: Füge auch `www.ulo.ad` hinzu mit Redirect
|
||||
|
||||
### 8.3 Warten
|
||||
|
||||
DNS-Änderungen können 5-30 Minuten dauern. SSL-Zertifikate werden automatisch erstellt.
|
||||
|
||||
---
|
||||
|
||||
## Schritt 9: Deployment starten
|
||||
|
||||
### 9.1 Erster Deploy
|
||||
|
||||
1. Gehe zu deiner Application
|
||||
2. Klicke **Deploy**
|
||||
3. Warte auf den Build (ca. 3-5 Minuten)
|
||||
|
||||
### 9.2 Build-Logs überwachen
|
||||
|
||||
Klicke auf das laufende Deployment um die Logs zu sehen.
|
||||
|
||||
**Erfolgreicher Build zeigt:**
|
||||
```
|
||||
✔ done
|
||||
Listening on http://0.0.0.0:3000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Schritt 10: Datenbank-Migration
|
||||
|
||||
### 10.1 Nach erstem Deployment
|
||||
|
||||
Die Datenbank-Tabellen müssen erstellt werden:
|
||||
|
||||
1. In Coolify: Gehe zu deiner Application → **Terminal**
|
||||
2. Oder via SSH:
|
||||
|
||||
```bash
|
||||
# Container-Name finden
|
||||
docker ps | grep uload
|
||||
|
||||
# In Container gehen
|
||||
docker exec -it CONTAINER-NAME sh
|
||||
|
||||
# Migration ausführen
|
||||
npx drizzle-kit push
|
||||
```
|
||||
|
||||
### 10.2 Alternative: Pre-Deploy Command
|
||||
|
||||
In Coolify → Application → **Settings** → **Pre-Deploy Command**:
|
||||
|
||||
```bash
|
||||
cd /app && npx drizzle-kit push
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Schritt 11: Verifizieren
|
||||
|
||||
### 11.1 Health Check
|
||||
|
||||
```bash
|
||||
curl https://ulo.ad/api/health
|
||||
```
|
||||
|
||||
Erwartete Antwort:
|
||||
```json
|
||||
{"status":"ok","timestamp":"2025-11-25T12:00:00.000Z","uptime":123.45}
|
||||
```
|
||||
|
||||
### 11.2 Website öffnen
|
||||
|
||||
Öffne `https://ulo.ad` im Browser.
|
||||
|
||||
---
|
||||
|
||||
## Automatische Deployments
|
||||
|
||||
### Webhook (Standard)
|
||||
|
||||
Coolify erstellt automatisch einen GitHub Webhook. Bei jedem Push auf `main` wird automatisch deployed.
|
||||
|
||||
### Manuelles Deployment
|
||||
|
||||
In Coolify: Application → **Redeploy**
|
||||
|
||||
---
|
||||
|
||||
## Wartung & Monitoring
|
||||
|
||||
### Logs anzeigen
|
||||
|
||||
**In Coolify:**
|
||||
Application → **Logs**
|
||||
|
||||
**Via SSH:**
|
||||
```bash
|
||||
docker logs -f $(docker ps -qf "name=uload")
|
||||
```
|
||||
|
||||
### Container neustarten
|
||||
|
||||
In Coolify: Application → **Restart**
|
||||
|
||||
### Datenbank Backup
|
||||
|
||||
```bash
|
||||
# Manuelles Backup
|
||||
docker exec uload-postgres pg_dump -U uload uload > backup_$(date +%Y%m%d).sql
|
||||
|
||||
# Backup wiederherstellen
|
||||
cat backup_20251125.sql | docker exec -i uload-postgres psql -U uload uload
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Build schlägt fehl
|
||||
|
||||
| Problem | Lösung |
|
||||
|---------|--------|
|
||||
| "Cannot find package" | Prüfe Base Directory (muss `/` sein) |
|
||||
| "pnpm-lock.yaml not found" | Prüfe dass pnpm-lock.yaml im Repo ist |
|
||||
| Timeout beim Build | Erhöhe Build-Timeout in Coolify Settings |
|
||||
|
||||
### Container startet nicht
|
||||
|
||||
| Problem | Lösung |
|
||||
|---------|--------|
|
||||
| "Missing API key" | Prüfe RESEND_API_KEY Environment Variable |
|
||||
| "Cannot connect to database" | Prüfe DATABASE_URL (Internal URL!) |
|
||||
| Port already in use | Prüfe ob alter Container noch läuft |
|
||||
|
||||
### SSL-Zertifikat Fehler
|
||||
|
||||
1. Prüfe DNS-Einträge (A-Record auf Server-IP)
|
||||
2. Warte 5-10 Minuten
|
||||
3. In Coolify: Domain löschen und neu hinzufügen
|
||||
4. Prüfe ob Port 80 erreichbar ist (Firewall)
|
||||
|
||||
### Datenbank-Verbindung fehlgeschlagen
|
||||
|
||||
1. Prüfe ob PostgreSQL-Container läuft
|
||||
2. Verwende **Internal URL** (nicht External!)
|
||||
3. Teste Verbindung:
|
||||
```bash
|
||||
docker exec -it uload-postgres psql -U uload -d uload -c "SELECT 1"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Checkliste Production-Ready
|
||||
|
||||
- [ ] Hetzner VPS erstellt und SSH funktioniert
|
||||
- [ ] Coolify installiert und Admin-Account erstellt
|
||||
- [ ] PostgreSQL läuft und CONNECTION_STRING notiert
|
||||
- [ ] Redis läuft (optional)
|
||||
- [ ] GitHub Repository verbunden
|
||||
- [ ] Application mit korrektem Dockerfile-Pfad erstellt
|
||||
- [ ] Alle Environment Variables gesetzt
|
||||
- [ ] AUTH_SECRET generiert (min. 32 Zeichen)
|
||||
- [ ] DNS A-Records konfiguriert
|
||||
- [ ] Domain in Coolify hinzugefügt
|
||||
- [ ] SSL-Zertifikat aktiv
|
||||
- [ ] Erster Deploy erfolgreich
|
||||
- [ ] Datenbank-Migration ausgeführt
|
||||
- [ ] Health-Check funktioniert (`/api/health`)
|
||||
- [ ] Website erreichbar
|
||||
|
||||
---
|
||||
|
||||
## Dateien im Repository
|
||||
|
||||
| Datei | Beschreibung |
|
||||
|-------|--------------|
|
||||
| `uload/Dockerfile` | Multi-Stage Docker Build |
|
||||
| `uload/docker-compose.yml` | Lokale Entwicklung |
|
||||
| `uload/docker-compose.coolify.yml` | Coolify Deployment |
|
||||
| `uload/docker-compose.prod.yml` | Standalone Production |
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
Bei Problemen:
|
||||
1. Coolify Logs prüfen
|
||||
2. Container Logs prüfen (`docker logs`)
|
||||
3. GitHub Issues: https://github.com/anthropics/claude-code/issues
|
||||
Loading…
Add table
Add a link
Reference in a new issue