3 Commits

Author SHA1 Message Date
stefan c086190097 docs: translate remaining architectural guides to German and standardize formatting
Translated all remaining English architectural documents into German, including ADRs, guides, release notes, and reference materials. Standardized formatting across translated files, updated section headings, and localized inline comments within code examples for consistency.
2026-03-06 14:02:51 +01:00
stefan 4c0ff6008d docs: rewrite Gitea Actions cache tutorial and adapt for localized usage
Revised and translated the guide for enabling Gitea Actions cache to streamline CI/CD workflows. Added localized examples, clarified tool and action caching configurations, and linked relevant resources for improved usability. Removed redundant sections and updated structure for better readability.
2026-03-06 13:54:26 +01:00
stefan 5ab0c9524e docs: update architecture to reflect Proxmox migration and correct network configurations
Revised multiple documents to align with the migration from Incus to Proxmox VE 8.4.10. Updated hypervisor, IP ranges, subnet details, and NAT configurations across all relevant files. Marked Incus sections as historical for clarity. Added AI-Stack setup guide for Proxmox LXC.
2026-03-06 13:50:56 +01:00
34 changed files with 1145 additions and 410 deletions
@@ -3,68 +3,68 @@ type: Reference
status: ACTIVE
owner: Lead Architect
---
# Frontend Architecture & Modularization Strategy
# Frontend-Architektur & Modularisierungsstrategie
**Status:** DRAFT
**Last Updated:** 2026-01-19
**Context:** Migration to Clean Architecture & Feature Modules
**Status:** ENTWURF
**Zuletzt aktualisiert:** 2026-01-19
**Kontext:** Migration zu Clean Architecture & Feature-Modulen
---
## 1. Overview
The frontend architecture of **Meldestelle** is based on **Kotlin Multiplatform (KMP)** with **Compose Multiplatform** for UI. We follow a strict **Clean Architecture** approach to ensure testability, scalability, and separation of concerns.
## 1. Übersicht
Die Frontend-Architektur von **Meldestelle** basiert auf **Kotlin Multiplatform (KMP)** mit **Compose Multiplatform** für die Benutzeroberfläche. Wir folgen einem strikten **Clean Architecture**-Ansatz, um Testbarkeit, Skalierbarkeit und Trennung der Zuständigkeiten sicherzustellen.
## 2. Module Structure
The project is organized into the following layers:
## 2. Modulstruktur
Das Projekt ist in folgende Schichten unterteilt:
### 2.1 Core Modules (`frontend/core`)
Reusable components that are agnostic of specific business features.
* `core-network`: Central HTTP Client configuration (Auth, Logging, ContentNegotiation).
* `core-sync`: Generic synchronization logic (`SyncManager`, `SyncableRepository`).
* `core-ui`: Shared UI components and design system.
### 2.1 Core-Module (`frontend/core`)
Wiederverwendbare Komponenten, die unabhängig von spezifischen Geschäftsfunktionen sind.
* `core-network`: Zentrale HTTP-Client-Konfiguration (Auth, Logging, ContentNegotiation).
* `core-sync`: Generische Synchronisierungslogik (`SyncManager`, `SyncableRepository`).
* `core-ui`: Gemeinsame UI-Komponenten und Design-System.
### 2.2 Feature Modules (`frontend/features`)
Each business domain (e.g., `ping`, `auth`, `events`) resides in its own module.
A feature module MUST follow the **Clean Architecture** package structure:
### 2.2 Feature-Module (`frontend/features`)
Jede Geschäftsdomäne (z.B. `ping`, `auth`, `events`) liegt in ihrem eigenen Modul.
Ein Feature-Modul MUSS die **Clean Architecture** Paketstruktur einhalten:
* `at.mocode.{feature}.feature.domain`
* **Entities:** Pure data classes.
* **Interfaces:** Repository interfaces, Service interfaces.
* **Use Cases:** Business logic (optional, for complex logic).
* **Entitäten:** Reine Datenklassen.
* **Interfaces:** Repository-Interfaces, Service-Interfaces.
* **Use Cases:** Geschäftslogik (optional, für komplexe Logik).
* `at.mocode.{feature}.feature.data`
* **Implementations:** Repository implementations, API Clients.
* **DTOs:** Data Transfer Objects (if different from domain entities).
* **Implementierungen:** Repository-Implementierungen, API-Clients.
* **DTOs:** Data Transfer Objects (wenn von Domain-Entitäten abweichend).
* `at.mocode.{feature}.feature.presentation`
* **ViewModels:** State management.
* **Screens:** Composable functions.
* **ViewModels:** Zustandsverwaltung.
* **Screens:** Composable-Funktionen.
* `at.mocode.{feature}.feature.di`
* **Koin Module:** Dependency injection configuration.
* **Koin-Modul:** Konfiguration der Dependency Injection.
### 2.3 Shells (`frontend/shells`)
Application entry points that wire everything together.
* `meldestelle-portal`: The main web/desktop application.
Anwendungs-Einstiegspunkte, die alles zusammenführen.
* `meldestelle-portal`: Die Haupt-Web-/Desktop-Anwendung.
## 3. Migration Strategy (Transition Phase)
We are currently migrating from a monolithic `clients` package structure to modular feature modules.
## 3. Migrationsstrategie (Übergangsphase)
Wir migrieren aktuell von einer monolithischen `clients`-Paketstruktur zu modularen Feature-Modulen.
**Rules for Migration:**
1. **New Features:** Must be implemented directly in `frontend/features/{name}` using the Clean Architecture structure.
2. **Existing Features:** Will be migrated incrementally.
3. **Coexistence:** During the transition, legacy code in `clients/` is permitted but deprecated.
4. **Dependency Injection:** Legacy code must use the new Koin modules if available.
5. **No Ghost Classes:** Do not duplicate classes. If a class is moved to a feature module, delete the old one in `clients/`.
**Regeln für die Migration:**
1. **Neue Features:** Müssen direkt in `frontend/features/{name}` unter Verwendung der Clean Architecture-Struktur implementiert werden.
2. **Bestehende Features:** Werden schrittweise migriert.
3. **Koexistenz:** Während des Übergangs ist Legacy-Code in `clients/` erlaubt, aber als veraltet markiert.
4. **Dependency Injection:** Legacy-Code muss die neuen Koin-Module verwenden, sofern verfügbar.
5. **Keine Ghost-Klassen:** Klassen nicht duplizieren. Wenn eine Klasse in ein Feature-Modul verschoben wird, muss die alte in `clients/` gelöscht werden.
## 4. Key Decisions (ADRs)
## 4. Wichtige Entscheidungen (ADRs)
### ADR-001: Sync Logic Decoupling
* **Decision:** ViewModels must not depend directly on `SyncManager`.
* **Reason:** To allow easier testing and to hide the complexity of the generic sync mechanism.
* **Implementation:** Introduce a domain service interface (e.g., `PingSyncService`) that wraps the `SyncManager` call.
### ADR-001: Entkopplung der Sync-Logik
* **Entscheidung:** ViewModels dürfen nicht direkt vom `SyncManager` abhängen.
* **Begründung:** Um einfacheres Testen zu ermöglichen und die Komplexität des generischen Sync-Mechanismus zu verbergen.
* **Umsetzung:** Ein Domain-Service-Interface (z.B. `PingSyncService`) einführen, das den `SyncManager`-Aufruf kapselt.
### ADR-002: Feature Module Isolation
* **Decision:** Feature modules should not depend on each other directly if possible.
* **Communication:** Use shared Core modules or loose coupling via interfaces/events if cross-feature communication is needed.
### ADR-002: Feature-Modul-Isolation
* **Entscheidung:** Feature-Module sollten nach Möglichkeit nicht direkt voneinander abhängen.
* **Kommunikation:** Gemeinsame Core-Module oder lose Kopplung über Interfaces/Events verwenden, wenn modulübergreifende Kommunikation nötig ist.
---
**Approved by:** Lead Architect
**Freigegeben von:** Lead Architect
@@ -2,200 +2,173 @@
type: Guide
status: ACTIVE
owner: DevOps Engineer
source: https://about.gitea.com/resources/tutorials/enable-gitea-actions-cache-to-accelerate-cicd/
author: Nanguan Lin
date: 2023-10-26
---
# Enable Gitea Actions Cache to Accelerate CI/CD
[![](/gitea-text.svg)Gitea](/)
# Gitea Actions Cache aktivieren und CI/CD beschleunigen
Open main menu
> **Quelle:** [Gitea Tutorial](https://about.gitea.com/resources/tutorials/enable-gitea-actions-cache-to-accelerate-cicd/) — übersetzt und für dieses Projekt adaptiert.
Products
## Einleitung
[Gitea Cloud](/products/cloud/)
Caching ist ein wesentlicher Bestandteil moderner Softwareentwicklung. In diesem Dokument wird erläutert, wie der Gitea Actions Cache (Giteas integriertes CI/CD-System) aktiviert wird, um Build-Zeiten zu reduzieren.
Get a DevOps instance in minutes
Gitea Actions nutzt zwei Arten von Caches:
[Gitea Enterprise](/products/gitea-enterprise/)
1. **Runner Tool Cache** — wird beim Start eines Runners angelegt. Der Runner erstellt ein Volume namens `act-toolcache`, das ins lokale Dateisystem eingehängt wird (standardmäßig `/opt/hostedtoolcache`). Wenn eine Action wie `setup-go` verwendet wird, lädt sie eine Go-Version herunter und speichert sie in diesem Volume — dadurch werden redundante Downloads verhindert.
Run an enhanced DevOps instance yourself
2. **Action Cache (`actions/cache`)** — feingranularer Cache, der aus GitHub Actions stammt, aber vollständig mit Gitea Actions kompatibel ist. Er verwendet einen Hash-Schlüssel zum Abrufen eines spezifischen Caches. Details zur Konfiguration: [GitHub Offizielle Dokumentation](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows).
[Gitea](/products/gitea/)
---
Run a free DevOps instance yourself
## Runner Tool Cache verwenden
[Gitea Actions](/products/runner/)
Die Einrichtung ist unkompliziert: Die Umgebungsvariable `RUNNER_TOOL_CACHE` im Workflow setzen — der Act Runner erkennt diese automatisch und speichert den Download-Cache dort.
Automate your Gitea workflows
> **Hinweis:** `/toolcache` ist im Upstream-Projekt `nektos/act` [hardcodiert](https://github.com/nektos/act/blob/4fae81efe4cdd9e09e7ef8e874a2d63b1ed98524/pkg/runner/run_context.go#L137-L139) und kann nicht geändert werden.
[Tea](/products/tea/)
**Beispiel-Konfiguration:**
Command line tool to interact with Gitea Servers
```yaml
jobs:
build:
env:
RUNNER_TOOL_CACHE: /toolcache
# ...
```
Resources
Alternativ kann ein **Docker Volume** explizit gemountet werden:
[News](/news)
```yaml
jobs:
build:
runs-on: ubuntu-latest
container:
image: dein_docker_image
volumes:
- dein_docker_volume:/opt/hostedtoolcache # Standard-Cache-Pfad des Runners
```
Vergleich der Download-Zeiten:
- Vor dem Cache: ![Download-Zeit vorher](https://about.gitea.com/img/tutorials/enable-gitea-actions-cache-to-accelerate-cicd/download_before.png)
- Nach dem Cache: ![Download-Zeit nachher](https://about.gitea.com/img/tutorials/enable-gitea-actions-cache-to-accelerate-cicd/download_after.png)
What happened around CommitGo and Gitea
---
## Cache Action verwenden
[Documentations](https://docs.gitea.com)
Der Runner verwendet einen Cache-Server zum Speichern von Schlüssel/Wert-Paaren. Dieser ist standardmäßig aktiviert — `actions/cache` kann direkt genutzt werden.
Documentation for Gitea and related tools
**Beispiel-Konfiguration:**
[Tutorials](/resources/tutorials/)
```yaml
name: Caching mit Go
on: push
jobs:
Cache-Go:
name: Cache Go
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: '>=1.20.1'
- uses: https://gitea.com/actions/go-hashfiles@v0.0.1
id: hash-go
with:
patterns: |
go.mod
go.sum
- name: Go Cache
id: cache-go
uses: actions/cache@v3
with:
path: |
/dein_cache_pfad
key: go_path-${{ steps.hash-go.outputs.hash }}
restore-keys: |-
go_cache-${{ steps.hash-go.outputs.hash }}
```
Tutorals and advice on using Gitea
Hier wird `go-hashfiles` verwendet, um einen Hash aus `go.mod` und `go.sum` zu erzeugen. Der Cache-Pfad und Schlüssel sind je nach Programmiersprache anzupassen.
[Blog](/blog)
> **Wichtige Hinweise:**
>
> 1. Bei Verwendung des Runners mit Docker kann es zu Netzwerkproblemen mit dem Cache-Server kommen. In diesem Fall muss `host` und `port` des Cache-Servers in der `config.yaml` des Act Runners angepasst werden. Details: [Gitea Dokumentation](https://docs.gitea.com/usage/actions/act-runner#configuring-cache-when-starting-a-runner-using-docker-image)
> 2. Die eingebaute Funktion `hashFiles` im Workflow-YAML wird in Gitea Actions aktuell **nicht** unterstützt. Als Alternative: [`go-hashfiles`](https://gitea.com/actions/go-hashfiles) (gepflegt von den Gitea-Maintainern).
Release notes and updates about Gitea Products
---
Community
## Vollständiges Beispiel
[Forum](https://forum.gitea.com)
Annahme: Es wird eine App namens `Hello-Gitea` mit Go gebaut, mit aktiviertem Gitea Actions Workflow für jeden Push.
Find or help out with community support
Workflow-Datei (auch verfügbar [auf Gitea](https://gitea.com/lng2020/cache_example/src/branch/main/.gitea/workflows/cache.yaml)):
[Chatroom](https://discord.gg/gitea)
Chat with the community
[Open Source](https://github.com/go-gitea/gitea)
View Gitea code and contribute development
[Community Blog](https://blog.gitea.com)
Release notes and updates about Gitea
[Translation](https://translate.gitea.com)
Help translate Gitea
[Supporters](/community/supporters/)
View supporters of Gitea
[Pricing](/pricing)[Cloud](https://cloud.gitea.com)
[Sign In](https://gitea.com/user/login)     [Contact Us](/contact/contact)
![](/img/tutorials/enable-gitea-actions-cache-to-accelerate-cicd/cover.jpg)
# Enable Gitea Actions Cache to Accelerate CI/CD
![](https://github.com/lng2020.png)
[Nanguan Lin](https://github.com/lng2020)
2023-10-26
4 min read
[CI/CD](/resources/tutorials?category=cicd)
## Introduction
Caching is a vital aspect of modern computer science. Today, we will discuss enabling Gitea Actions(Gitea's built-in CI/CD) cache to speed up CI/CD.
Gitea Actions utilizes two types of caches. The first is the Runner Tool Cache, created when launching a runner. This runner creates a volume named `act-toolcache`, which is mounted to the local file system(usually `/opt/hostedtoolcache`). When an action like `setup-go` is used, it downloads and installs a version of Go, storing it in this special volume, thus preventing redundant downloads of dependencies.
The second type is more fine-grained. Originating from Github Actions but compatible with Gitea Actions, it's called `action/cache`. This action uses a hash key to retrieve the specific cache. For more specific information and detailed configuration about this action, refer to this [Github Offical Doc](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows). In this tutorial, we will enable both types of caches to accelerate CI/CD.
## Use Runner Tool Cache
The process is straightforward. Just add an `env` variable called `RUNNER_TOOL_CACHE` in your Gitea action workflow, and the Gitea act runner will automatically detect this environment and store the download cache there.
**Notice**: For now, `/toolcache` is [hardcoded](https://github.com/nektos/act/blob/4fae81efe4cdd9e09e7ef8e874a2d63b1ed98524/pkg/runner/run_context.go#L137-L139) in the upstream project `nektos/act`. So it cannot be changed.
An example configuration:
`jobs: build: env: RUNNER_TOOL_CACHE: /toolcache ...`
Alternatively, you can use `Docker Volume` to specifically mount the cache volume:
`jobs: build: runs-on: ubuntu-latest container: image: your_docker_image volumes: - your_docker_volumn:/opt/hostedtoolcache # this is where Runner store their cache default`
download time before: ![download_before](/img/tutorials/enable-gitea-actions-cache-to-accelerate-cicd/download_before.png)
download time after: ![download_after](/img/tutorials/enable-gitea-actions-cache-to-accelerate-cicd/download_after.png)
## Use Cache Action
The Runner uses a cache server to store the key/value pair cache. The cache server is enabled by default. So You can directly use the `action/cache`.
An example configuration:
`name: Caching with Go on: push jobs: Cache-Go: name: Cache Go runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-go@v3 with: go-version: '>=1.20.1' - uses: https://gitea.com/actions/go-hashfiles@v0.0.1 id: hash-go with: patterns: | go.mod go.sum - name: cache go id: cache-go uses: actions/cache@v3 with: # Specify with your cache path path: | /your_cache_path key: go_path-${{ steps.hash-go.outputs.hash }} restore-keys: |- go_cache-${{ steps.hash-go.outputs.hash }}`
This example utilizes a go cache and `go-hashfiles` to generate a hash. You should specify your cache path according to your programming language and define the key in any form you like.
**Notice**
1. If you are running the Runner with docker. You may encounter network issue with the cache server. You should change the cache server host and port in `config.yaml` for your Act Runner. The configuration is explained [here](https://docs.gitea.com/usage/actions/act-runner#configuring-cache-when-starting-a-runner-using-docker-image) in detail.
2. The built-in function `hashFiles` in the workflow yaml is not supported in Gitea Actions right now. You can use [`go-hashfiles`](https://gitea.com/actions/go-hashfiles)(maintained by Gitea maintainers) or other alternatives instead.
## A Complete Example
Let's use an example to demonstrate how to utilize these two types of caches in a real development environment.
Assume we're going to build an app called `Hello-Gitea` using Go, and we enable the Gitea Actions workflow for every Push.
Here is the workflow yaml(this file is also available [on Gitea website](https://gitea.com/lng2020/cache_example/src/branch/main/.gitea/workflows/cache.yaml))
`name: Test Cache on: push jobs: TestCache: env: RUNNER_TOOL_CACHE: /toolcache # Runner Tool Cache name: Cache Go runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-go@v3 with: go-version: '>=1.20.1' - name: Get go-hashfiles uses: https://gitea.com/actions/go-hashfiles@v0.0.1 id: hash-go with: patterns: |- go.mod go.sum - name: Echo hash run: echo ${{ steps.hash-go.outputs.hash }} - name: Cache go id: cache-go uses: https://github.com/actions/cache@v3 # Action cache with: # specify with your GOMODCACHE and GOCACHE path: |- /root/go/pkg/mod /root/.cache/go-build key: go_cache-${{ steps.hash-go.outputs.hash }} restore-keys: |- go_cache-${{ steps.hash-go.outputs.hash }} - name: Build run: go build -v . - name: Test run: go test -v ./...`
After setting everything up, we can see how these caches are utilized.
![tool cache](/img/tutorials/enable-gitea-actions-cache-to-accelerate-cicd/tool_cache.png)
![action cache](/img/tutorials/enable-gitea-actions-cache-to-accelerate-cicd/action_cache.png)
## FAQ
Q: Why should I specify `RUNNER_TOOL_CACHE: /toolcache` to make the Runner Tool Cache work? It seems like it should cache the file by default
A: It's an upstream issue of `nektos/act`. See the [issue](https://gitea.com/gitea/act_runner/issues/70) for more details.
Q: Can different act runners on the same host share the Runner Tool Cache?
A: Yes, they can. To do so, use a Docker volume to map the cache directory.
## Footer
![SOC 2 Type 2 Certified](/img/SOC2-blue.png)
Private, Fast, Reliable DevOps Platform
[LinkedIn](https://linkedin.com/company/commitgo)[X](https://twitter.com/giteaio)[GitHub](https://github.com/go-gitea/gitea)[Gitea](https://gitea.com/gitea)
© 2026 CommitGo, Inc. All rights reserved.
### Products
* [Gitea Cloud](/products/cloud)
* [Gitea Enterprise](/products/gitea-enterprise)
* [Gitea](/products/gitea)
* [Gitea Runner](/products/runner)
* [Tea Command-line Tool](/products/tea)
### Support
* [Pricing](/pricing/)
* [Documentation](https://docs.gitea.com)
* [Tutorials](/resources/tutorials/)
* [API](https://docs.gitea.com/api/1.21/)
* [Blog](https://blog.gitea.com)
* [Forum](https://forum.gitea.com)
* [Chatroom](https://discord.gg/gitea)
### About Us
* [What is DevOps](/about/devops)
* [Why Gitea](/about/whygitea)
* [Contact Us](/contact/contact)
* [Compliance](/about/compliance)
### Legal
* [Privacy](/privacy-policy/)
* [Terms](/terms-of-service/)
```yaml
name: Test Cache
on:
push
jobs:
TestCache:
env:
RUNNER_TOOL_CACHE: /toolcache # Runner Tool Cache
name: Cache Go
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: '>=1.20.1'
- name: go-hashfiles laden
uses: https://gitea.com/actions/go-hashfiles@v0.0.1
id: hash-go
with:
patterns: |-
go.mod
go.sum
- name: Hash ausgeben
run: echo ${{ steps.hash-go.outputs.hash }}
- name: Go Cache
id: cache-go
uses: https://github.com/actions/cache@v3 # Action Cache
with:
path: |-
/root/go/pkg/mod
/root/.cache/go-build
key: go_cache-${{ steps.hash-go.outputs.hash }}
restore-keys: |-
go_cache-${{ steps.hash-go.outputs.hash }}
- name: Build
run: go build -v .
- name: Test
run: go test -v ./...
```
Nach der Einrichtung sind beide Cache-Typen aktiv:
- Tool Cache: ![Tool Cache](https://about.gitea.com/img/tutorials/enable-gitea-actions-cache-to-accelerate-cicd/tool_cache.png)
- Action Cache: ![Action Cache](https://about.gitea.com/img/tutorials/enable-gitea-actions-cache-to-accelerate-cicd/action_cache.png)
---
## Häufige Fragen (FAQ)
**F: Warum muss `RUNNER_TOOL_CACHE: /toolcache` explizit gesetzt werden? Sollte das nicht standardmäßig funktionieren?**
A: Das ist ein Upstream-Problem von `nektos/act`. Details: [Issue #70](https://gitea.com/gitea/act_runner/issues/70).
---
**F: Können mehrere Act Runner auf demselben Host den Runner Tool Cache teilen?**
A: Ja. Dazu ein Docker Volume verwenden und das Cache-Verzeichnis einmappen.
@@ -73,7 +73,7 @@ Wir validieren die gesamte Architektur-Kette (Frontend -> Gateway -> Service ->
4. **Infrastructure Setup (Home-Server):**
* Hardware: Minisforum MS-R1 (ARM64, 12 Cores, 10G LAN) ✅ **GELIEFERT (07.02.2026)**.
* OS: Debian 12 (Vendor Variant) als Host.
* Hypervisor: **Incus** (LXC/LXD Fork).
* Hypervisor: **Proxmox VE 8.4.10** (`pve.mo-code.at`).
* Virtualization Strategy:
* `infra-gitea` (LXC Container): Gitea + Actions Runner (Native ARM Builds).
* `docker-host-prod` (VM): Debian VM als Docker Host für den Meldestelle-Stack (Isolation, keine Nesting-Probleme).
@@ -0,0 +1,431 @@
---
type: Guide
status: ACTIVE
owner: DevOps Engineer
---
# Installations-Anleitung: Lokaler AI-Stack auf Zora (MS-R1)
> **Ziel:** Ollama + Open WebUI als isolierter Proxmox LXC-Container auf Zora.
> Vollständig lokal, datenschutzkonform, erreichbar via Pangolin-Tunnel.
> **Datum:** 2026-03-06 | **Aktualisiert:** 2026-03-06 (Incus → Proxmox)
---
## Hardware-Profil: CIX P1 (CP8180) — Was steckt drin?
| Komponente | Detail | Bedeutung für KI |
|---|---|---|
| **CPU** | 12 Cores: 4x Cortex-X4 (fast, ~2.6GHz) + 4x A720 (medium) + 4x A520 (slow) | Tri-Cluster → großzügige Kern-Zuweisung sinnvoll |
| **GPU** | Arm Immortalis-G720 MC10 | Vulkan 1.3 — experimentelle GPU-Beschleunigung möglich |
| **RAM** | 64 GB LPDDR5 5500MHz | Auch 70B-Modelle laufen vollständig im RAM! |
| **NPU** | CIX P1 integrierte NPU | ⚠️ Aktuell kein Ollama/llama.cpp-Support — Zukunft |
| **OS** | Proxmox VE 8.4.10 | Hypervisor auf Debian-Basis, ARM64-native |
### CPU-Kerne im Detail
```
Cores 0 3 → Cortex-A520 (Efficiency / langsam)
Cores 4 7 → Cortex-A720 (Balanced / mittel)
Cores 811 → Cortex-X4 (Performance / schnell)
```
Der AI-Container bekommt 10 Kerne (211), Proxmox behält Kern 01 für Host-Betrieb.
CPU-Governor auf `performance` für die X4-Kerne maximiert den Inferenz-Durchsatz.
---
## Architektur-Entscheidung: Warum separater Proxmox LXC-Container?
```
Zora — Proxmox 8.4.10 (10.0.0.20)
├── VM 102 gitea-runner (10.0.0.23) ← Gitea CI/CD Runner
├── VM 110 meldestelle-host (10.0.0.50) ← Docker App-Stack
├── LXC 100 pangolin-client ← Pangolin Tunnel
├── LXC 101 gitea (10.0.0.22) ← Gitea Server
├── LXC 103 immich ← Immich
└── LXC 111 ai-stack (10.0.0.60) ← Ollama + Open WebUI ← NEU
```
**Begründung:** Modelle (540 GB pro Modell) wachsen unkontrolliert.
Isolierter LXC-Container schützt den App-Stack vor RAM/CPU-Hunger der KI.
Unabhängige Updates — Ollama-Modelle liegen im Container-Volume, nicht im Git-Repo.
---
## Phase 1 — Proxmox Host vorbereiten
### 1.1 — CPU Governor auf Performance setzen (auf Proxmox-Node)
```bash
# SSH auf den Proxmox-Node
ssh root@10.0.0.20
# oder: ssh root@pve.mo-code.at
# cpufrequtils installieren
apt-get install -y cpufrequtils
# Alle 12 Kerne auf Performance
for i in $(seq 0 11); do
cpufreq-set -c $i -g performance
done
# Prüfen
cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor
# Erwartete Ausgabe: 12x "performance"
# Persistent machen
tee /etc/systemd/system/cpu-performance.service > /dev/null <<'EOF'
[Unit]
Description=Set CPU Governor to Performance
After=multi-user.target
[Service]
Type=oneshot
ExecStart=/bin/bash -c 'for i in $(seq 0 11); do cpufreq-set -c $i -g performance; done'
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now cpu-performance.service
```
### 1.2 — Huge Pages aktivieren (bessere RAM-Nutzung für große Modelle)
```bash
# Auf dem Proxmox-Node:
echo "vm.nr_hugepages = 512" >> /etc/sysctl.conf
sysctl -p
```
---
## Phase 2 — Proxmox LXC-Container erstellen
> ️ **Alle Befehle laufen auf dem Proxmox-Node** (`ssh root@10.0.0.20`)
> Alternativ: Proxmox Web-UI unter `https://pve.mo-code.at:8006`
### 2.1 — Debian 12 Template herunterladen
```bash
# Template-Liste aktualisieren
pveam update
# Debian 12 ARM64 Template suchen und herunterladen
pveam available --section system | grep debian-12.*arm64
pveam download local debian-12-standard_12.7-1_arm64.tar.zst
```
### 2.2 — Container erstellen (CT 111)
```bash
pct create 111 local:vztmpl/debian-12-standard_12.7-1_arm64.tar.zst \
--hostname ai-stack \
--arch aarch64 \
--cores 10 \
--memory 49152 \
--swap 4096 \
--rootfs local-lvm:200 \
--net0 name=eth0,bridge=vmbr0,ip=10.0.0.60/24,gw=10.0.0.138,firewall=1 \
--nameserver 10.0.0.138 \
--searchdomain mo-code.at \
--unprivileged 1 \
--features nesting=1 \
--password
# Container starten
pct start 111
# Status prüfen
pct status 111
pct list
```
### 2.3 — CPU-Pinning konfigurieren (Performance-Kerne für KI)
```bash
# Kerne 211 dem Container zuweisen (Kerne 811 = X4 Performance-Kerne!)
# In der Container-Config:
echo "cpulimit: 10" >> /etc/pve/lxc/111.conf
echo "cpuunits: 1024" >> /etc/pve/lxc/111.conf
# Alternativ via Web-UI:
# CT 111 → Options → CPU Limit: 10 Cores
```
### 2.4 — In Container einloggen und Basis-Setup
```bash
# Direkt per pct:
pct enter 111
# Im Container:
apt-get update && apt-get upgrade -y
apt-get install -y curl wget git htop nano ca-certificates gnupg lsb-release
# Docker installieren (für Open WebUI)
curl -fsSL https://get.docker.com | sh
systemctl enable --now docker
# Benutzer anlegen
useradd -m -s /bin/bash aiuser
usermod -aG docker aiuser
```
---
## Phase 3 — Ollama installieren & optimieren
### 3.1 — Ollama installieren
```bash
# Im Container (pct enter 111):
curl -fsSL https://ollama.com/install.sh | sh
# ARM64 wird automatisch erkannt
ollama --version
```
### 3.2 — Ollama-Service konfigurieren (Performance-Tuning)
```bash
mkdir -p /etc/systemd/system/ollama.service.d/
cat > /etc/systemd/system/ollama.service.d/override.conf << 'EOF'
[Service]
# Auf allen Interfaces lauschen (nicht nur localhost)
Environment="OLLAMA_HOST=0.0.0.0:11434"
# Alle zugewiesenen CPU-Kerne nutzen
Environment="OLLAMA_NUM_THREADS=10"
# Modelle 24h im RAM halten (kein ständiges Laden)
Environment="OLLAMA_KEEP_ALIVE=24h"
# Bis zu 2 Modelle gleichzeitig geladen halten
Environment="OLLAMA_MAX_LOADED_MODELS=2"
# Flash Attention aktivieren (schnellere Verarbeitung langer Kontexte)
Environment="OLLAMA_FLASH_ATTENTION=1"
# Kontext-Größe erhöhen für längere Gespräche
Environment="OLLAMA_MAX_CONTEXT_LENGTH=8192"
EOF
systemctl daemon-reload
systemctl restart ollama
systemctl status ollama
```
### 3.3 — Vulkan GPU-Beschleunigung (Immortalis-G720)
> ⚠️ **Experimentell** — CIX P1 + Immortalis-G720 Vulkan-Treiber sind noch nicht
> vollständig im Mainline-Kernel. Zuerst ohne Vulkan starten, später nachrüsten.
```bash
# Prüfen ob Vulkan-Device sichtbar ist
apt-get install -y vulkan-tools
vulkaninfo --summary 2>/dev/null | grep -i "GPU\|device\|driver"
# Falls Vulkan verfügbar — in override.conf ergänzen:
# Environment="OLLAMA_GPU_LAYERS=999"
# → Dann ollama neu starten und testen
```
---
## Phase 4 — Modelle herunterladen
### Empfohlene Modelle für 64 GB ARM64
```bash
# Im Container:
ollama pull llama3.1:8b # 5 GB — schnell, Allrounder, Deutsch OK
ollama pull qwen2.5-coder:14b # 9 GB — Beste Code-Qualität für IDEA!
ollama pull nomic-embed-text # 300MB — Pflicht für RAG/Embeddings
# Optional (wenn du mehr Power willst):
ollama pull llama3.1:70b # 40 GB — Maximale Qualität, läuft in 64GB RAM!
ollama pull qwen2.5:32b # 20 GB — Gute Balance Qualität/Speed
# Testen:
ollama run llama3.1:8b "Erkläre mir Spring Boot in einem Satz auf Deutsch"
```
### Modell-Entscheidungshilfe
| Modell | RAM | Speed | Qualität | Empfehlung |
|---|---|---|---|---|
| `llama3.1:8b` | 5 GB | ⚡⚡⚡ | ★★★ | Täglicher Chat, schnelle Antworten |
| `qwen2.5-coder:14b` | 9 GB | ⚡⚡ | ★★★★ | **IDEA-Integration, Kotlin/Java Code** |
| `qwen2.5:32b` | 20 GB | ⚡ | ★★★★★ | Tiefe Analysen, Architektur-Fragen |
| `llama3.1:70b` | 40 GB | 🐢 | ★★★★★ | Maximale Qualität, geduldige Anfragen |
| `nomic-embed-text` | 300 MB | ⚡⚡⚡ | RAG | **Pflicht für Docs-RAG** |
---
## Phase 5 — Open WebUI installieren
```bash
# Im Container (pct enter 111):
docker run -d \
--name open-webui \
--restart always \
-p 3001:8080 \
-v open-webui:/app/backend/data \
-e OLLAMA_BASE_URL=http://host.docker.internal:11434 \
--add-host=host.docker.internal:10.0.0.60 \
ghcr.io/open-webui/open-webui:main
# Prüfen:
docker ps
curl http://localhost:3001
```
### 5.1 — Open WebUI RAG-Konfiguration
```
Browser: http://10.0.0.60:3001
1. Admin-Account anlegen (erster Login)
2. Settings → Admin Panel → Connections
→ Ollama URL: http://10.0.0.60:11434 ✓
3. Settings → Admin Panel → Documents
→ Embedding Model: nomic-embed-text
→ Chunk Size: 1500
→ Chunk Overlap: 150
4. Workspace → Documents → Upload
→ Deine /docs/**/*.md Dateien hochladen
→ Besonders: 04_Agents/Playbooks/, 01_Architecture/adr/
```
---
## Phase 6 — Pangolin-Route konfigurieren
| Route | Ziel | Port | Sichtbarkeit |
|---|---|---|---|
| `ai.mo-code.at` | `10.0.0.60` | `3001` | Nur intern / VPN! |
> 🔒 **Sicherheit:** Open WebUI NIEMALS ohne Auth öffentlich exponieren.
> Pangolin-Zugang nur via VPN oder mit Basic-Auth absichern.
---
## Phase 7 — IntelliJ IDEA Integration
### Option A: Continue.dev Plugin (empfohlen für Code-Completion)
```
1. IDEA → Settings → Plugins → "Continue" installieren
2. Continue-Konfiguration öffnen (~/.continue/config.json):
```
```json
{
"models": [
{
"title": "Zora-Coder (qwen2.5)",
"provider": "ollama",
"model": "qwen2.5-coder:14b",
"apiBase": "http://10.0.0.60:11434"
},
{
"title": "Zora-Chat (llama3.1)",
"provider": "ollama",
"model": "llama3.1:8b",
"apiBase": "http://10.0.0.60:11434"
}
],
"tabAutocompleteModel": {
"title": "Zora-Autocomplete",
"provider": "ollama",
"model": "qwen2.5-coder:14b",
"apiBase": "http://10.0.0.60:11434"
},
"embeddingsProvider": {
"provider": "ollama",
"model": "nomic-embed-text",
"apiBase": "http://10.0.0.60:11434"
}
}
```
```
3. In IDEA:
- Ctrl+I → Chat öffnen (Inline-Fragen im Code)
- Ctrl+Shift+I → Tab Autocomplete aktivieren
- Alle Daten bleiben auf Zora — kein Cloud-Kontakt!
```
### Option B: JetBrains AI Assistant mit lokalem Modell
```
Settings → Tools → AI Assistant
→ "Use custom AI provider"
→ Endpoint: http://10.0.0.60:11434/v1
→ Model: qwen2.5-coder:14b
→ API Key: ollama (beliebiger String)
```
---
## Zukunft: NPU-Beschleunigung
Der CIX P1 hat eine integrierte NPU, die aktuell **nicht von Ollama/llama.cpp unterstützt** wird.
**Roadmap:**
- `llama.cpp` arbeitet an OpenCL/Vulkan-Backend → Immortalis-G720 wird profitieren
- CIX P1 NPU-Treiber müssen von CIX Technology als Open-Source freigegeben werden
- **Empfehlung:** System ohne NPU in Betrieb nehmen, NPU-Support nachrüsten sobald verfügbar
**Monitoring:**
- https://github.com/ollama/ollama/issues (Filter: "vulkan", "arm64")
- https://github.com/ggml-org/llama.cpp/issues (Filter: "vulkan")
---
## Quick-Reference: Wichtige Befehle
```bash
# Container verwalten (auf Proxmox-Node: ssh root@10.0.0.20)
pct start 111
pct stop 111
pct enter 111
pct status 111
# Modelle verwalten (im Container)
ollama list # Installierte Modelle
ollama pull <modell> # Neues Modell herunterladen
ollama rm <modell> # Modell löschen
ollama ps # Laufende Modelle
# Logs
journalctl -u ollama -f # Ollama-Logs live
docker logs -f open-webui # Open WebUI Logs
# Performance prüfen
htop # CPU/RAM-Auslastung
ollama ps # Welches Modell läuft, RAM-Nutzung
```
---
## Netz-Übersicht nach diesem Setup
```
Zora — Proxmox 8.4.10 (10.0.0.20)
├── VM 102 gitea-runner 10.0.0.23 Gitea CI/CD Runner
├── VM 110 meldestelle-host 10.0.0.50 Docker App-Stack
├── LXC 101 gitea 10.0.0.22 Gitea Server
├── LXC 103 immich Immich
└── LXC 111 ai-stack 10.0.0.60 Ollama :11434 | Open WebUI :3001
Pangolin-Tunnel:
├── ai.mo-code.at → 10.0.0.60:3001 (Open WebUI — nur intern/VPN)
├── api.mo-code.at → 10.0.0.50:8081 (API Gateway)
└── auth.mo-code.at → 10.0.0.50:8180 (Keycloak)
```
@@ -57,9 +57,11 @@ sudo iptables -t nat -A POSTROUTING -s 10.0.6.0/24 ! -d 10.0.6.0/24 -j MASQUERAD
Aufgrund der ARM64-Architektur und Kernel-Einschränkungen wurden folgende Anpassungen vorgenommen:
* **Firewall-Modus:** Das System nutzt `iptables-legacy`, da moderne NFT-Module teilweise fehlen.
* **NAT-Regel:** Da Incus die Firewall nicht automatisch verwalten kann, muss der Internetzugriff für Container manuell maskiert werden:
`sudo iptables -t nat -A POSTROUTING -s 10.0.6.0/24 ! -d 10.0.6.0/24 -j MASQUERADE`
* **Hypervisor:** Proxmox VE 8.4.10 läuft auf Zora (`pve.mo-code.at`, IP `10.0.0.20`).
* **Netz-Bridge:** `vmbr0`, Subnetz `10.0.0.0/24`, Gateway `10.0.0.138`.
* **Firewall-Modus:** Proxmox verwaltet Firewall und NAT automatisch via `vmbr0` — keine manuelle iptables-Regel nötig.
> ️ Frühere Einträge zu Incus/iptables-MASQUERADE sind historisch (Testbetrieb Feb 2026) und wurden durch Proxmox abgelöst.
## Wichtige Befehle
@@ -70,7 +72,11 @@ Aufgrund der ARM64-Architektur und Kernel-Einschränkungen wurden folgende Anpas
## 2. Incus_Konfiguration&Bedienungsanleitung.md
# Infrastruktur-Dokumentation: Incus Virtualisierung
> ⚠️ **HISTORISCH (Testbetrieb Feb 2026) — ABGELÖST durch Proxmox VE 8.4.10**
> Diese Sektion dokumentiert den ursprünglichen Incus-Testbetrieb. Zora läuft heute auf Proxmox.
> Aktueller Stand: `SSoT_Konfigurations-Masterplan_Zora.md`
# Infrastruktur-Dokumentation: Incus Virtualisierung (HISTORISCH)
## 1. Virtualisierungs-Strategie
@@ -6,44 +6,112 @@ owner: DevOps Engineer
# SSoT Konfigurations-Masterplan für Zora (ARM64)
## 1. System-Umgebung (Infrastruktur)
| Parameter | Wert | Erklärung |
|:-------------------|:--------------|:-------------------------------------------------|
| **Architektur** | `linux/arm64` | Native Architektur von Zora (Host). |
| **Host-IP (Zora)** | `10.0.6.1` | Gateway für alle Container (Bridge `incusbr0`). |
| **Projekt-IP** | `10.0.6.50` | Feste IP für den Docker-Host `prod-meldestelle`. |
| **DNS-Server** | `10.0.6.1` | Zora übernimmt die Namensauflösung. |
| Parameter | Wert | Erklärung |
|:-----------------------|:----------------|:-------------------------------------------------------------|
| **Architektur** | `linux/arm64` | Native Architektur von Zora (CIX P1 / CP8180). |
| **Hypervisor** | Proxmox VE 8.4.10 | `pve.mo-code.at` — Web-UI: `https://pve.mo-code.at:8006` |
| **Proxmox-Node-IP** | `10.0.0.20` | SSH: `ssh root@10.0.0.20` |
| **Netz-Bridge** | `vmbr0` | Alle VMs und Container im Subnetz `10.0.0.0/24` |
| **Gateway (Router)** | `10.0.0.138` | Standard-Gateway für alle VMs/Container |
### VM & Container Übersicht
| ID | Name | Typ | IP | Start-Reihenfolge | Zweck |
|:----|:------------------|:-----|:--------------|:------------------|:-----------------------------|
| 100 | pangolin-client | LXC | `10.0.0.21` | order=1, up=30 | Pangolin Tunnel Client |
| 101 | gitea | LXC | `10.0.0.22` | order=2, up=30 | Gitea Server |
| 102 | gitea-runner | VM | `10.0.0.23` | — | Gitea CI/CD Runner (aarch64) |
| 103 | immich | LXC | `10.0.0.24` | order=3, up=30 | Immich Foto-Server |
| 110 | meldestelle-host | VM | `10.0.0.50` | — | Docker App-Stack |
| 111 | ai-stack | LXC | `10.0.0.60` | — | Ollama + Open WebUI (geplant)|
### Detaillierte Ressourcen-Konfiguration
#### CT 100 — pangolin-client (LXC)
| Parameter | Wert |
|:-----------------|:---------------------------------------|
| **OS** | Ubuntu, arm64 |
| **CPU** | 4 Cores (cpulimit=4) |
| **RAM** | 512 MiB + 512 MiB Swap |
| **Disk** | 8 GB (`local:100/vm-100-disk-0.raw`) |
| **Netzwerk** | eth0 → vmbr0, IP `10.0.0.21/24`, GW `10.0.0.138`, Firewall: Yes |
| **Typ** | Unprivileged, nesting=1 |
| **Autostart** | Ja — order=1, up=30 |
#### CT 101 — gitea (LXC)
| Parameter | Wert |
|:-----------------|:---------------------------------------|
| **OS** | Ubuntu, arm64 |
| **CPU** | 4 Cores |
| **RAM** | 1.00 GiB + 512 MiB Swap |
| **Disk** | 20 GB (`local:101/vm-101-disk-0.raw`) |
| **Netzwerk** | eth0 → vmbr0, IP `10.0.0.22/24`, GW `10.0.0.138`, Firewall: Yes |
| **Typ** | Unprivileged, nesting=1 |
| **Autostart** | Ja — order=2, up=30 |
#### CT 103 — immich (LXC)
| Parameter | Wert |
|:-----------------|:---------------------------------------|
| **OS** | Ubuntu, arm64 |
| **CPU** | 8 Cores |
| **RAM** | 10.00 GiB + 512 MiB Swap |
| **Root Disk** | 200 GB (`local:103/vm-103-disk-0.raw`) |
| **Mount Point** | mp0: `/mnt/immich_gross``/mnt/fotos` (Foto-Bibliothek) |
| **Netzwerk** | eth0 → vmbr0, IP `10.0.0.24/24`, GW `10.0.0.138`, Firewall: Yes |
| **Typ** | Unprivileged, nesting=1, keyctl=1, fuse=1 |
| **Autostart** | Ja — order=3, up=30 |
#### VM 102 — gitea-runner (QEMU/KVM)
| Parameter | Wert |
|:-----------------|:-----------------------------------------------------------------|
| **BIOS** | OVMF (UEFI) |
| **Machine** | virt (ARM64) |
| **CPU** | 8 Cores (1 Socket, host-type, numa=1) |
| **RAM** | 16.00 GiB (balloon=0, kein Dynamic Memory) |
| **Disk** | 50 GB SSD (`local:102/vm-102-disk-1.qcow2`, aio=io_uring, iothread=1) |
| **EFI Disk** | `local:102/vm-102-disk-0.qcow2`, efitype=4m, 64 MB |
| **Netzwerk** | virtio, bridge=vmbr0, Firewall: Yes |
| **SCSI** | VirtIO SCSI single |
## 2. Mail-Relay (SSoT Identity)
Diese Daten müssen in der Spring Boot `application.yml` oder `.env` abgeglichen werden.
* **SMTP-Host:** `10.0.6.1` (Zora Host Relay)
* **SMTP-Host:** `10.0.0.20` (Zora Proxmox-Node als Mail-Relay)
* **SMTP-Port:** `25` (Passwortloser interner Zugriff via `mynetworks`)
* **Absender:** `zora@mo-code.at` (Verifizierte World4You Identität)
## 3. Docker-Image Checkliste (ARM64 Kompatibilität)
Bitte prüfe in deinen `docker-compose.yaml` Dateien, ob diese Images genutzt werden (alle unterstützen offiziell ARM64):
| Dienst | Empfohlenes Image | Status |
|:---------------|:---------------------------------|:-----------------------------------------------|
| **Datenbank** | `postgres:15-alpine` | ARM64 Support: Ja |
| **Cache** | `valkey/valkey:8-alpine` | ARM64 Support: Ja (Besserer Support als Redis) |
| **Identity** | `quay.io/keycloak/keycloak:24.0` | ARM64 Support: Ja (Offiziell) |
| **Monitoring** | `prom/prometheus:latest` | ARM64 Support: Ja |
| **Dashboards** | `grafana/grafana:latest` | ARM64 Support: Ja |
| Dienst | Empfohlenes Image | Status |
|:---------------|:-------------------------------------------|:-----------------------------------------------|
| **Datenbank** | `postgres:16-alpine` | ARM64 Support: Ja |
| **Cache** | `valkey/valkey:9-alpine` | ARM64 Support: Ja (Besserer Support als Redis) |
| **Identity** | `quay.io/keycloak/keycloak:26.4` | ARM64 Support: Ja (Offiziell) |
| **Monitoring** | `prom/prometheus:v3.7.3` | ARM64 Support: Ja |
| **Dashboards** | `grafana/grafana:12.3` | ARM64 Support: Ja |
## 4. Backend & Gateway (Spring Boot)
Da du diese selbst baust, ist die Dockerfile-Konfiguration entscheidend:
* **Base Image:** Nutze `eclipse-temurin:17-jre-alpine` oder `21-jre-alpine`. Diese sind für ARM64 optimiert.
* **Build-Prozess:** Dein Gitea-Runner auf Zora baut automatisch für ARM64, da er auf der gleichen Hardware läuft.
* **Base Image:** `eclipse-temurin:25-jre-alpine` (ARM64-native, via Gitea-Pipeline gebaut)
* **Build-Prozess:** Gitea-Runner (VM 102, `10.0.0.23`) baut nativ für `linux/arm64`
## 5. Keycloak SSoT Integration
Wichtige Endpunkte für deine Microservices in der IDEA:
* **External Issuer:** `https://auth.mo-code.at/realms/mocode-realm`
* **Internal Issuer:** `http://infra-keycloak:8080/realms/mocode-realm` (Für die Kommunikation innerhalb des Docker-Netzwerks)
* **Client-ID:** `meldestelle-client`
## 6. Cloudflare Tunnel Routing
Stelle sicher, dass deine Ingress-Rules auf die IP der Meldestelle zeigen:
* `api.mo-code.at` -> `http://10.0.6.50:8080` (Gateway)
* `auth.mo-code.at` -> `http://10.0.6.50:8180` (Keycloak)
* `git.mo-code.at` -> `http://10.0.6.100:3000` (Gitea LXC - bereits aktiv)
* **External Issuer:** `http://10.0.0.50:8180/realms/meldestelle`
* **Internal Issuer:** `http://keycloak:8080/realms/meldestelle` (Docker-intern)
* **Client-IDs:** `api-gateway`, `web-app`
## 6. Pangolin Tunnel Routing
> Pangolin läuft auf CT 100 (pangolin-client, `10.0.0.21`) als Tunnel-Client zu `pangolin.mo-code.at`.
| Route | Ziel (intern) | Port | Sichtbarkeit |
|:-----------------------|:---------------|:-------|:---------------------|
| `api.mo-code.at` | `10.0.0.50` | `8081` | Öffentlich |
| `auth.mo-code.at` | `10.0.0.50` | `8180` | Öffentlich |
| `git.mo-code.at` | `10.0.0.22` | `3000` | Öffentlich |
| `photos.mo-code.at` | `10.0.0.24` | `2283` | Nur intern / VPN |
| `ai.mo-code.at` | `10.0.0.60` | `3001` | Nur intern / VPN |
@@ -11,7 +11,7 @@ Das System nutzt das **Single Source of Truth (SSoT)** Prinzip für den gesamten
| Parameter | Wert | Funktion |
|:-------------------|:-------------------------|:-------------------------------------------------|
| **Zentraler Host** | `zora.mo-code.at` | Primärer Mail-Transfer-Agent (MTA). |
| **Gateway IP** | `10.0.6.1` | Interne Erreichbarkeit für alle Container/VMs. |
| **Gateway IP** | `10.0.0.20` | Proxmox-Node-IP, interne Erreichbarkeit für alle VMs/Container. |
| **Relay Host** | `smtp.world4you.com:587` | Externer Provider für den tatsächlichen Versand. |
| **SSoT Account** | `zora@mo-code.at` | Die verifizierte Absender-Identität. |
@@ -20,7 +20,7 @@ Das System nutzt das **Single Source of Truth (SSoT)** Prinzip für den gesamten
## 2. Technische Umsetzung (Der "Postbote")
Wir haben Postfix als **Satellitensystem** konfiguriert. Er nimmt Briefe im internen Netzwerk entgegen und bringt sie sicher zum Provider.
* **Sicherheit:** Postfix lauscht auf Port 25, erlaubt den Versand aber **nur** für Anfragen aus dem internen Netz `10.0.0.0/8` (Incus/Docker).
* **Sicherheit:** Postfix lauscht auf Port 25, erlaubt den Versand aber **nur** für Anfragen aus dem internen Netz `10.0.0.0/8` (Proxmox vmbr0 / Docker).
* **Authentifizierung:** Nur Zora kennt das Passwort für den World4You-Account (gespeichert in `sasl_passwd`).
* **Umschreibung (Canonical Mapping):** Postfix korrigiert automatisch Absender wie `root@zora` oder `grandmo@zora` zu `zora@mo-code.at`, damit der Provider die Mails nicht ablehnt.
* **Verschlüsselung:** Die Verbindung zu World4You ist via TLS (STARTTLS) abgesichert.
@@ -38,7 +38,7 @@ Ein Dienst stellt einen Zustand fest, der gemeldet werden muss.
### Phase B: Der interne Transport
Der Dienst verbindet sich ohne Passwort (da er sich im vertrauenswürdigen Netz befindet) mit:
`SMTP_HOST: 10.0.6.1` | `SMTP_PORT: 25`
`SMTP_HOST: 10.0.0.20` | `SMTP_PORT: 25`
Dies minimiert das Risiko: Keine Passwörter in `.env`-Dateien von Applikationen.
### Phase C: Die externe Zustellung
@@ -5,47 +5,47 @@ owner: DevOps Engineer
tags: [gradle, kotlin, dsl, build]
---
# Gradle Kotlin DSL Primer
# Gradle Kotlin DSL Einführung
**Quelle:** [Original Gradle Documentation](https://docs.gradle.org/current/userguide/kotlin_dsl.html)
**Quelle:** [Offizielle Gradle-Dokumentation](https://docs.gradle.org/current/userguide/kotlin_dsl.html)
**Kontext:** Dieses Dokument dient als Referenz für die im Projekt verwendete Gradle Kotlin DSL. Es fasst die wichtigsten Konzepte und Syntax-Elemente zusammen.
---
Gradles Kotlin DSL offers an alternative to the traditional Groovy DSL, delivering an enhanced editing experience in supported IDEs.
Gradles Kotlin DSL bietet eine Alternative zur traditionellen Groovy DSL und liefert eine verbesserte Editor-Erfahrung in unterstützten IDEs.
## Key Concepts
## Wichtige Konzepte
### Script File Names
### Skript-Dateinamen
* Groovy DSL: `.gradle`
* Kotlin DSL: `.gradle.kts`
To activate the Kotlin DSL, use the `.gradle.kts` extension for your build scripts, settings file (`settings.gradle.kts`), and initialization scripts (`init.gradle.kts`).
Um die Kotlin DSL zu aktivieren, wird die Erweiterung `.gradle.kts`r Build-Skripte, die Settings-Datei (`settings.gradle.kts`) und Initialisierungsskripte (`init.gradle.kts`) verwendet.
### Type-safe Model Accessors
The Kotlin DSL replaces Groovy's dynamic resolution with type-safe model accessors for elements contributed by plugins (configurations, tasks, extensions). This provides better IDE support (code completion, refactoring).
### Typsichere Model-Accessors
Die Kotlin DSL ersetzt Groovys dynamische Auflösung durch typsichere Model-Accessors für Elemente, die von Plugins bereitgestellt werden (Konfigurationen, Tasks, Erweiterungen). Dies sorgt für bessere IDE-Unterstützung (Code-Vervollständigung, Refactoring).
**Example:**
**Beispiel:**
```kotlin
plugins {
`java-library`
}
dependencies {
// 'api', 'implementation' are type-safe accessors
// 'api', 'implementation' sind typsichere Accessors
api("junit:junit:4.13")
implementation("org.apache.commons:commons-lang3:3.12.0")
}
tasks {
// 'test' is a type-safe accessor for the Test task
// 'test' ist ein typsicherer Accessor für den Test-Task
test {
useJUnitPlatform()
}
}
```
Accessors are available for elements contributed by plugins applied in the `plugins {}` block. For elements created dynamically later in the script, you must fall back to string-based lookups:
Accessors stehen für Elemente zur Verfügung, die von Plugins im `plugins {}`-Block bereitgestellt werden. Für dynamisch im Skript erstellte Elemente muss auf zeichenkettenbasierte Lookups zurückgegriffen werden:
```kotlin
configurations.create("custom")
@@ -55,20 +55,20 @@ dependencies {
```
### Lazy Property Assignment
The Kotlin DSL supports lazy property assignment using the `=` operator for types like `Property` and `ConfigurableFileCollection`. This is the preferred way over the `set()` method.
Die Kotlin DSL unterstützt die verzögerte Zuweisung von Properties mit dem `=`-Operator für Typen wie `Property` und `ConfigurableFileCollection`. Dies ist der bevorzugte Weg gegenüber der `set()`-Methode.
```kotlin
// Instead of:
// Anstatt:
// javaVersion.set(JavaLanguageVersion.of(17))
// Use:
// Verwende:
javaVersion = JavaLanguageVersion.of(17)
```
### Working with Containers
You can interact with containers like `tasks` or `configurations` in several ways:
### Arbeiten mit Containern
Mit Containern wie `tasks` oder `configurations` kann auf verschiedene Weisen interagiert werden:
1. **Container API (using `named` and `register`):**
1. **Container API (mit `named` und `register`):**
```kotlin
tasks.named<Test>("test") {
testLogging.showExceptions = true
@@ -79,7 +79,7 @@ You can interact with containers like `tasks` or `configurations` in several way
}
```
2. **Delegated Properties (using `by existing` and `by registering`):**
2. **Delegierte Properties (mit `by existing` und `by registering`):**
```kotlin
val test by tasks.existing(Test::class) {
testLogging.showStackTraces = true
@@ -91,17 +91,17 @@ You can interact with containers like `tasks` or `configurations` in several way
```
### Extra Properties
Access project or task-level extra properties via delegated properties:
Auf extra Properties auf Projekt- oder Task-Ebene wird über delegierte Properties zugegriffen:
```kotlin
// Define an extra property
val myNewProperty by extra("initial value")
// Eine extra Property definieren
val myNewProperty by extra("Initialwert")
// Read an existing extra property
// Eine bestehende extra Property lesen
val myExtraProperty: String by extra
```
### Kotlin DSL Plugin (`kotlin-dsl`)
This plugin is essential for developing build logic in Kotlin (e.g., in `buildSrc` or for convention plugins). It automatically applies the Kotlin plugin and adds necessary dependencies like `kotlin-stdlib` and `gradleKotlinDsl()`.
Dieses Plugin ist essenziell für die Entwicklung von Build-Logik in Kotlin (z.B. in `buildSrc` oder für Convention Plugins). Es wendet automatisch das Kotlin-Plugin an und fügt notwendige Abhängigkeiten wie `kotlin-stdlib` und `gradleKotlinDsl()` hinzu.
```kotlin
// buildSrc/build.gradle.kts
@@ -5,69 +5,69 @@ owner: Lead Architect
tags: [kotlin, release-notes, tech-stack]
---
# What's new in Kotlin 2.3.0
# Was ist neu in Kotlin 2.3.0
**Quelle:** [Original Kotlin Documentation](https://kotlinlang.org/docs/whatsnew23.html)
**Quelle:** [Offizielle Kotlin-Dokumentation](https://kotlinlang.org/docs/whatsnew23.html)
**Datum des Dokuments:** 16. Dezember 2025
**Kontext:** Dieses Dokument dient als Referenz für die im Projekt verwendete Kotlin-Version.
---
The Kotlin 2.3.0 release is out! Here are the main highlights:
Kotlin 2.3.0 ist erschienen! Hier sind die wichtigsten Highlights:
* **Language:** More stable and default features, unused return value checker, explicit backing fields, and changes to context-sensitive resolution.
* **Kotlin/JVM:** Support for Java 25.
* **Kotlin/Native:** Improved interop through Swift export, faster build time for release tasks, C and Objective-C library import in Beta.
* **Kotlin/Wasm:** Fully qualified names and new exception handling proposal enabled by default, as well as new compact storage for Latin-1 characters.
* **Kotlin/JS:** New experimental suspend function export, `LongArray` representation, unified companion object access, and more.
* **Gradle:** Compatibility with Gradle 9.0 and a new API for registering generated sources.
* **Compose compiler:** Stack traces for minified Android applications.
* **Standard library:** Stable time tracking functionality and improved UUID generation and parsing.
* **Sprache:** Mehr stabile und standardmäßig aktivierte Features, Checker für ungenutzte Rückgabewerte, explizite Backing Fields und Änderungen bei der kontextsensitiven Auflösung.
* **Kotlin/JVM:** Unterstützung für Java 25.
* **Kotlin/Native:** Verbesserte Interoperabilität durch Swift-Export, schnellere Build-Zeiten für Release-Tasks, C- und Objective-C-Bibliotheksimport in Beta.
* **Kotlin/Wasm:** Vollständig qualifizierte Namen und neuer Ausnahmebehandlungsvorschlag standardmäßig aktiviert, sowie kompakter Speicher für Latin-1-Zeichen.
* **Kotlin/JS:** Neuer experimenteller Export von Suspend-Funktionen, `LongArray`-Darstellung, einheitlicher Companion-Object-Zugriff und mehr.
* **Gradle:** Kompatibilität mit Gradle 9.0 und neue API zur Registrierung generierter Quellen.
* **Compose Compiler:** Stack Traces für minimierte Android-Anwendungen.
* **Standardbibliothek:** Stabile Zeiterfassungsfunktionalität sowie verbesserte UUID-Generierung und -Analyse.
## Language
## Sprache
Kotlin 2.3.0 focuses on feature stabilization, introduces a new mechanism for detecting unused return values, and improves context-sensitive resolution.
Kotlin 2.3.0 konzentriert sich auf die Stabilisierung von Features, führt einen neuen Mechanismus zur Erkennung ungenutzter Rückgabewerte ein und verbessert die kontextsensitive Auflösung.
### Stable features
### Stabile Features
The following features have now graduated to Stable:
* Support for nested type aliases
* Data-flow-based exhaustiveness checks for `when` expressions
Folgende Features sind nun stabil:
* Unterstützung für verschachtelte Typ-Aliase
* Datenflussbasierte Vollständigkeitsprüfungen für `when`-Ausdrücke
### Features enabled by default
* Support for `return` statements in expression bodies with explicit return types is now enabled by default.
### Standardmäßig aktivierte Features
* Unterstützung für `return`-Anweisungen in Ausdrucks-Bodies mit explizitem Rückgabetyp ist nun standardmäßig aktiviert.
### Experimental: Unused return value checker
Kotlin 2.3.0 introduces the unused return value checker to help prevent ignored results.
### Experimentell: Checker für ungenutzte Rückgabewerte
Kotlin 2.3.0 führt den Checker für ungenutzte Rückgabewerte ein, um das versehentliche Ignorieren von Ergebnissen zu verhindern.
### Experimental: Explicit backing fields
A new syntax for explicitly declaring the underlying field that holds a property's value, simplifying the common backing properties pattern.
### Experimentell: Explizite Backing Fields
Eine neue Syntax zur expliziten Deklaration des zugrundeliegenden Felds, das den Wert einer Property hält vereinfacht das verbreitete Backing-Properties-Muster.
## Kotlin/JVM: Support for Java 25
Starting with Kotlin 2.3.0, the compiler can generate classes containing Java 25 bytecode.
## Kotlin/JVM: Unterstützung für Java 25
Ab Kotlin 2.3.0 kann der Compiler Klassen mit Java-25-Bytecode generieren.
## Kotlin/Native
* **Improved Swift Export:** Direct mapping for native enum classes and variadic function parameters.
* **C and Objective-C Library Import is in Beta:** Better diagnostics for binary compatibility issues.
* **Faster Build Time:** Up to 40% faster release builds, especially for iOS targets.
* **Verbesserter Swift-Export:** Direkte Zuordnung für native Enum-Klassen und variadische Funktionsparameter.
* **C- und Objective-C-Bibliotheksimport in Beta:** Bessere Diagnosen bei binären Kompatibilitätsproblemen.
* **Schnellere Build-Zeit:** Bis zu 40 % schnellere Release-Builds, besonders für iOS-Targets.
## Kotlin/Wasm
* **Fully Qualified Names Enabled by Default:** `KClass.qualifiedName` is now available at runtime without extra configuration.
* **Compact Storage for Latin-1 Characters:** Reduces metadata and binary size.
* **New Exception Handling for `wasmWasi`:** Enabled by default for better compatibility with modern WebAssembly runtimes.
* **Vollständig qualifizierte Namen standardmäßig aktiviert:** `KClass.qualifiedName` ist nun ohne zusätzliche Konfiguration zur Laufzeit verfügbar.
* **Kompakter Speicher für Latin-1-Zeichen:** Reduziert Metadaten- und Binärgröße.
* **Neue Ausnahmebehandlung für `wasmWasi`:** Standardmäßig aktiviert für bessere Kompatibilität mit modernen WebAssembly-Laufzeitumgebungen.
## Kotlin/JS
* **Experimental Suspend Function Export:** Export suspend functions directly to JavaScript using `@JsExport`.
* **`BigInt64Array` for `LongArray`:** Simplifies interop with JavaScript APIs that use typed arrays.
* **Unified Companion Object Access:** Consistent access to companion objects in interfaces across all JS module systems.
* **Experimenteller Export von Suspend-Funktionen:** Suspend-Funktionen direkt nach JavaScript exportieren mittels `@JsExport`.
* **`BigInt64Array` für `LongArray`:** Vereinfacht die Interoperabilität mit JavaScript-APIs, die typisierte Arrays verwenden.
* **Einheitlicher Companion-Object-Zugriff:** Konsistenter Zugriff auf Companion Objects in Interfaces über alle JS-Modulsysteme hinweg.
## Gradle
* Fully compatible with Gradle 7.6.3 through 9.0.0.
* New experimental API for registering generated sources.
* Vollständig kompatibel mit Gradle 7.6.3 bis 9.0.0.
* Neue experimentelle API zur Registrierung generierter Quellen.
## Standard library
* **Stable Time Tracking:** `kotlin.time.Clock` and `kotlin.time.Instant` are now stable.
* **Improved UUID Generation:** New functions like `Uuid.parseOrNull()`, `Uuid.generateV4()`, and `Uuid.generateV7()`.
## Standardbibliothek
* **Stabile Zeiterfassung:** `kotlin.time.Clock` und `kotlin.time.Instant` sind nun stabil.
* **Verbesserte UUID-Generierung:** Neue Funktionen wie `Uuid.parseOrNull()`, `Uuid.generateV4()` und `Uuid.generateV7()`.
## Compose compiler
* **Stack Traces for Minified Android Apps:** The compiler now outputs ProGuard mappings for Compose stack traces when applications are minified by R8.
## Compose Compiler
* **Stack Traces für minimierte Android-Apps:** Der Compiler gibt nun ProGuard-Mappings für Compose-Stack-Traces aus, wenn Anwendungen durch R8 minimiert werden.
@@ -3,12 +3,12 @@ type: ADR
status: DRAFT
owner: Lead Architect
---
# PENDING DECISIONS: Backend Infrastructure & Architecture
# OFFENE ENTSCHEIDUNGEN: Backend-Infrastruktur & Architektur
**Status:** RESOLVED
**Date:** 2026-01-15
**See:** [ADR 001: Backend Infrastructure & Architecture Decisions](001-backend-infrastructure-decisions.md)
**Status:** GELÖST
**Datum:** 2026-01-15
**Siehe:** [ADR 001: Backend-Infrastruktur & Architekturentscheidungen](001-backend-infrastructure-decisions.md)
---
*This document is kept for historical context. All decisions have been moved to ADR 001.*
*Dieses Dokument wird aus historischen Gründen aufbewahrt. Alle Entscheidungen wurden in ADR 001 überführt.*
@@ -3,70 +3,70 @@ type: ADR
status: ACTIVE
owner: Lead Architect
---
# ADR 001: Backend Infrastructure & Architecture Decisions
# ADR 001: Backend-Infrastruktur & Architekturentscheidungen
**Status:** ACCEPTED
**Date:** 2026-01-15
**Author:** Lead Architect
**Context:** "Operation Tracer Bullet" (Phase 1) - Hardening of the `ping-service`.
**Status:** AKZEPTIERT
**Datum:** 2026-01-15
**Autor:** Lead Architect
**Kontext:** Operation Tracer Bullet" (Phase 1) Härtung des `ping-service`.
---
## 1. Persistence Strategy: JPA vs. Exposed
## 1. Persistenzstrategie: JPA vs. Exposed
**Decision:** **Hybrid Approach (Command/Query Separation)**
* **Primary (Command/Write):** We use **JPA (Hibernate)** for the standard "Write Model" in our microservices.
* *Reason:* Best integration with Spring Data, transaction management, and validation. Standard for Enterprise Spring Boot.
* **Secondary (Query/Read/Batch):** We allow **Exposed** for complex read queries or bulk operations where JPA overhead is too high.
* *Reason:* Kotlin-native, type-safe SQL generation, better performance for read-heavy operations.
**Entscheidung:** **Hybrider Ansatz (Command/Query-Trennung)**
* **Primär (Command/Write):** Wir verwenden **JPA (Hibernate)** für das Standard-„Write Model" in unseren Microservices.
* *Begründung:* Beste Integration mit Spring Data, Transaktionsverwaltung und Validierung. Standard für Enterprise Spring Boot.
* **Sekundär (Query/Read/Batch):** Wir erlauben **Exposed** für komplexe Leseabfragen oder Bulk-Operationen, bei denen der JPA-Overhead zu hoch ist.
* *Begründung:* Kotlin-nativ, typsichere SQL-Generierung, bessere Performance bei leselastigen Operationen.
**Action:**
* The `backend/infrastructure/persistence` module will support **both**.
* `ping-service` will primarily use **JPA** for its entities (`PingEntity`).
* We will NOT remove JPA from `ping-service`.
* We will NOT remove Exposed from `infrastructure/persistence`.
**Maßnahmen:**
* Das Modul `backend/infrastructure/persistence` unterstützt **beide** Ansätze.
* `ping-service` verwendet primär **JPA** für seine Entitäten (`PingEntity`).
* JPA wird NICHT aus dem `ping-service` entfernt.
* Exposed wird NICHT aus `infrastructure/persistence` entfernt.
## 2. Security Shared Module
## 2. Gemeinsames Security-Modul
**Decision:** **Extract `backend/infrastructure/security`**
* **Reason:** We strictly follow DRY (Don't Repeat Yourself). Security configuration (OAuth2 Resource Server, JWT Converter, CORS, Global Method Security) is identical for all microservices.
* **Scope:**
**Entscheidung:** **Extraktion von `backend/infrastructure/security`**
* **Begründung:** Wir folgen konsequent dem DRY-Prinzip (Don't Repeat Yourself). Die Sicherheitskonfiguration (OAuth2 Resource Server, JWT Converter, CORS, Global Method Security) ist für alle Microservices identisch.
* **Umfang:**
* `SecurityConfig`: Standard `SecurityFilterChain`.
* `KeycloakRoleConverter`: Extracting roles from JWT.
* `CorsConfig`: Centralized CORS policy.
* `KeycloakRoleConverter`: Rollen aus JWT extrahieren.
* `CorsConfig`: Zentrale CORS-Richtlinie.
**Action:**
* Create `backend/infrastructure/security`.
* Move security logic from `ping-service` (if any) to this module.
**Maßnahmen:**
* `backend/infrastructure/security` erstellen.
* Sicherheitslogik aus `ping-service` (falls vorhanden) in dieses Modul verschieben.
## 3. Messaging vs. Sync Protocol
## 3. Messaging vs. Sync-Protokoll
**Decision:** **REST-based Pull (Phase 1) -> Kafka (Phase 3)**
* **Phase 1 (Tracer Bullet):** We do **NOT** use Kafka for the simple `ping-service` yet.
* *Reason:* Keep the "Tracer Bullet" simple. We want to validate the HTTP/Auth chain first.
* **Phase 3 (Offline Sync):** We will introduce Kafka for the "Outbox Pattern" later.
* *Reason:* Reliable event delivery for offline clients requires a durable log.
**Entscheidung:** **REST-basiertes Pull (Phase 1) Kafka (Phase 3)**
* **Phase 1 (Tracer Bullet):** Für den einfachen `ping-service` wird Kafka noch NICHT verwendet.
* *Begründung:* Die „Tracer Bullet" soll einfach bleiben. Zuerst die HTTP/Auth-Kette validieren.
* **Phase 3 (Offline-Sync):** Kafka wird später für das „Outbox Pattern" eingeführt.
* *Begründung:* Zuverlässige Event-Zustellung für Offline-Clients erfordert ein dauerhaftes Log.
**Action:**
* Remove `reactor-kafka` dependency from `ping-service` for now to reduce noise.
* Focus on `PingEntity` (JPA) and REST endpoints.
**Maßnahmen:**
* `reactor-kafka`-Abhängigkeit vorerst aus `ping-service` entfernen, um die Komplexität zu reduzieren.
* Fokus auf `PingEntity` (JPA) und REST-Endpunkte.
## 4. Database Migration (Flyway)
## 4. Datenbank-Migration (Flyway)
**Decision:** **Database per Service (Option A)**
* **Reason:** Microservices autonomy. Each service owns its schema.
* **Location:** `src/main/resources/db/migration` inside each service module.
* **Naming:** `V{Version}__{Description}.sql` (e.g., `V1__init_ping_schema.sql`).
**Entscheidung:** **Datenbank pro Service (Option A)**
* **Begründung:** Autonomie der Microservices. Jeder Service besitzt sein eigenes Schema.
* **Ablageort:** `src/main/resources/db/migration` innerhalb jedes Service-Moduls.
* **Benennung:** `V{Version}__{Beschreibung}.sql` (z.B. `V1__init_ping_schema.sql`).
**Action:**
* `ping-service` must contain `V1__init.sql`.
**Maßnahmen:**
* `ping-service` muss `V1__init.sql` enthalten.
* `spring.flyway.enabled=true` in `application.yml`.
---
## Summary of Tasks for Senior Backend Developer
## Zusammenfassung der Aufgaben für den Senior Backend Developer
1. **Persistence:** Use JPA for `PingEntity`.
2. **Security:** Wait for `infrastructure/security` module (Architect will create skeleton) OR start implementing in `ping-service` and refactor later (preferred: Architect creates module now).
3. **Messaging:** Ignore Kafka for now.
4. **Flyway:** Create `V1__init.sql` in `ping-service`.
1. **Persistenz:** JPA für `PingEntity` verwenden.
2. **Security:** Auf das Modul `infrastructure/security` warten (Architect erstellt Skeleton) ODER direkt im `ping-service` implementieren und später refaktorieren (bevorzugt: Architect erstellt Modul sofort).
3. **Messaging:** Kafka vorerst ignorieren.
4. **Flyway:** `V1__init.sql` im `ping-service` erstellen.
@@ -3,13 +3,13 @@ type: Guide
status: ACTIVE
owner: Frontend Expert
---
# SQLDelight Integration in Compose Multiplatform
# SQLDelight-Integration in Compose Multiplatform
This guide shows how to integrate SQLDelight in a Compose Multiplatform project with Koin dependency injection.
Diese Anleitung zeigt, wie SQLDelight in einem Compose Multiplatform-Projekt mit Koin Dependency Injection integriert wird.
## Step 1: Add Dependencies
## Schritt 1: Abhängigkeiten hinzufügen
Add below dependencies In `gradle/libs.versions.toml`:
Folgende Abhängigkeiten in `gradle/libs.versions.toml` eintragen:
```toml
[versions]
@@ -28,7 +28,7 @@ koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin" }
sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" }
```
In `build.gradle.kts` (project level):
In `build.gradle.kts` (Projektebene):
```kotlin
plugins {
@@ -75,13 +75,13 @@ sqldelight {
```
##Step 2: Create SQL Schema
## Schritt 2: SQL-Schema erstellen
**Create directory structure:**
**Verzeichnisstruktur anlegen:**
`shared/src/commonMain/sqldelight/com/example/database/`
Create `User.sq` file:
Datei `User.sq` erstellen:
```sql
CREATE TABLE User
@@ -91,48 +91,48 @@ CREATE TABLE User
imageUrl TEXT
);
-- Insert a new user
-- Neuen Benutzer einfügen
insertUser
:
::
INSERT INTO User(name, imageUrl)
VALUES (?, ?);
-- Get all users
-- Alle Benutzer abrufen
getAllUsers
:
::
SELECT *
FROM User;
-- Get user by ID
-- Benutzer nach ID abrufen
getUserById
:
::
SELECT *
FROM User
WHERE id = ?;
-- Update user
-- Benutzer aktualisieren
updateUser
:
::
UPDATE User
SET name = ?,
imageUrl = ?
WHERE id = ?;
-- Delete user
-- Benutzer löschen
deleteUser
:
::
DELETE
FROM User
WHERE id = ?;
-- Delete all users
-- Alle Benutzer löschen
deleteAllUsers
:
::
DELETE
FROM User;
```
## Step 3: Create Database Driver Interface
## Schritt 3: Datenbank-Treiber-Interface erstellen
In `shared/src/commonMain/kotlin/database/DatabaseDriverFactory.kt`:
@@ -146,7 +146,7 @@ expect class DatabaseDriverFactory {
}
```
## Step 4: Platform-Specific Implementations
## Schritt 4: Plattformspezifische Implementierungen
### Android —
@@ -211,7 +211,7 @@ actual class DatabaseDriverFactory {
```
## Step 5: Create Repository
## Schritt 5: Repository erstellen
In `shared/src/commonMain/kotlin/repository/UserRepository.kt`:
@@ -255,7 +255,7 @@ class UserRepository(private val database: AppDatabase) {
```
## Step 6: Setup Koin Modules
## Schritt 6: Koin-Module konfigurieren
In `shared/src/commonMain/kotlin/di/DatabaseModule.kt`:
@@ -275,7 +275,7 @@ val databaseModule = module {
```
### Platform-specific modules
### Plattformspezifische Module
### Android —
@@ -326,7 +326,7 @@ actual val platformModule = module {
```
### Common module declaration —
### Gemeinsame Modul-Deklaration —
`shared/src/commonMain/kotlin/di/PlatformModule.kt`:
@@ -339,7 +339,7 @@ expect val platformModule: Module
```
## Step 7: Initialize Koin
## Schritt 7: Koin initialisieren
In `shared/src/commonMain/kotlin/di/KoinInit.kt`:
@@ -359,7 +359,7 @@ fun initKoin(appDeclaration: KoinAppDeclaration = {}) = startKoin {
```
## Step 8: Platform Initialization
## Schritt 8: Plattform-Initialisierung
### Android —
@@ -423,9 +423,9 @@ fun main() {
```
## Step 9: Use in Compose
## Schritt 9: In Compose verwenden
### Create VieModel —
### ViewModel erstellen
In `shared/src/commonMain/kotlin/viewmodel/UserViewModel.kt`:
@@ -478,7 +478,7 @@ class UserViewModel(private val userRepository: UserRepository) : ViewModel() {
```
Use in Compose Screen:
Im Compose-Screen verwenden:
```kotlin
@Composable
@@ -509,21 +509,20 @@ fun UserItem(user: User, onDelete: () -> Unit) {
)
Button(onClick = onDelete) {
Text("Delete")
Text("Löschen")
}
}
}
```
### Thats It!
### Fertig!
You now have SQLDelight fully integrated in your Compose Multiplatform project with:
SQLDelight ist nun vollständig in das Compose Multiplatform-Projekt integriert mit:
- Database working on Android, iOS, and Desktop
- Koin dependency injection setup
- Repository pattern for clean architecture
- Ready-to-use User table with CRUD operations
- Datenbankbetrieb auf Android, iOS und Desktop
- Koin Dependency Injection konfiguriert
- Repository-Pattern für Clean Architecture
- Einsatzbereite User-Tabelle mit CRUD-Operationen
The database will automatically handle platform-specific implementations while sharing the same business logic across
all platforms.
Die Datenbank verwaltet automatisch die plattformspezifischen Implementierungen, während dieselbe Geschäftslogik auf allen Plattformen geteilt wird.
@@ -31,7 +31,7 @@ owner: DevOps Engineer
Keycloak-Secrets.
*[ ] **Stack-Launch (dc-infra.yaml):** * Start von **Postgres**, **Keycloak** und **Valkey** (Redis-Alternative).
*[ ] **Wichtig:** Kontrolle der Logs auf ARM64-Kompatibilität (`exec format error` vermeiden).
*[ ] **Netzwerk-Check:** * Testen der Erreichbarkeit von Zoras Mail-Relay (`10.0.6.1:25`) aus dem neuen Stack heraus.
*[ ] **Netzwerk-Check:** * Testen der Erreichbarkeit von Zoras Mail-Relay (`10.0.0.20:25`) aus dem neuen Stack heraus.
---
@@ -40,8 +40,8 @@ owner: DevOps Engineer
**Ziel:** Den Ping-Service über das Spring Cloud Gateway erreichbar machen.
*[ ] **Backend-Start (dc-backend.yaml):** * Deployment des **api-gateways** und des **ping-services**.
*[ ] **Cloudflare-Tunnel Update:** * Hinzufügen der Route `api.mo-code.at`, die auf die IP der Meldestelle (`10.0.6.50`)
und den Port des Gateways (`8080`) zeigt.
*[ ] **Pangolin-Tunnel Update:** * Hinzufügen der Route `api.mo-code.at`, die auf die IP der Meldestelle (`10.0.0.50`)
und den Port des Gateways (`8081`) zeigt.
*[ ] **Keycloak-Veredelung:** * Konfiguration des Realms und Erstellen des Clients für die Meldestelle im
Keycloak-Admin-Panel (via `auth.mo-code.at`).
@@ -9,7 +9,7 @@ tags: [e2e, smoke, docker, migration, ktor-3.4.0, exposed-1.0.0]
# E2E Smoke Migration Exposed 1.0.0 & Ktor 3.4.0
## Setup
## Einrichtung
- Compose: docker compose --profile all up --build -d
- Services (Auszug):
- api-gateway (8080/actuator, 8080/api via Proxy)
@@ -25,11 +25,11 @@ tags: [e2e, smoke, docker, migration, ktor-3.4.0, exposed-1.0.0]
- Web-App Health: 200 OK (Fallback-Assets aktiv, Favicon bereitgestellt)
- Desktop-App: Xvfb/XFCE/x11vnc/noVNC aktiv, Zugriff via http://localhost:6080/
## Observability
## Beobachtbarkeit
- Prometheus-Metriken erreichbar (Gateway/Ping)
- Logs ohne kritische Fehler im Happy Path
## Issues & Notes
## Probleme & Hinweise
- Frontend KMP/JS-Build schlägt in Builder aktuell fehl (fehlende JS-Implementierungen in Auth/Ping-Data). Nginx liefert Fallback-Assets aus; Favicon hinzugefügt, um 404 zu vermeiden.
## Entscheidung
@@ -0,0 +1,73 @@
# Journal - 2026-03-06 (Session 2)
## 📝 Zusammenfassung
Strategische Planungssession mit dem Lead Architect. Drei konkrete Bugs aus der Tiefenanalyse behoben (Valkey-Inkonsistenz, Dependency Verification, backup.sh Hardcodes). Vollständige Schritt-für-Schritt-Anleitung für das heutige Meldestellen-Host-Setup erstellt. Architektur-Entscheidung für einen lokalen AI-Stack (Ollama + Open WebUI) als separaten Proxmox LXC-Container (CT 111) auf Zora getroffen und Installations-Anleitung dokumentiert. Korrektur: Zora läuft auf Proxmox 8.4.10 (nicht Incus); IPs im Netz 10.0.0.0/24.
---
## 🛠️ Änderungen
### 1. Bug-Fixes aus Tiefenanalyse
* **`dc-backend.yaml`:** ping-service von veralteten `SPRING_DATA_REDIS_*` auf `SPRING_DATA_VALKEY_*` umgestellt — konsistent mit api-gateway.
* **`gradle.properties`:** `org.gradle.dependency.verification=lenient``strict` — Sicherheits-Level angehoben, da eigene Gitea-Registry vorhanden.
* **`config/scripts/backup.sh`:** `.env` via `source` geladen, `grandmo`-Hardcode entfernt, `DB_USER` liest `${POSTGRES_USER}` aus `.env`, `BACKUP_DIR` und `BACKUP_RETENTION_DAYS` konfigurierbar.
* **`.env` + `.env.example`:** `BACKUP_DIR` und `BACKUP_RETENTION_DAYS` als neue SSoT-Variablen ergänzt.
### 2. Neue Dokumentation
* **`docs/01_Architecture/Minisforum-MS-R1/AI_Stack_Ollama_Setup.md`:** Vollständige Installations-Anleitung für Ollama + Open WebUI auf Zora als Proxmox LXC-Container (CT 111, IP 10.0.0.60). Enthält Hardware-Profil (CIX P1 / CP8180), CPU-Governor-Tuning auf Proxmox-Node, `pct create`-Befehle, Modell-Matrix, Open-WebUI-RAG-Setup, Pangolin-Routing und IntelliJ IDEA Continue.dev-Integration.
---
## 🏗️ Architektur-Entscheidungen
### AI-Stack: Separater Proxmox LXC-Container (nicht im App-Repo)
**Entscheidung:** Ollama + Open WebUI laufen in einem eigenen Proxmox LXC-Container `ai-stack` (CT 111, IP 10.0.0.60), **nicht** als Docker-Service im Meldestelle-Repo.
**Begründung:**
* Modelle (540 GB pro Modell) wachsen unkontrolliert — Isolation schützt den App-Stack.
* Unabhängige Updates ohne Risiko für den Meldestelle-Produktivbetrieb.
* RAM/CPU-Hunger der KI-Inference beeinflusst nicht die Keycloak/Gateway/DB-Verfügbarkeit.
### Datenschutz: Vollständig lokal via Ollama
* Kein Byte verlässt das Heimnetz — alle Inferenz läuft auf Zora CPU (CIX P1, 12 Cores).
* IDEA-Integration via **Continue.dev Plugin** mit `http://10.0.0.60:11434` als Endpoint.
* Pangolin-Route `ai.mo-code.at` → nur intern / VPN exponieren.
### NPU: Noch nicht nutzbar
* CIX P1 NPU aktuell **kein** Ollama/llama.cpp-Support.
* Immortalis-G720 (Vulkan 1.3) — experimentell, nachrüsten sobald stabil.
* Primärer Pfad: CPU-Inferenz mit NEON/SVE2-Optimierungen (ARM64-native Ollama).
---
## 📚 Gelerntes
* **CIX P1 (CP8180) = Tri-Cluster ARM:** 4x Cortex-X4 (fast) + 4x A720 (medium) + 4x A520 (slow) — alle 12 Kerne Ollama zuweisen, CPU-Governor auf `performance` setzen.
* **64 GB RAM = 70B-Modelle möglich:** `llama3.1:70b` (~40 GB) läuft vollständig im RAM — einzigartige Stärke dieser Hardware.
* **Proxmox vmbr0:** Routing und NAT werden von Proxmox via `vmbr0` automatisch verwaltet — keine manuelle iptables-Regel nötig.
* **Kafka-Entscheidung bestätigt:** Bleibt im Build für Phase 3 (Outbox-Pattern / Event-Driven).
* **Ghost Services bleiben ON HOLD:** Bewusste Entscheidung — erst Fachexperten-Session (FEI/ÖTO Regelwerk) bevor fachliche Implementierung beginnt.
---
## 🔜 Nächste Schritte
### Heute Abend (Priorität)
1. Meldestellen-Host aufsetzen nach Anleitung (siehe Haupt-Session-Log)
2. `.env` auf Zora mit Server-IP konfigurieren (`KC_HOSTNAME`, `JWT_ISSUER_URI`)
3. Docker-Stack starten (Infra → Backend → Ops)
4. Ping-Service + Login verifizieren
5. `ai-stack` Proxmox LXC-Container (CT 111) aufsetzen, Ollama + Open WebUI installieren
6. Continue.dev in IDEA konfigurieren
### Nächste Sessions
* **Session 2:** Grafana Dashboards einrichten + Pangolin-URLs finalisieren
* **Session 3:** Willkommensseite + Design-System (Tokens, Farben, Typografie)
* **Session 4:** Fachexperten-Session — FEI/ÖTO Regelwerk, Anforderungen, Rollen definieren
* **Session 5:** Admin-Dashboard (Backend) + Rollen-Management in Keycloak
@@ -0,0 +1,74 @@
---
type: Journal
status: ACTIVE
owner: Curator
date: 2026-03-06
session: Immich & Pangolin — Konfiguration dokumentiert
---
# Session Log — 2026-03-06: Immich & Pangolin Konfiguration
## 📋 Kontext
Ergänzungssession zur Proxmox-Korrektursession vom selben Tag.
10 neue Screenshots wurden geliefert, die die vollständige Konfiguration von
CT 100 (pangolin-client) und CT 103 (immich) sowie die detaillierten Hardware-Specs
von VM 102 (gitea-runner) dokumentieren.
---
## 🛠️ Änderungen
### `SSoT_Konfigurations-Masterplan_Zora.md`
**Neue Daten aus Screenshots (06.03.2026, 13:2513:27):**
#### CT 100 — pangolin-client (LXC)
- IP ergänzt: `10.0.0.21/24`
- Ressourcen dokumentiert: 4 Cores, 512 MiB RAM, 512 MiB Swap, 8 GB Disk
- OS: Ubuntu arm64, Unprivileged, nesting=1
- Autostart: order=1, up=30
#### CT 101 — gitea (LXC)
- Ressourcen ergänzt: 4 Cores, 1 GiB RAM, 512 MiB Swap, 20 GB Disk
- OS: Ubuntu arm64, Unprivileged, nesting=1
- Autostart: order=2, up=30
#### CT 103 — immich (LXC) ← NEU vollständig dokumentiert
- IP: `10.0.0.24/24`
- Ressourcen: 8 Cores, 10 GiB RAM, 512 MiB Swap
- Root Disk: 200 GB (`local:103/vm-103-disk-0.raw`)
- Mount Point mp0: `/mnt/immich_gross``/mnt/fotos` (externe Foto-Bibliothek)
- OS: Ubuntu arm64, Unprivileged
- Features: `nesting=1, keyctl=1, fuse=1` (Docker-in-LXC kompatibel)
- Autostart: order=3, up=30
#### VM 102 — gitea-runner (QEMU/KVM) ← Hardware-Details ergänzt
- IP: `10.0.0.23/24` (bestätigt)
- BIOS: OVMF (UEFI), Machine: virt (ARM64)
- CPU: 8 Cores, 1 Socket, host-type, numa=1
- RAM: 16 GiB (balloon=0 — kein Dynamic Memory)
- Disk: 50 GB SSD (aio=io_uring, iothread=1 — maximale I/O-Performance)
- EFI Disk: 64 MB, efitype=4m
- Netzwerk: virtio, vmbr0, Firewall aktiv
- SCSI: VirtIO SCSI single
#### Pangolin Tunnel Routing erweitert
- Route `photos.mo-code.at` → CT 103 Immich (`10.0.0.24:2283`) ergänzt (intern/VPN)
- Pangolin-Kontext-Hinweis: CT 100 (10.0.0.21) ist der Tunnel-Client
---
## 📚 Gelernt
- **Immich Features:** `keyctl=1` und `fuse=1` sind für Docker-in-LXC und Immich-Thumbnail-Verarbeitung notwendig.
- **io_uring:** Der Gitea-Runner nutzt modernes async I/O für maximale Build-Performance auf NVMe.
- **Start-Reihenfolge:** Pangolin (order=1) → Gitea (order=2) → Immich (order=3) — sinnvolle Abhängigkeitskette.
- **balloon=0 beim Runner:** Festes RAM verhindert Memory-Ballooning-Latenzen bei intensiven CI-Builds.
---
## 🔜 Nächste Schritte
1. Pangolin-Route `photos.mo-code.at` im Pangolin-Dashboard anlegen (sobald Immich-Setup abgeschlossen).
2. Immich-Setup-Anleitung erstellen (optional, eigene Session).
@@ -0,0 +1,57 @@
---
type: Journal
status: ACTIVE
owner: Lead Architect
---
# Journal - 2026-03-06 (Session 3 — Proxmox-Korrektur)
## 📝 Zusammenfassung
Korrektur-Session: Die vorherige Installations-Anleitung für den AI-Stack verwendete fälschlicherweise **Incus** als Container-Technologie und das IP-Subnetz **10.0.6.x**. Die Analyse der Proxmox-Screenshots ergab den korrekten Ist-Stand: Zora läuft auf **Proxmox VE 8.4.10** (`pve.mo-code.at`), alle VMs und Container befinden sich im Subnetz **10.0.0.0/24**. Alle betroffenen Dokumentationsdateien wurden korrigiert.
---
## 🔍 Ist-Stand Zora (aus Proxmox-Screenshots)
| ID | Name | Typ | IP | Specs |
|:----|:------------------|:-----|:-------------|:-------------------------------|
| 100 | pangolin-client | LXC | — | Pangolin Tunnel |
| 101 | gitea | LXC | `10.0.0.22` | Bridge vmbr0, GW 10.0.0.138 |
| 102 | gitea-runner | VM | `10.0.0.24` | aarch64, 16 GB RAM, 8 Cores, 50 GB |
| 103 | immich | LXC | — | Immich Foto-Server |
| 110 | meldestelle-host | VM | `10.0.0.50` | aarch64, 16 GB RAM, 8 Cores, 150 GB |
* **Proxmox-Node:** `10.0.0.20` / `pve.mo-code.at`
* **Netz-Bridge:** `vmbr0`, Subnetz `10.0.0.0/24`, Gateway `10.0.0.138`
* **Beide VMs (102, 110):** QEMU KVM, aarch64, UEFI (OVMF), cpu=host, numa=1
---
## 🛠️ Geänderte Dateien
| Datei | Änderung |
|:------|:---------|
| `docs/01_Architecture/Minisforum-MS-R1/AI_Stack_Ollama_Setup.md` | Vollständiger Rewrite: Incus→Proxmox LXC (CT 111), `incus launch``pct create`, alle IPs 10.0.6.x→10.0.0.x, Phase 1 auf Proxmox-Node, Incus-NAT-Sektion entfernt |
| `docs/99_Journal/2026-03-06_Session_Log_DevOps_und_AI_Stack.md` | AI-Stack-Entscheidung korrigiert: Incus→Proxmox, IPs aktualisiert, iptables→vmbr0 |
| `docs/01_Architecture/MASTER_ROADMAP_2026_Q1.md` | Hypervisor: Incus→Proxmox VE 8.4.10 |
| `docs/01_Architecture/Minisforum-MS-R1/SSoT_Konfigurations-Masterplan_Zora.md` | Vollständiger Rewrite: Proxmox-Tabelle mit allen VMs/LXCs, IPs korrigiert, Images auf aktuelle Versionen |
| `docs/01_Architecture/Minisforum-MS-R1/Zentrales_Mail-Relay-SSoT_Zora.md` | SMTP-Host: 10.0.6.1→10.0.0.20, Incus/Docker→Proxmox vmbr0/Docker |
| `docs/07_Infrastructure/Zora_Infrastructure_Deployment_02-2026.md` | Mail-Relay-IP korrigiert, Cloudflare→Pangolin, Port 8080→8081, IP 10.0.6.50→10.0.0.50 |
| `docs/01_Architecture/Minisforum-MS-R1/MS-R1_Konfiguration&Bedienung.md` | Firewall-Sektion auf Proxmox aktualisiert, Incus-Sektion als ⚠️ HISTORISCH markiert |
---
## 📚 Gelerntes
* **Incus war Testbetrieb (Feb 2026):** Nach dem Incus-Test wurde auf Proxmox VE umgestellt — alle Docs spiegelten noch den alten Zustand wider.
* **Proxmox vmbr0 vereinfacht NAT:** Keine manuellen iptables-MASQUERADE-Regeln nötig — Proxmox erledigt das automatisch.
* **Core-Pinning in Proxmox:** Gitea-Runner und Meldestelle-Host sind auf 8 Kerne (4 große + 4 mittlere) gebunden → `cpu=host`, `numa=1` in VM-Config.
* **Docs-as-Code Pflicht:** Screenshots als Primärquelle genutzt um den wahren Ist-Stand zu ermitteln — wichtig wenn mündliche Korrekturen noch nicht in Docs eingeflossen sind.
---
## 🔜 Nächste Schritte
1. AI-Stack LXC (CT 111, `10.0.0.60`) in Proxmox erstellen nach `AI_Stack_Ollama_Setup.md`
2. Continue.dev Plugin in IDEA konfigurieren
3. Grafana Dashboards + Pangolin-URLs finalisieren
@@ -0,0 +1,54 @@
---
type: Journal
status: FINAL
owner: Lead Architect
date: 2026-03-06
---
# Session Log Übersetzung aller Dokumente auf Deutsch
**Datum:** 06.03.2026
**Agent:** 🏗️ Lead Architect (Curator)
**Ziel:** Alle englischsprachigen Markdown-Dateien in `docs/` auf Deutsch übersetzen.
---
## Ausgangslage
Ein automatischer Sprachscan hat 7 Dateien identifiziert, die noch überwiegend auf Englisch verfasst waren. Alle anderen Dokumente waren bereits auf Deutsch.
---
## Übersetzte Dateien
| Datei | Bereich | Anmerkung |
|---|---|---|
| `01_Architecture/02_Frontend_Architecture.md` | Architektur | Vollständige Übersetzung inkl. ADR-Abschnitte |
| `01_Architecture/adr/001-backend-infrastructure-decisions.md` | ADR | Vollständige Übersetzung, Code-Blöcke unverändert |
| `01_Architecture/adr/000-PENDING-backend-infrastructure-decisions.md` | ADR | Kurzdokument, vollständig übersetzt |
| `01_Architecture/Reference/Tech_Stack/Gradle_Kotlin_DSL_Primer.md` | Referenz | Fließtext übersetzt, Code-Kommentare eingedeutscht |
| `01_Architecture/Reference/Tech_Stack/Kotlin_2-3-0_ReleaseNotes.md` | Referenz | Vollständige Übersetzung der Release Notes |
| `02_Guides/SQLDelight_Integration_Compose_Multiplatform.md` | Anleitung | Schritt-für-Schritt-Anleitung eingedeutscht, Code unverändert |
| `90_Reports/2026-01-31_E2E_Smoke_Migration_Exposed_Ktor.md` | Report | War bereits größtenteils Deutsch fehlende Abschnittsüberschriften übersetzt |
---
## Prinzipien bei der Übersetzung
- **Code-Blöcke:** Unverändert — nur Kommentare innerhalb von Code-Blöcken wurden übersetzt
- **Fachbegriffe:** Etablierte englische Fachbegriffe (Clean Architecture, Repository, ViewModel, Use Case, etc.) wurden beibehalten
- **Frontmatter:** Werte wie `ACTIVE`, `DRAFT`, `Report` etc. blieben unverändert (technische Metadaten)
- **Externe Quellenangaben:** URLs und Quellenverweise blieben auf Englisch
---
## Gelerntes
- Der automatische Sprachscan via Python-Regex ist ein effizientes Werkzeug zur Identifizierung englischer Dokumente
- Manche Dokumente waren bereits hybrid (z.B. der E2E-Report) — dort genügten gezielte Korrekturen der Abschnittsüberschriften
---
## Nächste Schritte
- Bei neuen Dokumenten: direkt auf Deutsch verfassen
- Neue externe Referenzen (z.B. Release Notes) beim Einpflegen sofort übersetzen
Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB