Bläddra i källkod

dokumentation

mrx8086 11 månader sedan
förälder
incheckning
57632206a6

+ 41 - 31
docs/context/project_documentation.md

@@ -1,15 +1,15 @@
 # Automatisiertes Büro 2.0 - Projektdokumentation
 
 ## Inhaltsverzeichnis
-1. [Projektübersicht](#projektübersicht)
-2. [Systemarchitektur](#systemarchitektur)
-3. [Entwicklungsumgebung](#entwicklungsumgebung)
-4. [Konfigurationen](#konfigurationen)
-5. [Installationsanleitung](#installationsanleitung)
-6. [Workflows](#workflows)
-7. [Sicherheitskonzept](#sicherheitskonzept)
-8. [Wartung und Monitoring](#wartung-und-monitoring)
-9. [Troubleshooting](#troubleshooting)
+1.  [Projektübersicht](#projektübersicht)
+2.  [Systemarchitektur](#systemarchitektur)
+3.  [Entwicklungsumgebung](#entwicklungsumgebung)
+4.  [Konfigurationen](#konfigurationen)
+5.  [Installationsanleitung](#installationsanleitung)
+6.  [Workflows](#workflows)
+7.  [Sicherheitskonzept](#sicherheitskonzept)
+8.  [Wartung und Monitoring](#wartung-und-monitoring)
+9.  [Troubleshooting](#troubleshooting)
 
 ## Projektübersicht
 
@@ -111,26 +111,26 @@ graph TB
 
 Die Systemarchitektur ist in vier Hauptbereiche gegliedert:
 
-1. **Eingangssysteme:**
-   - Erfassen verschiedene Kommunikationskanäle zentral.
-   - Sorgen für eine einheitliche Weiterverarbeitung aller Eingänge.
+1.  **Eingangssysteme:**
+    - Erfassen verschiedene Kommunikationskanäle zentral.
+    - Sorgen für eine einheitliche Weiterverarbeitung aller Eingänge.
 
-2. **Zentrale Plattform:**
-   - **Nextcloud (NC):** Dient als zentraler Hub für die Dateiverwaltung und Kollaboration.
-   - **Paperless (PL):** Zuständig für das Dokumentenmanagement und die optische Zeichenerkennung (OCR).
-   - **Node-RED (NR):** Automatisierung von Workflows und Geschäftsprozessen.
-   - **Keycloak (KC):** Bereitstellung von Single-Sign-On (SSO) und Identitätsmanagement.
-     - Keycloak wird als zentrale Authentifizierungsstelle für alle Dienste der zentralen Plattform verwendet, wodurch ein sicherer und zentralisierter Zugriff gewährleistet wird.
-   -  **OpenWebUI (OUI):** KI-gestützte Kommunikation und Integration in die Workflow-Automatisierung
-   -  **Kimai (KI):** Zeiterfassungslösung zur Verwaltung von Arbeitszeiten und Projekten.
+2.  **Zentrale Plattform:**
+    -   **Nextcloud (NC):** Dient als zentraler Hub für die Dateiverwaltung und Kollaboration.
+    -   **Paperless (PL):** Zuständig für das Dokumentenmanagement und die optische Zeichenerkennung (OCR).
+    -   **Node-RED (NR):** Automatisierung von Workflows und Geschäftsprozessen.
+    -   **Keycloak (KC):** Bereitstellung von Single-Sign-On (SSO) und Identitätsmanagement.
+        - Keycloak wird als zentrale Authentifizierungsstelle für alle Dienste der zentralen Plattform verwendet, wodurch ein sicherer und zentralisierter Zugriff gewährleistet wird.
+    -  **OpenWebUI (OUI):** KI-gestützte Kommunikation und Integration in die Workflow-Automatisierung
+    -  **Kimai (KI):** Zeiterfassungslösung zur Verwaltung von Arbeitszeiten und Projekten.
 
-3. **Geschäftsprozesse:**
-   - Automatisierte Task-Priorisierung (TP) für eine effiziente Aufgabenverteilung.
-   - Integrierte Finanzprozesse mit Bankenanbindung (RE, ZA, BA).
-   - Zentralisierte Verwaltungsprozesse (KV, ZE, DO).
+3.  **Geschäftsprozesse:**
+    - Automatisierte Task-Priorisierung (TP) für eine effiziente Aufgabenverteilung.
+    - Integrierte Finanzprozesse mit Bankenanbindung (RE, ZA, BA).
+    - Zentralisierte Verwaltungsprozesse (KV, ZE, DO).
 
-4. **Monitoring & Analytics:**
-   - **ELK Stack (ELK):** Ermöglicht umfassendes Logging und Analyse in Echtzeit zur Überwachung aller Systeme.
+4.  **Monitoring & Analytics:**
+    -   **ELK Stack (ELK):** Ermöglicht umfassendes Logging und Analyse in Echtzeit zur Überwachung aller Systeme.
 
 #### Containerstruktur
 - Docker als Containerisierungsplattform
@@ -230,6 +230,8 @@ Die Konfigurationen für das setup_realm.js Script werden in einer .env Datei ge
 - `NODERED_CLIENT_ID`: Die Client ID für Node-RED (z.B. nodered).
 - `TESTADMIN_PASSWORD`: Das Passwort für den Testadmin User.
 - `TESTUSER_PASSWORD`: Das Passwort für den Testuser User.
+- `TESTSERVICEUSER_PASSWORD`: Das Passwort für den Testserviceuser User.
+- `KEYCLOAK_NEXTCLOUD_CLIENT_SECRET`: Das Client Secret für Nextcloud.
 
 #### NGINX-Konfigurationen
 Für jeden Service existiert eine dedizierte NGINX-Konfiguration. In der Development Umgebung wird der **NGINX Proxy Manager** verwendet. Für **Staging** und **Production** werden die entsprechenden NGINX Konfigurationsdateien in `/config/nginx` abgelegt.
@@ -263,6 +265,8 @@ Eine im `docker/` Verzeichnis für die Docker-Konfiguration und eine im `scripts
 - `NODERED_CLIENT_ID`: Die Client-ID für Node-RED (z.B. `nodered`).
 - `TESTADMIN_PASSWORD`: Das Passwort für den Testadmin User.
 - `TESTUSER_PASSWORD`: Das Passwort für den Testuser User.
+- `TESTSERVICEUSER_PASSWORD`: Das Passwort für den Testserviceuser User.
+- `KEYCLOAK_NEXTCLOUD_CLIENT_SECRET`: Das Client Secret für Nextcloud.
 
 Die Passwörter in der `.env` Datei im `scripts/setup/keycloak` werden vom `setup_environment.sh` Skript generiert.
 
@@ -297,7 +301,7 @@ Die folgenden Clients werden über das Skript konfiguriert:
 - **Nextcloud:**
   -   **Client ID:** `nextcloud`
   -   **Name:** `Nextcloud`
-  -   **Redirect URIs:** `https://cloud.mrx8086.com/*`
+  -   **Redirect URIs:** `https://cloud.mrx8086.com/apps/sociallogin/custom_oidc/keycloak`, `https://cloud.mrx8086.com/apps/user_oidc/code`
 - **Paperless:**
   -   **Client ID:** `paperless`
   -   **Name:** `Paperless`
@@ -308,18 +312,24 @@ Die folgenden Clients werden über das Skript konfiguriert:
   -   **Redirect URIs:** `https://automate.mrx8086.com/*`
 
 ##### Gruppen
-- **Administrators:** Gruppe für Benutzer mit vollem Systemzugriff.
-- **Users:** Gruppe für reguläre Systembenutzer.
+- **nextcloud-admins:** Gruppe für Benutzer mit vollem Nextcloud Zugriff.
+- **nextcloud-users:** Gruppe für reguläre Nextcloud Benutzer.
+- **nextcloud-youpi:** Gruppe für Youpi User.
+- **nextcloud-service:** Gruppe für Service User.
 
 ##### Benutzer
 - **testadmin:**
   - Benutzername: `testadmin`
   - Passwort: wird entweder aus der Umgebungsvariable `TESTADMIN_PASSWORD` gelesen oder standardmäßig `initial123!` gesetzt.
-  - Gruppe: `/Administrators`
+  - Gruppe: `/nextcloud-admins`, `/nextcloud-users`
 - **testuser:**
   - Benutzername: `testuser`
   - Passwort: wird entweder aus der Umgebungsvariable `TESTUSER_PASSWORD` gelesen oder standardmäßig `initial123!` gesetzt.
-  - Gruppe: `/Users`
+  - Gruppe: `/nextcloud-users`, `/nextcloud-youpi`
+- **testserviceuser:**
+  - Benutzername: `testserviceuser`
+  - Passwort: wird entweder aus der Umgebungsvariable `TESTSERVICEUSER_PASSWORD` gelesen oder standardmäßig `initial123!` gesetzt.
+  - Gruppe: `/nextcloud-service`
 
 #### SSL/TLS-Setup
 -   **Entwicklungsumgebung (`dev`):** Für lokale Entwicklung werden selbsignierte SSL-Zertifikate verwendet.

+ 223 - 309
docs/context/project_structure.txt

@@ -1,309 +1,223 @@
-../../
-├── ansible
-│   ├── inventory
-│   │   ├── production
-│   │   └── staging
-│   │       └── hosts
-│   ├── roles
-│   │   ├── common
-│   │   │   ├── defaults
-│   │   │   │   └── main.yml
-│   │   │   ├── files
-│   │   │   ├── handlers
-│   │   │   │   └── main.yml
-│   │   │   ├── meta
-│   │   │   │   └── main.yml
-│   │   │   ├── tasks
-│   │   │   │   └── main.yml
-│   │   │   ├── templates
-│   │   │   │   └── jail.local.j2
-│   │   │   └── vars
-│   │   │       └── main.yml
-│   │   ├── docker
-│   │   │   ├── defaults
-│   │   │   │   └── main.yml
-│   │   │   ├── files
-│   │   │   ├── handlers
-│   │   │   │   └── main.yml
-│   │   │   ├── meta
-│   │   │   │   └── main.yml
-│   │   │   ├── tasks
-│   │   │   │   └── main.yml
-│   │   │   ├── templates
-│   │   │   │   └── daemon.json.j2
-│   │   │   └── vars
-│   │   │       └── main.yml
-│   │   ├── nginx
-│   │   │   ├── defaults
-│   │   │   │   └── main.yml
-│   │   │   ├── files
-│   │   │   ├── handlers
-│   │   │   │   └── main.yml
-│   │   │   ├── meta
-│   │   │   │   └── main.yml
-│   │   │   ├── tasks
-│   │   │   │   └── main.yml
-│   │   │   ├── templates
-│   │   │   └── vars
-│   │   │       └── main.yml
-│   │   └── services
-│   ├── site.yml
-│   └── vars
-│       ├── customers
-│       │   └── customer1.yml
-│       └── defaults
-│           └── main.yml
-├── config
-│   ├── credentials
-│   │   └── credentials_2024-12-11_18-28-21.txt.gpg
-│   ├── keycloak
-│   │   └── themes
-│   ├── nextcloud
-│   ├── nginx
-│   │   ├── sites-available
-│   │   │   ├── default
-│   │   │   ├── keycloak
-│   │   │   ├── n8n
-│   │   │   ├── nextcloud
-│   │   │   ├── nodered
-│   │   │   └── paperless
-│   │   └── ssl
-│   │       └── mrx8086.com
-│   │           ├── cert.pem
-│   │           ├── fullchain.pem
-│   │           └── privkey.pem
-│   ├── nodered
-│   └── paperless
-├── data
-│   └── keycloak
-│       └── db  [error opening dir]
-├── docker
-│   ├── Containerfile
-│   └── docker-compose.yml
-├── docs
-│   ├── context
-│   │   ├── ansible_setup.md
-│   │   ├── architecture
-│   │   │   └── system_architecture.md
-│   │   ├── configuration
-│   │   │   ├── docker-compose.yml
-│   │   │   └── env-template
-│   │   ├── current_state.md
-│   │   ├── nginx_configs
-│   │   │   ├── default
-│   │   │   ├── keycloak
-│   │   │   ├── n8n
-│   │   │   ├── nextcloud
-│   │   │   ├── nodered
-│   │   │   └── paperless
-│   │   ├── project_documentation.md
-│   │   ├── project_structure.txt
-│   │   └── scripts
-│   │       └── setup_environment.sh
-│   ├── maintenance
-│   ├── setup
-│   └── workflows
-└── scripts
-    ├── backup
-    ├── install
-    │   ├── install_and_setup.sh
-    │   └── setup_environment.sh
-    ├── maintenance
-    └── setup
-        └── keycloak
-            ├── node_modules
-            │   ├── asynckit
-            │   │   ├── LICENSE
-            │   │   ├── README.md
-            │   │   ├── bench.js
-            │   │   ├── index.js
-            │   │   ├── lib
-            │   │   │   ├── abort.js
-            │   │   │   ├── async.js
-            │   │   │   ├── defer.js
-            │   │   │   ├── iterate.js
-            │   │   │   ├── readable_asynckit.js
-            │   │   │   ├── readable_parallel.js
-            │   │   │   ├── readable_serial.js
-            │   │   │   ├── readable_serial_ordered.js
-            │   │   │   ├── state.js
-            │   │   │   ├── streamify.js
-            │   │   │   └── terminator.js
-            │   │   ├── package.json
-            │   │   ├── parallel.js
-            │   │   ├── serial.js
-            │   │   ├── serialOrdered.js
-            │   │   └── stream.js
-            │   ├── axios
-            │   │   ├── CHANGELOG.md
-            │   │   ├── LICENSE
-            │   │   ├── MIGRATION_GUIDE.md
-            │   │   ├── README.md
-            │   │   ├── SECURITY.md
-            │   │   ├── dist
-            │   │   │   ├── axios.js
-            │   │   │   ├── axios.js.map
-            │   │   │   ├── axios.min.js
-            │   │   │   ├── axios.min.js.map
-            │   │   │   ├── browser
-            │   │   │   │   ├── axios.cjs
-            │   │   │   │   └── axios.cjs.map
-            │   │   │   ├── esm
-            │   │   │   │   ├── axios.js
-            │   │   │   │   ├── axios.js.map
-            │   │   │   │   ├── axios.min.js
-            │   │   │   │   └── axios.min.js.map
-            │   │   │   └── node
-            │   │   │       ├── axios.cjs
-            │   │   │       └── axios.cjs.map
-            │   │   ├── index.d.cts
-            │   │   ├── index.d.ts
-            │   │   ├── index.js
-            │   │   ├── lib
-            │   │   │   ├── adapters
-            │   │   │   │   ├── README.md
-            │   │   │   │   ├── adapters.js
-            │   │   │   │   ├── fetch.js
-            │   │   │   │   ├── http.js
-            │   │   │   │   └── xhr.js
-            │   │   │   ├── axios.js
-            │   │   │   ├── cancel
-            │   │   │   │   ├── CancelToken.js
-            │   │   │   │   ├── CanceledError.js
-            │   │   │   │   └── isCancel.js
-            │   │   │   ├── core
-            │   │   │   │   ├── Axios.js
-            │   │   │   │   ├── AxiosError.js
-            │   │   │   │   ├── AxiosHeaders.js
-            │   │   │   │   ├── InterceptorManager.js
-            │   │   │   │   ├── README.md
-            │   │   │   │   ├── buildFullPath.js
-            │   │   │   │   ├── dispatchRequest.js
-            │   │   │   │   ├── mergeConfig.js
-            │   │   │   │   ├── settle.js
-            │   │   │   │   └── transformData.js
-            │   │   │   ├── defaults
-            │   │   │   │   ├── index.js
-            │   │   │   │   └── transitional.js
-            │   │   │   ├── env
-            │   │   │   │   ├── README.md
-            │   │   │   │   ├── classes
-            │   │   │   │   │   └── FormData.js
-            │   │   │   │   └── data.js
-            │   │   │   ├── helpers
-            │   │   │   │   ├── AxiosTransformStream.js
-            │   │   │   │   ├── AxiosURLSearchParams.js
-            │   │   │   │   ├── HttpStatusCode.js
-            │   │   │   │   ├── README.md
-            │   │   │   │   ├── ZlibHeaderTransformStream.js
-            │   │   │   │   ├── bind.js
-            │   │   │   │   ├── buildURL.js
-            │   │   │   │   ├── callbackify.js
-            │   │   │   │   ├── combineURLs.js
-            │   │   │   │   ├── composeSignals.js
-            │   │   │   │   ├── cookies.js
-            │   │   │   │   ├── deprecatedMethod.js
-            │   │   │   │   ├── formDataToJSON.js
-            │   │   │   │   ├── formDataToStream.js
-            │   │   │   │   ├── fromDataURI.js
-            │   │   │   │   ├── isAbsoluteURL.js
-            │   │   │   │   ├── isAxiosError.js
-            │   │   │   │   ├── isURLSameOrigin.js
-            │   │   │   │   ├── null.js
-            │   │   │   │   ├── parseHeaders.js
-            │   │   │   │   ├── parseProtocol.js
-            │   │   │   │   ├── progressEventReducer.js
-            │   │   │   │   ├── readBlob.js
-            │   │   │   │   ├── resolveConfig.js
-            │   │   │   │   ├── speedometer.js
-            │   │   │   │   ├── spread.js
-            │   │   │   │   ├── throttle.js
-            │   │   │   │   ├── toFormData.js
-            │   │   │   │   ├── toURLEncodedForm.js
-            │   │   │   │   ├── trackStream.js
-            │   │   │   │   └── validator.js
-            │   │   │   ├── platform
-            │   │   │   │   ├── browser
-            │   │   │   │   │   ├── classes
-            │   │   │   │   │   │   ├── Blob.js
-            │   │   │   │   │   │   ├── FormData.js
-            │   │   │   │   │   │   └── URLSearchParams.js
-            │   │   │   │   │   └── index.js
-            │   │   │   │   ├── common
-            │   │   │   │   │   └── utils.js
-            │   │   │   │   ├── index.js
-            │   │   │   │   └── node
-            │   │   │   │       ├── classes
-            │   │   │   │       │   ├── FormData.js
-            │   │   │   │       │   └── URLSearchParams.js
-            │   │   │   │       └── index.js
-            │   │   │   └── utils.js
-            │   │   └── package.json
-            │   ├── combined-stream
-            │   │   ├── License
-            │   │   ├── Readme.md
-            │   │   ├── lib
-            │   │   │   └── combined_stream.js
-            │   │   ├── package.json
-            │   │   └── yarn.lock
-            │   ├── delayed-stream
-            │   │   ├── License
-            │   │   ├── Makefile
-            │   │   ├── Readme.md
-            │   │   ├── lib
-            │   │   │   └── delayed_stream.js
-            │   │   └── package.json
-            │   ├── dotenv
-            │   │   ├── CHANGELOG.md
-            │   │   ├── LICENSE
-            │   │   ├── README-es.md
-            │   │   ├── README.md
-            │   │   ├── config.d.ts
-            │   │   ├── config.js
-            │   │   ├── lib
-            │   │   │   ├── cli-options.js
-            │   │   │   ├── env-options.js
-            │   │   │   ├── main.d.ts
-            │   │   │   └── main.js
-            │   │   └── package.json
-            │   ├── follow-redirects
-            │   │   ├── LICENSE
-            │   │   ├── README.md
-            │   │   ├── debug.js
-            │   │   ├── http.js
-            │   │   ├── https.js
-            │   │   ├── index.js
-            │   │   └── package.json
-            │   ├── form-data
-            │   │   ├── License
-            │   │   ├── Readme.md
-            │   │   ├── index.d.ts
-            │   │   ├── lib
-            │   │   │   ├── browser.js
-            │   │   │   ├── form_data.js
-            │   │   │   └── populate.js
-            │   │   └── package.json
-            │   ├── mime-db
-            │   │   ├── HISTORY.md
-            │   │   ├── LICENSE
-            │   │   ├── README.md
-            │   │   ├── db.json
-            │   │   ├── index.js
-            │   │   └── package.json
-            │   ├── mime-types
-            │   │   ├── HISTORY.md
-            │   │   ├── LICENSE
-            │   │   ├── README.md
-            │   │   ├── index.js
-            │   │   └── package.json
-            │   └── proxy-from-env
-            │       ├── LICENSE
-            │       ├── README.md
-            │       ├── index.js
-            │       ├── package.json
-            │       └── test.js
-            ├── package-lock.json
-            ├── package.json
-            └── setup_realm.js
-
-98 directories, 209 files
+.gitignore
+ansible/inventory/staging/hosts
+ansible/requirements.yml
+ansible/roles/common/defaults/main.yml
+ansible/roles/common/handlers/main.yml
+ansible/roles/common/meta/main.yml
+ansible/roles/common/tasks/main.yml
+ansible/roles/common/templates/jail.local.j2
+ansible/roles/common/vars/main.yml
+ansible/roles/docker/defaults/main.yml
+ansible/roles/docker/handlers/main.yml
+ansible/roles/docker/meta/main.yml
+ansible/roles/docker/tasks/main.yml
+ansible/roles/docker/templates/daemon.json.j2
+ansible/roles/docker/vars/main.yml
+ansible/roles/nginx/defaults/main.yml
+ansible/roles/nginx/handlers/main.yml
+ansible/roles/nginx/meta/main.yml
+ansible/roles/nginx/tasks/main.yml
+ansible/roles/nginx/vars/main.yml
+ansible/roles/services/defaults/main.yml
+ansible/roles/services/tasks/main.yml
+ansible/site.yml
+ansible/vars/customers/customer1.yml
+ansible/vars/defaults/main.yml
+config/nginx/sites-available/default
+config/nginx/sites-available/keycloak
+config/nginx/sites-available/n8n
+config/nginx/sites-available/nextcloud
+config/nginx/sites-available/nodered
+config/nginx/sites-available/paperless
+config/nginx/ssl/mrx8086.com/cert.pem
+config/nginx/ssl/mrx8086.com/fullchain.pem
+config/nginx/ssl/mrx8086.com/privkey.pem
+docker/.env
+docker/docker-compose.yml
+docker/keycloak.Containerfile
+docs/context/ansible_setup.md
+docs/context/architecture/system_architecture.md
+docs/context/configuration/docker-compose.yml
+docs/context/configuration/env-template
+docs/context/current_state.md
+docs/context/nginx_configs/default
+docs/context/nginx_configs/keycloak
+docs/context/nginx_configs/n8n
+docs/context/nginx_configs/nextcloud
+docs/context/nginx_configs/nodered
+docs/context/nginx_configs/paperless
+docs/context/project_documentation.md
+docs/context/project_structure.txt
+docs/context/scripts/setup_environment.sh
+git_diff.txt
+scripts/install/install_and_setup.sh
+scripts/install/setup_environment.sh
+scripts/setup/keycloak/.env
+scripts/setup/keycloak/check_client_mappers.js
+scripts/setup/keycloak/create_groups_nextcloud_scope.js
+scripts/setup/keycloak/delete_groups_nextcloud_scope.js
+scripts/setup/keycloak/node_modules/.package-lock.json
+scripts/setup/keycloak/node_modules/asynckit/LICENSE
+scripts/setup/keycloak/node_modules/asynckit/README.md
+scripts/setup/keycloak/node_modules/asynckit/bench.js
+scripts/setup/keycloak/node_modules/asynckit/index.js
+scripts/setup/keycloak/node_modules/asynckit/lib/abort.js
+scripts/setup/keycloak/node_modules/asynckit/lib/async.js
+scripts/setup/keycloak/node_modules/asynckit/lib/defer.js
+scripts/setup/keycloak/node_modules/asynckit/lib/iterate.js
+scripts/setup/keycloak/node_modules/asynckit/lib/readable_asynckit.js
+scripts/setup/keycloak/node_modules/asynckit/lib/readable_parallel.js
+scripts/setup/keycloak/node_modules/asynckit/lib/readable_serial.js
+scripts/setup/keycloak/node_modules/asynckit/lib/readable_serial_ordered.js
+scripts/setup/keycloak/node_modules/asynckit/lib/state.js
+scripts/setup/keycloak/node_modules/asynckit/lib/streamify.js
+scripts/setup/keycloak/node_modules/asynckit/lib/terminator.js
+scripts/setup/keycloak/node_modules/asynckit/package.json
+scripts/setup/keycloak/node_modules/asynckit/parallel.js
+scripts/setup/keycloak/node_modules/asynckit/serial.js
+scripts/setup/keycloak/node_modules/asynckit/serialOrdered.js
+scripts/setup/keycloak/node_modules/asynckit/stream.js
+scripts/setup/keycloak/node_modules/axios/CHANGELOG.md
+scripts/setup/keycloak/node_modules/axios/LICENSE
+scripts/setup/keycloak/node_modules/axios/MIGRATION_GUIDE.md
+scripts/setup/keycloak/node_modules/axios/README.md
+scripts/setup/keycloak/node_modules/axios/SECURITY.md
+scripts/setup/keycloak/node_modules/axios/dist/axios.js
+scripts/setup/keycloak/node_modules/axios/dist/axios.js.map
+scripts/setup/keycloak/node_modules/axios/dist/axios.min.js
+scripts/setup/keycloak/node_modules/axios/dist/axios.min.js.map
+scripts/setup/keycloak/node_modules/axios/dist/browser/axios.cjs
+scripts/setup/keycloak/node_modules/axios/dist/browser/axios.cjs.map
+scripts/setup/keycloak/node_modules/axios/dist/esm/axios.js
+scripts/setup/keycloak/node_modules/axios/dist/esm/axios.js.map
+scripts/setup/keycloak/node_modules/axios/dist/esm/axios.min.js
+scripts/setup/keycloak/node_modules/axios/dist/esm/axios.min.js.map
+scripts/setup/keycloak/node_modules/axios/dist/node/axios.cjs
+scripts/setup/keycloak/node_modules/axios/dist/node/axios.cjs.map
+scripts/setup/keycloak/node_modules/axios/index.d.cts
+scripts/setup/keycloak/node_modules/axios/index.d.ts
+scripts/setup/keycloak/node_modules/axios/index.js
+scripts/setup/keycloak/node_modules/axios/lib/adapters/README.md
+scripts/setup/keycloak/node_modules/axios/lib/adapters/adapters.js
+scripts/setup/keycloak/node_modules/axios/lib/adapters/fetch.js
+scripts/setup/keycloak/node_modules/axios/lib/adapters/http.js
+scripts/setup/keycloak/node_modules/axios/lib/adapters/xhr.js
+scripts/setup/keycloak/node_modules/axios/lib/axios.js
+scripts/setup/keycloak/node_modules/axios/lib/cancel/CancelToken.js
+scripts/setup/keycloak/node_modules/axios/lib/cancel/CanceledError.js
+scripts/setup/keycloak/node_modules/axios/lib/cancel/isCancel.js
+scripts/setup/keycloak/node_modules/axios/lib/core/Axios.js
+scripts/setup/keycloak/node_modules/axios/lib/core/AxiosError.js
+scripts/setup/keycloak/node_modules/axios/lib/core/AxiosHeaders.js
+scripts/setup/keycloak/node_modules/axios/lib/core/InterceptorManager.js
+scripts/setup/keycloak/node_modules/axios/lib/core/README.md
+scripts/setup/keycloak/node_modules/axios/lib/core/buildFullPath.js
+scripts/setup/keycloak/node_modules/axios/lib/core/dispatchRequest.js
+scripts/setup/keycloak/node_modules/axios/lib/core/mergeConfig.js
+scripts/setup/keycloak/node_modules/axios/lib/core/settle.js
+scripts/setup/keycloak/node_modules/axios/lib/core/transformData.js
+scripts/setup/keycloak/node_modules/axios/lib/defaults/index.js
+scripts/setup/keycloak/node_modules/axios/lib/defaults/transitional.js
+scripts/setup/keycloak/node_modules/axios/lib/env/README.md
+scripts/setup/keycloak/node_modules/axios/lib/env/classes/FormData.js
+scripts/setup/keycloak/node_modules/axios/lib/env/data.js
+scripts/setup/keycloak/node_modules/axios/lib/helpers/AxiosTransformStream.js
+scripts/setup/keycloak/node_modules/axios/lib/helpers/AxiosURLSearchParams.js
+scripts/setup/keycloak/node_modules/axios/lib/helpers/HttpStatusCode.js
+scripts/setup/keycloak/node_modules/axios/lib/helpers/README.md
+scripts/setup/keycloak/node_modules/axios/lib/helpers/ZlibHeaderTransformStream.js
+scripts/setup/keycloak/node_modules/axios/lib/helpers/bind.js
+scripts/setup/keycloak/node_modules/axios/lib/helpers/buildURL.js
+scripts/setup/keycloak/node_modules/axios/lib/helpers/callbackify.js
+scripts/setup/keycloak/node_modules/axios/lib/helpers/combineURLs.js
+scripts/setup/keycloak/node_modules/axios/lib/helpers/composeSignals.js
+scripts/setup/keycloak/node_modules/axios/lib/helpers/cookies.js
+scripts/setup/keycloak/node_modules/axios/lib/helpers/deprecatedMethod.js
+scripts/setup/keycloak/node_modules/axios/lib/helpers/formDataToJSON.js
+scripts/setup/keycloak/node_modules/axios/lib/helpers/formDataToStream.js
+scripts/setup/keycloak/node_modules/axios/lib/helpers/fromDataURI.js
+scripts/setup/keycloak/node_modules/axios/lib/helpers/isAbsoluteURL.js
+scripts/setup/keycloak/node_modules/axios/lib/helpers/isAxiosError.js
+scripts/setup/keycloak/node_modules/axios/lib/helpers/isURLSameOrigin.js
+scripts/setup/keycloak/node_modules/axios/lib/helpers/null.js
+scripts/setup/keycloak/node_modules/axios/lib/helpers/parseHeaders.js
+scripts/setup/keycloak/node_modules/axios/lib/helpers/parseProtocol.js
+scripts/setup/keycloak/node_modules/axios/lib/helpers/progressEventReducer.js
+scripts/setup/keycloak/node_modules/axios/lib/helpers/readBlob.js
+scripts/setup/keycloak/node_modules/axios/lib/helpers/resolveConfig.js
+scripts/setup/keycloak/node_modules/axios/lib/helpers/speedometer.js
+scripts/setup/keycloak/node_modules/axios/lib/helpers/spread.js
+scripts/setup/keycloak/node_modules/axios/lib/helpers/throttle.js
+scripts/setup/keycloak/node_modules/axios/lib/helpers/toFormData.js
+scripts/setup/keycloak/node_modules/axios/lib/helpers/toURLEncodedForm.js
+scripts/setup/keycloak/node_modules/axios/lib/helpers/trackStream.js
+scripts/setup/keycloak/node_modules/axios/lib/helpers/validator.js
+scripts/setup/keycloak/node_modules/axios/lib/platform/browser/classes/Blob.js
+scripts/setup/keycloak/node_modules/axios/lib/platform/browser/classes/FormData.js
+scripts/setup/keycloak/node_modules/axios/lib/platform/browser/classes/URLSearchParams.js
+scripts/setup/keycloak/node_modules/axios/lib/platform/browser/index.js
+scripts/setup/keycloak/node_modules/axios/lib/platform/common/utils.js
+scripts/setup/keycloak/node_modules/axios/lib/platform/index.js
+scripts/setup/keycloak/node_modules/axios/lib/platform/node/classes/FormData.js
+scripts/setup/keycloak/node_modules/axios/lib/platform/node/classes/URLSearchParams.js
+scripts/setup/keycloak/node_modules/axios/lib/platform/node/index.js
+scripts/setup/keycloak/node_modules/axios/lib/utils.js
+scripts/setup/keycloak/node_modules/axios/package.json
+scripts/setup/keycloak/node_modules/combined-stream/License
+scripts/setup/keycloak/node_modules/combined-stream/Readme.md
+scripts/setup/keycloak/node_modules/combined-stream/lib/combined_stream.js
+scripts/setup/keycloak/node_modules/combined-stream/package.json
+scripts/setup/keycloak/node_modules/combined-stream/yarn.lock
+scripts/setup/keycloak/node_modules/delayed-stream/.npmignore
+scripts/setup/keycloak/node_modules/delayed-stream/License
+scripts/setup/keycloak/node_modules/delayed-stream/Makefile
+scripts/setup/keycloak/node_modules/delayed-stream/Readme.md
+scripts/setup/keycloak/node_modules/delayed-stream/lib/delayed_stream.js
+scripts/setup/keycloak/node_modules/delayed-stream/package.json
+scripts/setup/keycloak/node_modules/dotenv/CHANGELOG.md
+scripts/setup/keycloak/node_modules/dotenv/LICENSE
+scripts/setup/keycloak/node_modules/dotenv/README-es.md
+scripts/setup/keycloak/node_modules/dotenv/README.md
+scripts/setup/keycloak/node_modules/dotenv/config.d.ts
+scripts/setup/keycloak/node_modules/dotenv/config.js
+scripts/setup/keycloak/node_modules/dotenv/lib/cli-options.js
+scripts/setup/keycloak/node_modules/dotenv/lib/env-options.js
+scripts/setup/keycloak/node_modules/dotenv/lib/main.d.ts
+scripts/setup/keycloak/node_modules/dotenv/lib/main.js
+scripts/setup/keycloak/node_modules/dotenv/package.json
+scripts/setup/keycloak/node_modules/follow-redirects/LICENSE
+scripts/setup/keycloak/node_modules/follow-redirects/README.md
+scripts/setup/keycloak/node_modules/follow-redirects/debug.js
+scripts/setup/keycloak/node_modules/follow-redirects/http.js
+scripts/setup/keycloak/node_modules/follow-redirects/https.js
+scripts/setup/keycloak/node_modules/follow-redirects/index.js
+scripts/setup/keycloak/node_modules/follow-redirects/package.json
+scripts/setup/keycloak/node_modules/form-data/License
+scripts/setup/keycloak/node_modules/form-data/Readme.md
+scripts/setup/keycloak/node_modules/form-data/index.d.ts
+scripts/setup/keycloak/node_modules/form-data/lib/browser.js
+scripts/setup/keycloak/node_modules/form-data/lib/form_data.js
+scripts/setup/keycloak/node_modules/form-data/lib/populate.js
+scripts/setup/keycloak/node_modules/form-data/package.json
+scripts/setup/keycloak/node_modules/mime-db/HISTORY.md
+scripts/setup/keycloak/node_modules/mime-db/LICENSE
+scripts/setup/keycloak/node_modules/mime-db/README.md
+scripts/setup/keycloak/node_modules/mime-db/db.json
+scripts/setup/keycloak/node_modules/mime-db/index.js
+scripts/setup/keycloak/node_modules/mime-db/package.json
+scripts/setup/keycloak/node_modules/mime-types/HISTORY.md
+scripts/setup/keycloak/node_modules/mime-types/LICENSE
+scripts/setup/keycloak/node_modules/mime-types/README.md
+scripts/setup/keycloak/node_modules/mime-types/index.js
+scripts/setup/keycloak/node_modules/mime-types/package.json
+scripts/setup/keycloak/node_modules/proxy-from-env/.eslintrc
+scripts/setup/keycloak/node_modules/proxy-from-env/.travis.yml
+scripts/setup/keycloak/node_modules/proxy-from-env/LICENSE
+scripts/setup/keycloak/node_modules/proxy-from-env/README.md
+scripts/setup/keycloak/node_modules/proxy-from-env/index.js
+scripts/setup/keycloak/node_modules/proxy-from-env/package.json
+scripts/setup/keycloak/node_modules/proxy-from-env/test.js
+scripts/setup/keycloak/package-lock.json
+scripts/setup/keycloak/package.json
+scripts/setup/keycloak/setup_realm.js
+scripts/setup/keycloak/test_realm.js

+ 157 - 69
docs/context/scripts/setup_environment.sh

@@ -3,42 +3,161 @@ set -e
 
 # Ensure we're in the project root directory
 PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
-cd "${PROJECT_ROOT}"
 
 # Define directories relative to project root
-CREDENTIALS_DIR="config/credentials"
-DOCKER_DIR="docker"
-KEYCLOAK_SCRIPTS_DIR="scripts/setup/keycloak"
-ANSIBLE_PLAYBOOK="ansible/site.yml"
-ANSIBLE_INVENTORY="ansible/inventory/staging/hosts"
+CREDENTIALS_DIR="${PROJECT_ROOT}/config/credentials"
+DOCKER_DIR="${PROJECT_ROOT}/docker"
+KEYCLOAK_SETUP_DIR="${PROJECT_ROOT}/scripts/setup/keycloak"
+ANSIBLE_PLAYBOOK="${PROJECT_ROOT}/ansible/site.yml"
+ANSIBLE_INVENTORY="${PROJECT_ROOT}/ansible/inventory/staging/hosts"
+NEXTCLOUD_DATA_DIR="${PROJECT_ROOT}/data/nextcloud/data"
+TEMP_FILE=$(mktemp)
+KEYCLOAK_DB_DIR="${PROJECT_ROOT}/data/keycloak-db"
 
 # Create necessary directories
-mkdir -p "${CREDENTIALS_DIR}"
-mkdir -p "${DOCKER_DIR}"
-mkdir -p "${KEYCLOAK_SCRIPTS_DIR}"
+sudo mkdir -p "${CREDENTIALS_DIR}"
+sudo mkdir -p "${DOCKER_DIR}"
+sudo mkdir -p "${KEYCLOAK_SETUP_DIR}"
+
+# Initialize password variables
+KEYCLOAK_ADMIN_PASSWORD=""
+KC_DB_PASSWORD=""
+TESTADMIN_PASSWORD=""
+TESTUSER_PASSWORD=""
+TESTSERVICEUSER_PASSWORD=""
+KEYCLOAK_NEXTCLOUD_CLIENT_SECRET=""
+
+# Function to read a password from a .env file
+read_password_from_env() {
+    local env_file="$1"
+    local variable_name="$2"
+    if [ -f "$env_file" ]; then
+        grep "^${variable_name}=" "$env_file" | cut -d '=' -f2
+    fi
+}
 
 # Function to generate secure passwords
 generate_password() {
-    openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 24
+    openssl rand -base64 32
+}
+
+# Function to generate password if empty
+generate_password_if_empty() {
+    local variable_name="$1"
+    eval "local value=\$$variable_name"
+    if [ -z "$value" ]; then
+        eval "$variable_name=\"$(generate_password)\""
+        echo ">>> Generiertes Passwort für: $variable_name"
+    fi
 }
 
+# Function to create .env file
+create_env_file() {
+    local env_file="$1"
+    local content="$2"
+    if [ ! -f "$env_file" ]; then
+        echo "$content" > "$env_file"
+        echo ">>> .env file created: $env_file"
+    else
+        echo ">>> .env file already exists: $env_file"
+    fi
+}
+
+echo ">>> Überprüfe bestehende .env Dateien und lese Passwörter..."
+
+# Try reading passwords from existing .env files
+if [ -f "$DOCKER_DIR/.env" ]; then
+    KC_DB_PASSWORD=$(read_password_from_env "$DOCKER_DIR/.env" "KC_DB_PASSWORD")
+    KEYCLOAK_ADMIN_PASSWORD=$(read_password_from_env "$DOCKER_DIR/.env" "KEYCLOAK_ADMIN_PASSWORD")
+fi
+
+if [ -f "$KEYCLOAK_SETUP_DIR/.env" ]; then
+    KEYCLOAK_ADMIN_PASSWORD=$(read_password_from_env "$KEYCLOAK_SETUP_DIR/.env" "KEYCLOAK_ADMIN_PASSWORD") # Überschreibt ggf. den Wert aus docker/.env
+    TESTADMIN_PASSWORD=$(read_password_from_env "$KEYCLOAK_SETUP_DIR/.env" "TESTADMIN_PASSWORD")
+    TESTUSER_PASSWORD=$(read_password_from_env "$KEYCLOAK_SETUP_DIR/.env" "TESTUSER_PASSWORD")
+    TESTSERVICEUSER_PASSWORD=$(read_password_from_env "$KEYCLOAK_SETUP_DIR/.env" "TESTSERVICEUSER_PASSWORD")
+    KEYCLOAK_NEXTCLOUD_CLIENT_SECRET=$(read_password_from_env "$KEYCLOAK_SETUP_DIR/.env" "KEYCLOAK_NEXTCLOUD_CLIENT_SECRET")
+fi
+
+echo ">>> Generiere neue Passwörter für fehlende Werte..."
+
+# Generate passwords if they are still empty
+generate_password_if_empty KEYCLOAK_ADMIN_PASSWORD
+generate_password_if_empty KC_DB_PASSWORD
+generate_password_if_empty TESTADMIN_PASSWORD
+generate_password_if_empty TESTUSER_PASSWORD
+generate_password_if_empty TESTSERVICEUSER_PASSWORD
+generate_password_if_empty KEYCLOAK_NEXTCLOUD_CLIENT_SECRET
+
 # Date for documentation
 SETUP_DATE=$(date '+%Y-%m-%d_%H-%M-%S')
 
-# Generate passwords
-KEYCLOAK_ADMIN_PASSWORD=$(generate_password)
-KC_DB_PASSWORD=$(generate_password)
-TESTADMIN_PASSWORD=$(generate_password)
-TESTUSER_PASSWORD=$(generate_password)
-NEXTCLOUD_DB_ROOT_PASSWORD=$(generate_password)
-NEXTCLOUD_DB_USER=$(generate_password)
-NEXTCLOUD_DB_PASSWORD=$(generate_password)
-NEXTCLOUD_ADMIN_USER=$(generate_password)
-NEXTCLOUD_ADMIN_PASSWORD=$(generate_password)
-KEYCLOAK_NEXTCLOUD_CLIENT_SECRET=$(generate_password)
+# Create credentials content
+CREDENTIALS_CONTENT=$(cat <<EOL
+Setup Date: ${SETUP_DATE}
+
+Keycloak Admin Credentials:
+Username: admin
+Password: ${KEYCLOAK_ADMIN_PASSWORD}
+
+Keycloak Database Credentials:
+Username: keycloak
+Password: ${KC_DB_PASSWORD}
+
+Test User Credentials:
+Admin Password: ${TESTADMIN_PASSWORD}
+User Password: ${TESTUSER_PASSWORD}
+Service User Password: ${TESTSERVICEUSER_PASSWORD}
+Nextcloud Client Secret: ${KEYCLOAK_NEXTCLOUD_CLIENT_SECRET}
+
+EOL
+)
+
+# Store credentials hash
+CREDENTIALS_HASH=$(echo "$CREDENTIALS_CONTENT" | sha256sum | awk '{print $1}')
+echo "$CREDENTIALS_HASH" > "${CREDENTIALS_DIR}/credentials_hash.txt"
+echo ">>> Credentials hash stored in: ${CREDENTIALS_DIR}/credentials_hash.txt"
+
+# Set GPG PASSPHRASE
+export GPG_PASSPHRASE=$(generate_password)
+# Set GPG agent environment variable
+export GPG_TTY=$(tty)
+
+echo ">>> Trying openssl encryption first"
+# Alternative Verschlüsselung mit Openssl
+echo "$CREDENTIALS_CONTENT" > "$TEMP_FILE"
+          if openssl enc -aes-256-cbc -pbkdf2 -salt -in "$TEMP_FILE" -out "${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt.enc" -k "$GPG_PASSPHRASE" ; then
+             echo ">>> Credentials encrypted successfully using openssl"
+             mv  "${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt.enc" "${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt.gpg"
+        else
+           echo ">>> Openssl encryption failed, trying gpg"
+
+          # Attempt to kill existing gpg agent
+          gpgconf --kill gpg-agent 2>/dev/null
+          echo ">>> Attempting to manually start gpg-agent with pinentry-curses"
+          gpg-agent --daemon --pinentry-program /usr/bin/pinentry-curses
+          gpg-connect-agent /bye 2>/dev/null
+          eval $(gpg-agent --daemon)
+          gpg-connect-agent updatestartuptty /bye 2>/dev/null
+
+          # Attempt to encrypt credentials using GPG with error handling
+          if echo "$CREDENTIALS_CONTENT" | gpg --symmetric --cipher-algo AES256 -vvv -o "${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt.gpg" ; then
+                echo ">>> Credentials encrypted successfully using gpg."
+          else
+             echo ">>> GPG encryption failed. Attempting GPG encryption with password workaround."
+                # Attempt encryption with passphrase workaround
+                if echo "$CREDENTIALS_CONTENT" | gpg --batch --passphrase "$GPG_PASSPHRASE" --symmetric --cipher-algo AES256 -vvv -o "${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt.gpg"; then
+                      echo ">>> Credentials encrypted successfully using gpg with passphrase workaround."
+                else
+                   echo ">>> GPG encryption with passphrase workaround failed"
+                   exit 1
+                fi
+          fi
+       fi
+rm "$TEMP_FILE"
 
 # Create .env file in docker directory
-cat > "${DOCKER_DIR}/.env" << EOL
+DOCKER_ENV_CONTENT=$(cat <<EOL
 # Generated on ${SETUP_DATE}
 # Keycloak Admin
 KEYCLOAK_ADMIN_PASSWORD=${KEYCLOAK_ADMIN_PASSWORD}
@@ -46,18 +165,12 @@ KEYCLOAK_ADMIN_PASSWORD=${KEYCLOAK_ADMIN_PASSWORD}
 # Keycloak Database
 KC_DB_USERNAME=keycloak
 KC_DB_PASSWORD=${KC_DB_PASSWORD}
-
-# Nextcloud Database
-NEXTCLOUD_DB_ROOT_PASSWORD=${NEXTCLOUD_DB_ROOT_PASSWORD}
-NEXTCLOUD_DB_USER=${NEXTCLOUD_DB_USER}
-NEXTCLOUD_DB_PASSWORD=${NEXTCLOUD_DB_PASSWORD}
-NEXTCLOUD_ADMIN_USER=${NEXTCLOUD_ADMIN_USER}
-NEXTCLOUD_ADMIN_PASSWORD=${NEXTCLOUD_ADMIN_PASSWORD}
 EOL
+)
+create_env_file "$DOCKER_DIR/.env" "$DOCKER_ENV_CONTENT"
 
-# Create .env file for keycloak setup script
-cat > "${KEYCLOAK_SCRIPTS_DIR}/.env" << EOL
-# Generated on ${SETUP_DATE}
+# Create .env file in scripts/setup/keycloak directory
+KEYCLOAK_ENV_CONTENT=$(cat <<EOL
 KEYCLOAK_URL=https://auth.mrx8086.com
 KEYCLOAK_ADMIN_USER=admin
 KEYCLOAK_ADMIN_PASSWORD=${KEYCLOAK_ADMIN_PASSWORD}
@@ -66,49 +179,24 @@ PAPERLESS_CLIENT_ID=paperless
 NODERED_CLIENT_ID=nodered
 TESTADMIN_PASSWORD=${TESTADMIN_PASSWORD}
 TESTUSER_PASSWORD=${TESTUSER_PASSWORD}
+TESTSERVICEUSER_PASSWORD=${TESTSERVICEUSER_PASSWORD}
 KEYCLOAK_NEXTCLOUD_CLIENT_SECRET=${KEYCLOAK_NEXTCLOUD_CLIENT_SECRET}
 EOL
+)
+create_env_file "$KEYCLOAK_SETUP_DIR/.env" "$KEYCLOAK_ENV_CONTENT"
 
-# Create encrypted credentials documentation
-cat > "${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt" << EOL
-Setup Date: ${SETUP_DATE}
-
-Keycloak Admin Credentials:
-Username: admin
-Password: ${KEYCLOAK_ADMIN_PASSWORD}
-
-Keycloak Database Credentials:
-Username: keycloak
-Password: ${KC_DB_PASSWORD}
-
-Test Admin Credentials:
-Password: ${TESTADMIN_PASSWORD}
-
-Test User Credentials:
-Password: ${TESTUSER_PASSWORD}
+echo ">>> Environment setup completed!"
 
-Nextcloud Database Credentials:
-Root Password: ${NEXTCLOUD_DB_ROOT_PASSWORD}
-User: ${NEXTCLOUD_DB_USER}
-Password: ${NEXTCLOUD_DB_PASSWORD}
-
-Nextcloud Admin Credentials:
-Username: ${NEXTCLOUD_ADMIN_USER}
-Password: ${NEXTCLOUD_ADMIN_PASSWORD}
-EOL
+# --------------- KEYCLOAK KONFIGURATION ---------------
+echo ">>> Keycloak Konfiguration..."
+cd "$KEYCLOAK_SETUP_DIR"
 
-# Encrypt credentials file
-gpg --symmetric --cipher-algo AES256 "${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt"
-rm "${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt"
+echo ">>> Starte setup_realm.js"
+node setup_realm.js
 
-echo "Environment setup completed!"
-echo "Credentials have been saved and encrypted in: ${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt.gpg"
-echo ".env file for docker-compose has been created in: ${DOCKER_DIR}/.env"
-echo ".env file for setup_realm.js has been created in: ${KEYCLOAK_SCRIPTS_DIR}/.env"
-echo ""
-echo "To view credentials, use:"
-echo "gpg -d ${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt.gpg"
+cd "$PROJECT_ROOT"
 
+# --------------- NEXTCLOUD KONFIGURATION ---------------
 echo ">>> Nextcloud Konfiguration..."
 
 # Verify if variable is set from earlier in the script
@@ -118,7 +206,7 @@ echo ">>> Debug: KEYCLOAK_NEXTCLOUD_CLIENT_SECRET = ${KEYCLOAK_NEXTCLOUD_CLIENT_
 # Try reading from .env file if variable is empty
 if [ -z "${KEYCLOAK_NEXTCLOUD_CLIENT_SECRET}" ]; then
     echo ">>> Debug: Variable is empty, trying to read from .env file..."
-    KEYCLOAK_NEXTCLOUD_CLIENT_SECRET=$(grep KEYCLOAK_NEXTCLOUD_CLIENT_SECRET "${KEYCLOAK_SCRIPTS_DIR}/.env" | cut -d '=' -f2)
+    KEYCLOAK_NEXTCLOUD_CLIENT_SECRET=$(grep KEYCLOAK_NEXTCLOUD_CLIENT_SECRET "${KEYCLOAK_SETUP_DIR}/.env" | cut -d '=' -f2)
     echo ">>> Debug: Value from .env file = ${KEYCLOAK_NEXTCLOUD_CLIENT_SECRET}"
 fi
 

+ 823 - 1634
git_diff.txt

@@ -1,1634 +1,823 @@
-diff --git a/.gitignore b/.gitignore
-index 7e57cb3..8994b5d 100644
---- a/.gitignore
-+++ b/.gitignore
-@@ -1,2 +1,3 @@
- data/
--config/nextcloud/
-\ No newline at end of file
-+config/credentials
-+config/nextcloud/
-diff --git a/ansible/site.yml b/ansible/site.yml
-index 22ea670..ded0d95 100644
---- a/ansible/site.yml
-+++ b/ansible/site.yml
-@@ -1,11 +1,22 @@
--# automated-office/ansible/site.yml
- ---
- - name: Deploy Automated Office
--  hosts: all
-+  hosts: localhost
-   become: true
-   
-+  pre_tasks:
-+    - debug:
-+        msg: 
-+          - "Client secret defined: {{ client_secret is defined }}"
-+          - "Client secret length: {{ client_secret | default('') | length }}"
-+        verbosity: 1
-+
-+    - name: "Verify required variables"
-+      fail:
-+        msg: "Client secret is not set or empty"
-+      when: client_secret is not defined or client_secret | default('') | trim == ''
-+
-   roles:
--    - common
--    - docker
--    - nginx
--    - services
-\ No newline at end of file
-+    - role: common
-+    - role: docker
-+    - role: nginx
-+    - role: services
-\ No newline at end of file
-diff --git a/config/credentials/credentials_2024-12-11_18-28-21.txt.gpg b/config/credentials/credentials_2024-12-11_18-28-21.txt.gpg
-deleted file mode 100644
-index b43471e..0000000
-Binary files a/config/credentials/credentials_2024-12-11_18-28-21.txt.gpg and /dev/null differ
-diff --git a/config/nginx/sites-available/keycloak b/config/nginx/sites-available/keycloak
-index 2c2181a..cf437a2 100644
---- a/config/nginx/sites-available/keycloak
-+++ b/config/nginx/sites-available/keycloak
-@@ -5,7 +5,7 @@ upstream keycloak_upstream {
- server {
-     listen 80;
-     server_name auth.mrx8086.com;
--    
-+
-     # Redirect HTTP to HTTPS
-     return 301 https://$host$request_uri;
- }
-@@ -30,35 +30,49 @@ server {
-     add_header X-XSS-Protection "1; mode=block" always;
-     add_header X-Frame-Options SAMEORIGIN always;
-     add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
--    
-+
-     # Content Security Policy
-     add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; frame-src 'self'; frame-ancestors 'self'; connect-src 'self'" always;
- 
--    # Proxy settings for all locations
-+    # Proxy settings - Added X-Forwarded headers here to apply to all proxied locations
-     proxy_set_header X-Real-IP $remote_addr;
-     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-     proxy_set_header X-Forwarded-Proto $scheme;
-+    proxy_set_header X-Forwarded-Ssl on; # Optional, but explicit
-+    proxy_set_header X-Forwarded-Port $server_port;
-     proxy_set_header X-Forwarded-Host $host;
--    proxy_set_header X-Forwarded-Port 443;
-     proxy_set_header Host $host;
-     proxy_http_version 1.1;
--    
-+
-     # Root location for the main application
-     location / {
-         proxy_pass http://keycloak_upstream;
-     }
- 
-+    # Specific location for the token endpoint
-+    location ~ ^/auth/realms/[^/]+/protocol/openid-connect/token$ {
-+        proxy_pass http://keycloak_upstream;
-+        proxy_buffer_size 128k;
-+        proxy_buffers 4 256k;
-+        proxy_busy_buffers_size 256k;
-+        # WebSocket support (likely not needed for token endpoint, but keeping for consistency)
-+        proxy_set_header Upgrade $http_upgrade;
-+        proxy_set_header Connection "upgrade";
-+        # Timeouts
-+        proxy_connect_timeout 60s;
-+        proxy_send_timeout 60s;
-+        proxy_read_timeout 60s;
-+    }
-+
-     # Keycloak required paths
-     location /realms/ {
-         proxy_pass http://keycloak_upstream;
-         proxy_buffer_size 128k;
-         proxy_buffers 4 256k;
-         proxy_busy_buffers_size 256k;
--        
-         # WebSocket support
-         proxy_set_header Upgrade $http_upgrade;
-         proxy_set_header Connection "upgrade";
--        
-         # Timeouts
-         proxy_connect_timeout 60s;
-         proxy_send_timeout 60s;
-@@ -67,7 +81,7 @@ server {
- 
-     location /resources/ {
-         proxy_pass http://keycloak_upstream;
--        
-+
-         # Cache settings for static resources
-         proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
-         proxy_cache_valid 200 1d;
-diff --git a/config/nginx/sites-available/nextcloud b/config/nginx/sites-available/nextcloud
-index c384e10..8357df3 100644
---- a/config/nginx/sites-available/nextcloud
-+++ b/config/nginx/sites-available/nextcloud
-@@ -1,12 +1,10 @@
- upstream nextcloud_upstream {
--    server 172.19.0.3:80;  # Die IP wird später durch die tatsächliche Container-IP ersetzt
-+    server 172.19.0.3:80;  # SICHERSTELLEN, DASS DIES DIE KORREKTE IP IST
- }
- 
- server {
-     listen 80;
-     server_name cloud.mrx8086.com;
--    
--    # Redirect HTTP to HTTPS
-     return 301 https://$host$request_uri;
- }
- 
-@@ -14,27 +12,19 @@ server {
-     listen 443 ssl;
-     server_name cloud.mrx8086.com;
- 
--    # SSL Configuration
-+    # SSL Configuration (wie zuvor)
-     ssl_certificate /etc/nginx/ssl/mrx8086.com/fullchain.pem;
-     ssl_certificate_key /etc/nginx/ssl/mrx8086.com/privkey.pem;
--    ssl_session_timeout 1d;
--    ssl_session_tickets off;
-+    # ... weitere SSL-Einstellungen ...
- 
--    # Modern SSL configuration
--    ssl_protocols TLSv1.2 TLSv1.3;
--    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
--    ssl_prefer_server_ciphers off;
--
--    # Security headers
-+    # Security headers (wie zuvor)
-     add_header X-Content-Type-Options nosniff always;
-     add_header X-XSS-Protection "1; mode=block" always;
-     add_header X-Frame-Options SAMEORIGIN always;
-     add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
--    
--    # Content Security Policy für Nextcloud
-     add_header Content-Security-Policy "frame-ancestors 'self'; default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; media-src 'self';" always;
- 
--    # Proxy settings
-+    # Proxy settings (wie zuvor)
-     proxy_set_header X-Real-IP $remote_addr;
-     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-     proxy_set_header X-Forwarded-Proto $scheme;
-@@ -43,31 +33,36 @@ server {
-     proxy_set_header Host $host;
-     proxy_http_version 1.1;
- 
--    # Nextcloud specific settings
-+    # Nextcloud specific settings (wie zuvor)
-     client_max_body_size 512M;
-     fastcgi_buffers 64 4K;
--    
-+
-+    # Expliziter location-Block für den OpenID Connect Callback
-+    location /apps/sociallogin/custom_oidc/keycloak {
-+        proxy_pass http://nextcloud_upstream;
-+        proxy_set_header Host $host;
-+        proxy_set_header X-Real-IP $remote_addr;
-+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-+        proxy_set_header X-Forwarded-Proto $scheme;
-+    }
-+
-     # Root location
-     location / {
-         proxy_pass http://nextcloud_upstream;
--        
--        # WebSocket support
-         proxy_set_header Upgrade $http_upgrade;
-         proxy_set_header Connection "upgrade";
--        
--        # Timeouts
-         proxy_connect_timeout 60s;
-         proxy_send_timeout 60s;
-         proxy_read_timeout 60s;
-     }
- 
--    # Block sensitive paths
-+    # Block sensitive paths (wie zuvor)
-     location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/) {
-         deny all;
-         return 404;
-     }
- 
--    # Deny access to hidden files
-+    # Deny access to hidden files (wie zuvor)
-     location ~ /\. {
-         deny all;
-         return 404;
-diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
-index 0163334..ccbcd3b 100644
---- a/docker/docker-compose.yml
-+++ b/docker/docker-compose.yml
-@@ -34,8 +34,10 @@ services:
-       - keycloak-network
-     depends_on:
-       - keycloak-db
-+    extra_hosts:
-+      - "cloud.mrx8086.com:172.23.171.133"         
-     healthcheck:
--      test: ["CMD", "curl", "-f", "http://localhost:8080/health/ready"]
-+      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
-       interval: 30s
-       timeout: 10s
-       retries: 3
-@@ -48,7 +50,7 @@ services:
-       POSTGRES_USER: ${KC_DB_USERNAME}
-       POSTGRES_PASSWORD: ${KC_DB_PASSWORD}
-     volumes:
--      - ../data/keycloak/db:/var/lib/postgresql/data
-+      - ../data/keycloak-db:/var/lib/postgresql/data
-     networks:
-       - keycloak-network
-     restart: unless-stopped
-@@ -63,6 +65,13 @@ services:
-     image: nextcloud:latest
-     container_name: nextcloud
-     restart: unless-stopped
-+    ports:
-+        - "8081:80"
-+    volumes:
-+      - ../data/nextcloud:/var/www/html
-+      - ../config/nextcloud/config:/var/www/html/config
-+      - ../config/nextcloud/custom_apps:/var/www/html/custom_apps
-+      - ../data/nextcloud-db:/var/lib/mysql
-     environment:
-       - MYSQL_HOST=nextcloud-db
-       - MYSQL_DATABASE=nextcloud
-@@ -74,16 +83,24 @@ services:
-       - OVERWRITEPROTOCOL=https
-       - OVERWRITEHOST=cloud.mrx8086.com
-       - OVERWRITEWEBROOT=/
--      - TRUSTED_PROXIES=172.18.0.0/16
--    volumes:
--      - ../data/nextcloud:/var/www/html
--      - ../config/nextcloud/config:/var/www/html/config
--      - ../config/nextcloud/custom_apps:/var/www/html/custom_apps
--      - ../config/nextcloud/data:/var/www/html/data
-+      - TRUSTED_PROXIES=172.19.0.0/16
-+      - NEXTCLOUD_URL=https://cloud.mrx8086.com
-+      - NEXTCLOUD_DEBUG=1
-+      - NEXTCLOUD_CONFIG_CUSTOM_SCOPE="openid profile groups-nextcloud"
-+    healthcheck:
-+      test: ["CMD", "curl", "-f", "http://localhost:80/"]
-+      interval: 30s
-+      timeout: 10s
-+      retries: 3
-     networks:
-       - nextcloud-network
-     depends_on:
-       - nextcloud-db
-+    extra_hosts:
-+      - "auth.mrx8086.com:172.23.171.133"      
-+    dns:
-+      - 8.8.8.8
-+      - 8.8.4.4
- 
-   nextcloud-db:
-     image: mariadb:10.6
-diff --git a/docs/context/scripts/setup_environment.sh b/docs/context/scripts/setup_environment.sh
-index 9a1b2da..1244eb8 100644
---- a/docs/context/scripts/setup_environment.sh
-+++ b/docs/context/scripts/setup_environment.sh
-@@ -1,4 +1,5 @@
- #!/bin/bash
-+set -e
- 
- # Ensure we're in the project root directory
- PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
-@@ -8,7 +9,8 @@ cd "${PROJECT_ROOT}"
- CREDENTIALS_DIR="config/credentials"
- DOCKER_DIR="docker"
- KEYCLOAK_SCRIPTS_DIR="scripts/setup/keycloak"
--
-+ANSIBLE_PLAYBOOK="ansible/site.yml"
-+ANSIBLE_INVENTORY="ansible/inventory/staging/hosts"
- 
- # Create necessary directories
- mkdir -p "${CREDENTIALS_DIR}"
-@@ -33,7 +35,7 @@ NEXTCLOUD_DB_USER=$(generate_password)
- NEXTCLOUD_DB_PASSWORD=$(generate_password)
- NEXTCLOUD_ADMIN_USER=$(generate_password)
- NEXTCLOUD_ADMIN_PASSWORD=$(generate_password)
--
-+KEYCLOAK_NEXTCLOUD_CLIENT_SECRET=$(generate_password)
- 
- # Create .env file in docker directory
- cat > "${DOCKER_DIR}/.env" << EOL
-@@ -64,6 +66,7 @@ PAPERLESS_CLIENT_ID=paperless
- NODERED_CLIENT_ID=nodered
- TESTADMIN_PASSWORD=${TESTADMIN_PASSWORD}
- TESTUSER_PASSWORD=${TESTUSER_PASSWORD}
-+KEYCLOAK_NEXTCLOUD_CLIENT_SECRET=${KEYCLOAK_NEXTCLOUD_CLIENT_SECRET}
- EOL
- 
- # Create encrypted credentials documentation
-@@ -77,8 +80,10 @@ Password: ${KEYCLOAK_ADMIN_PASSWORD}
- Keycloak Database Credentials:
- Username: keycloak
- Password: ${KC_DB_PASSWORD}
-+
- Test Admin Credentials:
- Password: ${TESTADMIN_PASSWORD}
-+
- Test User Credentials:
- Password: ${TESTUSER_PASSWORD}
- 
-@@ -86,6 +91,7 @@ Nextcloud Database Credentials:
- Root Password: ${NEXTCLOUD_DB_ROOT_PASSWORD}
- User: ${NEXTCLOUD_DB_USER}
- Password: ${NEXTCLOUD_DB_PASSWORD}
-+
- Nextcloud Admin Credentials:
- Username: ${NEXTCLOUD_ADMIN_USER}
- Password: ${NEXTCLOUD_ADMIN_PASSWORD}
-@@ -101,4 +107,40 @@ echo ".env file for docker-compose has been created in: ${DOCKER_DIR}/.env"
- echo ".env file for setup_realm.js has been created in: ${KEYCLOAK_SCRIPTS_DIR}/.env"
- echo ""
- echo "To view credentials, use:"
--echo "gpg -d ${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt.gpg"
-\ No newline at end of file
-+echo "gpg -d ${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt.gpg"
-+
-+echo ">>> Nextcloud Konfiguration..."
-+
-+# Verify if variable is set from earlier in the script
-+echo ">>> Debug: Checking original variable..."
-+echo ">>> Debug: KEYCLOAK_NEXTCLOUD_CLIENT_SECRET = ${KEYCLOAK_NEXTCLOUD_CLIENT_SECRET}"
-+
-+# Try reading from .env file if variable is empty
-+if [ -z "${KEYCLOAK_NEXTCLOUD_CLIENT_SECRET}" ]; then
-+    echo ">>> Debug: Variable is empty, trying to read from .env file..."
-+    KEYCLOAK_NEXTCLOUD_CLIENT_SECRET=$(grep KEYCLOAK_NEXTCLOUD_CLIENT_SECRET "${KEYCLOAK_SCRIPTS_DIR}/.env" | cut -d '=' -f2)
-+    echo ">>> Debug: Value from .env file = ${KEYCLOAK_NEXTCLOUD_CLIENT_SECRET}"
-+fi
-+
-+# Ensure we have a value
-+if [ -z "${KEYCLOAK_NEXTCLOUD_CLIENT_SECRET}" ]; then
-+    echo ">>> Error: Could not get client secret value"
-+    exit 1
-+fi
-+
-+# Escape special characters in the secret for JSON
-+ESCAPED_SECRET=$(echo "$KEYCLOAK_NEXTCLOUD_CLIENT_SECRET" | sed 's/["\]/\\&/g')
-+echo ">>> Debug: Escaped secret = $ESCAPED_SECRET"
-+
-+# Create the extra vars
-+EXTRA_VARS="{\"client_secret\": \"$ESCAPED_SECRET\"}"
-+echo ">>> Debug: Extra vars = $EXTRA_VARS"
-+
-+# Run Ansible with the extra vars
-+sudo ansible-playbook \
-+    -i "$ANSIBLE_INVENTORY" \
-+    "$ANSIBLE_PLAYBOOK" \
-+    --extra-vars "$EXTRA_VARS" \
-+    -v
-+
-+echo ">>> Fertig"
-\ No newline at end of file
-diff --git a/scripts/install/setup_environment.sh b/scripts/install/setup_environment.sh
-old mode 100644
-new mode 100755
-index aba63dc..5b7bc0e
---- a/scripts/install/setup_environment.sh
-+++ b/scripts/install/setup_environment.sh
-@@ -1,31 +1,163 @@
- #!/bin/bash
-+set -e
- 
- # Ensure we're in the project root directory
- PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
--cd "${PROJECT_ROOT}"
- 
- # Define directories relative to project root
--CREDENTIALS_DIR="config/credentials"
--DOCKER_DIR="docker"
-+CREDENTIALS_DIR="${PROJECT_ROOT}/config/credentials"
-+DOCKER_DIR="${PROJECT_ROOT}/docker"
-+KEYCLOAK_SETUP_DIR="${PROJECT_ROOT}/scripts/setup/keycloak"
-+ANSIBLE_PLAYBOOK="${PROJECT_ROOT}/ansible/site.yml"
-+ANSIBLE_INVENTORY="${PROJECT_ROOT}/ansible/inventory/staging/hosts"
-+NEXTCLOUD_DATA_DIR="${PROJECT_ROOT}/data/nextcloud/data"
-+TEMP_FILE=$(mktemp)
-+KEYCLOAK_DB_DIR="${PROJECT_ROOT}/data/keycloak-db"
- 
- # Create necessary directories
--mkdir -p "${CREDENTIALS_DIR}"
--mkdir -p "${DOCKER_DIR}"
-+sudo mkdir -p "${CREDENTIALS_DIR}"
-+sudo mkdir -p "${DOCKER_DIR}"
-+sudo mkdir -p "${KEYCLOAK_SETUP_DIR}"
-+
-+# Initialize password variables
-+KEYCLOAK_ADMIN_PASSWORD=""
-+KC_DB_PASSWORD=""
-+TESTADMIN_PASSWORD=""
-+TESTUSER_PASSWORD=""
-+TESTSERVICEUSER_PASSWORD=""
-+KEYCLOAK_NEXTCLOUD_CLIENT_SECRET=""
-+
-+# Function to read a password from a .env file
-+read_password_from_env() {
-+    local env_file="$1"
-+    local variable_name="$2"
-+    if [ -f "$env_file" ]; then
-+        grep "^${variable_name}=" "$env_file" | cut -d '=' -f2
-+    fi
-+}
- 
- # Function to generate secure passwords
- generate_password() {
--    openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 24
-+    openssl rand -base64 32
-+}
-+
-+# Function to generate password if empty
-+generate_password_if_empty() {
-+    local variable_name="$1"
-+    eval "local value=\$$variable_name"
-+    if [ -z "$value" ]; then
-+        eval "$variable_name=\"$(generate_password)\""
-+        echo ">>> Generiertes Passwort für: $variable_name"
-+    fi
-+}
-+
-+# Function to create .env file
-+create_env_file() {
-+    local env_file="$1"
-+    local content="$2"
-+    if [ ! -f "$env_file" ]; then
-+        echo "$content" > "$env_file"
-+        echo ">>> .env file created: $env_file"
-+    else
-+        echo ">>> .env file already exists: $env_file"
-+    fi
- }
- 
-+echo ">>> Überprüfe bestehende .env Dateien und lese Passwörter..."
-+
-+# Try reading passwords from existing .env files
-+if [ -f "$DOCKER_DIR/.env" ]; then
-+    KC_DB_PASSWORD=$(read_password_from_env "$DOCKER_DIR/.env" "KC_DB_PASSWORD")
-+    KEYCLOAK_ADMIN_PASSWORD=$(read_password_from_env "$DOCKER_DIR/.env" "KEYCLOAK_ADMIN_PASSWORD")
-+fi
-+
-+if [ -f "$KEYCLOAK_SETUP_DIR/.env" ]; then
-+    KEYCLOAK_ADMIN_PASSWORD=$(read_password_from_env "$KEYCLOAK_SETUP_DIR/.env" "KEYCLOAK_ADMIN_PASSWORD") # Überschreibt ggf. den Wert aus docker/.env
-+    TESTADMIN_PASSWORD=$(read_password_from_env "$KEYCLOAK_SETUP_DIR/.env" "TESTADMIN_PASSWORD")
-+    TESTUSER_PASSWORD=$(read_password_from_env "$KEYCLOAK_SETUP_DIR/.env" "TESTUSER_PASSWORD")
-+    TESTSERVICEUSER_PASSWORD=$(read_password_from_env "$KEYCLOAK_SETUP_DIR/.env" "TESTSERVICEUSER_PASSWORD")
-+    KEYCLOAK_NEXTCLOUD_CLIENT_SECRET=$(read_password_from_env "$KEYCLOAK_SETUP_DIR/.env" "KEYCLOAK_NEXTCLOUD_CLIENT_SECRET")
-+fi
-+
-+echo ">>> Generiere neue Passwörter für fehlende Werte..."
-+
-+# Generate passwords if they are still empty
-+generate_password_if_empty KEYCLOAK_ADMIN_PASSWORD
-+generate_password_if_empty KC_DB_PASSWORD
-+generate_password_if_empty TESTADMIN_PASSWORD
-+generate_password_if_empty TESTUSER_PASSWORD
-+generate_password_if_empty TESTSERVICEUSER_PASSWORD
-+generate_password_if_empty KEYCLOAK_NEXTCLOUD_CLIENT_SECRET
-+
- # Date for documentation
- SETUP_DATE=$(date '+%Y-%m-%d_%H-%M-%S')
- 
--# Generate passwords
--KEYCLOAK_ADMIN_PASSWORD=$(generate_password)
--KC_DB_PASSWORD=$(generate_password)
-+# Create credentials content
-+CREDENTIALS_CONTENT=$(cat <<EOL
-+Setup Date: ${SETUP_DATE}
-+
-+Keycloak Admin Credentials:
-+Username: admin
-+Password: ${KEYCLOAK_ADMIN_PASSWORD}
-+
-+Keycloak Database Credentials:
-+Username: keycloak
-+Password: ${KC_DB_PASSWORD}
-+
-+Test User Credentials:
-+Admin Password: ${TESTADMIN_PASSWORD}
-+User Password: ${TESTUSER_PASSWORD}
-+Service User Password: ${TESTSERVICEUSER_PASSWORD}
-+Nextcloud Client Secret: ${KEYCLOAK_NEXTCLOUD_CLIENT_SECRET}
-+
-+EOL
-+)
-+
-+# Store credentials hash
-+CREDENTIALS_HASH=$(echo "$CREDENTIALS_CONTENT" | sha256sum | awk '{print $1}')
-+echo "$CREDENTIALS_HASH" > "${CREDENTIALS_DIR}/credentials_hash.txt"
-+echo ">>> Credentials hash stored in: ${CREDENTIALS_DIR}/credentials_hash.txt"
-+
-+# Set GPG PASSPHRASE
-+export GPG_PASSPHRASE=$(generate_password)
-+# Set GPG agent environment variable
-+export GPG_TTY=$(tty)
-+
-+echo ">>> Trying openssl encryption first"
-+# Alternative Verschlüsselung mit Openssl
-+echo "$CREDENTIALS_CONTENT" > "$TEMP_FILE"
-+          if openssl enc -aes-256-cbc -pbkdf2 -salt -in "$TEMP_FILE" -out "${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt.enc" -k "$GPG_PASSPHRASE" ; then
-+             echo ">>> Credentials encrypted successfully using openssl"
-+             mv  "${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt.enc" "${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt.gpg"
-+        else
-+           echo ">>> Openssl encryption failed, trying gpg"
-+
-+          # Attempt to kill existing gpg agent
-+          gpgconf --kill gpg-agent 2>/dev/null
-+          echo ">>> Attempting to manually start gpg-agent with pinentry-curses"
-+          gpg-agent --daemon --pinentry-program /usr/bin/pinentry-curses
-+          gpg-connect-agent /bye 2>/dev/null
-+          eval $(gpg-agent --daemon)
-+          gpg-connect-agent updatestartuptty /bye 2>/dev/null
-+
-+          # Attempt to encrypt credentials using GPG with error handling
-+          if echo "$CREDENTIALS_CONTENT" | gpg --symmetric --cipher-algo AES256 -vvv -o "${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt.gpg" ; then
-+                echo ">>> Credentials encrypted successfully using gpg."
-+          else
-+             echo ">>> GPG encryption failed. Attempting GPG encryption with password workaround."
-+                # Attempt encryption with passphrase workaround
-+                if echo "$CREDENTIALS_CONTENT" | gpg --batch --passphrase "$GPG_PASSPHRASE" --symmetric --cipher-algo AES256 -vvv -o "${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt.gpg"; then
-+                      echo ">>> Credentials encrypted successfully using gpg with passphrase workaround."
-+                else
-+                   echo ">>> GPG encryption with passphrase workaround failed"
-+                   exit 1
-+                fi
-+          fi
-+       fi
-+rm "$TEMP_FILE"
- 
- # Create .env file in docker directory
--cat > "${DOCKER_DIR}/.env" << EOL
-+DOCKER_ENV_CONTENT=$(cat <<EOL
- # Generated on ${SETUP_DATE}
- # Keycloak Admin
- KEYCLOAK_ADMIN_PASSWORD=${KEYCLOAK_ADMIN_PASSWORD}
-@@ -34,27 +166,69 @@ KEYCLOAK_ADMIN_PASSWORD=${KEYCLOAK_ADMIN_PASSWORD}
- KC_DB_USERNAME=keycloak
- KC_DB_PASSWORD=${KC_DB_PASSWORD}
- EOL
-+)
-+create_env_file "$DOCKER_DIR/.env" "$DOCKER_ENV_CONTENT"
- 
--# Create encrypted credentials documentation
--cat > "${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt" << EOL
--Setup Date: ${SETUP_DATE}
-+# Create .env file in scripts/setup/keycloak directory
-+KEYCLOAK_ENV_CONTENT=$(cat <<EOL
-+KEYCLOAK_URL=https://auth.mrx8086.com
-+KEYCLOAK_ADMIN_USER=admin
-+KEYCLOAK_ADMIN_PASSWORD=${KEYCLOAK_ADMIN_PASSWORD}
-+NEXTCLOUD_CLIENT_ID=nextcloud
-+PAPERLESS_CLIENT_ID=paperless
-+NODERED_CLIENT_ID=nodered
-+TESTADMIN_PASSWORD=${TESTADMIN_PASSWORD}
-+TESTUSER_PASSWORD=${TESTUSER_PASSWORD}
-+TESTSERVICEUSER_PASSWORD=${TESTSERVICEUSER_PASSWORD}
-+KEYCLOAK_NEXTCLOUD_CLIENT_SECRET=${KEYCLOAK_NEXTCLOUD_CLIENT_SECRET}
-+EOL
-+)
-+create_env_file "$KEYCLOAK_SETUP_DIR/.env" "$KEYCLOAK_ENV_CONTENT"
- 
--Keycloak Admin Credentials:
--Username: admin
--Password: ${KEYCLOAK_ADMIN_PASSWORD}
-+echo ">>> Environment setup completed!"
- 
--Keycloak Database Credentials:
--Username: keycloak
--Password: ${KC_DB_PASSWORD}
--EOL
-+# --------------- KEYCLOAK KONFIGURATION ---------------
-+echo ">>> Keycloak Konfiguration..."
-+cd "$KEYCLOAK_SETUP_DIR"
-+
-+echo ">>> Starte setup_realm.js"
-+node setup_realm.js
-+
-+cd "$PROJECT_ROOT"
-+
-+# --------------- NEXTCLOUD KONFIGURATION ---------------
-+echo ">>> Nextcloud Konfiguration..."
-+
-+# Verify if variable is set from earlier in the script
-+echo ">>> Debug: Checking original variable..."
-+echo ">>> Debug: KEYCLOAK_NEXTCLOUD_CLIENT_SECRET = ${KEYCLOAK_NEXTCLOUD_CLIENT_SECRET}"
-+
-+# Try reading from .env file if variable is empty
-+if [ -z "${KEYCLOAK_NEXTCLOUD_CLIENT_SECRET}" ]; then
-+    echo ">>> Debug: Variable is empty, trying to read from .env file..."
-+    KEYCLOAK_NEXTCLOUD_CLIENT_SECRET=$(grep KEYCLOAK_NEXTCLOUD_CLIENT_SECRET "${KEYCLOAK_SETUP_DIR}/.env" | cut -d '=' -f2)
-+    echo ">>> Debug: Value from .env file = ${KEYCLOAK_NEXTCLOUD_CLIENT_SECRET}"
-+fi
-+
-+# Ensure we have a value
-+if [ -z "${KEYCLOAK_NEXTCLOUD_CLIENT_SECRET}" ]; then
-+    echo ">>> Error: Could not get client secret value"
-+    exit 1
-+fi
-+
-+# Escape special characters in the secret for JSON
-+ESCAPED_SECRET=$(echo "$KEYCLOAK_NEXTCLOUD_CLIENT_SECRET" | sed 's/["\]/\\&/g')
-+echo ">>> Debug: Escaped secret = $ESCAPED_SECRET"
-+
-+# Create the extra vars
-+EXTRA_VARS="{\"client_secret\": \"$ESCAPED_SECRET\"}"
-+echo ">>> Debug: Extra vars = $EXTRA_VARS"
- 
--# Encrypt credentials file
--gpg --symmetric --cipher-algo AES256 "${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt"
--rm "${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt"
-+# Run Ansible with the extra vars
-+sudo ansible-playbook \
-+    -i "$ANSIBLE_INVENTORY" \
-+    "$ANSIBLE_PLAYBOOK" \
-+    --extra-vars "$EXTRA_VARS" \
-+    -v
- 
--echo "Environment setup completed!"
--echo "Credentials have been saved and encrypted in: ${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt.gpg"
--echo ".env file has been created in: ${DOCKER_DIR}/.env"
--echo ""
--echo "To view credentials, use:"
--echo "gpg -d ${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt.gpg"
-\ No newline at end of file
-+echo ">>> Fertig"
-\ No newline at end of file
-diff --git a/scripts/setup/keycloak/.env b/scripts/setup/keycloak/.env
-index 8c6b3c3..0087b01 100644
---- a/scripts/setup/keycloak/.env
-+++ b/scripts/setup/keycloak/.env
-@@ -6,4 +6,6 @@ PAPERLESS_CLIENT_ID=paperless
- NODERED_CLIENT_ID=nodered
- TESTADMIN_PASSWORD=TestAdminPwd
- TESTUSER_PASSWORD=TestUserPwd
--KEYCLOAK_NEXTCLOUD_CLIENT_SECRET=D939xgzoxi58T2XZShdUPZP4gsI0kBOu
-\ No newline at end of file
-+TESTSERVICEUSER_PASSWORD=TestServiceUserPwd
-+KEYCLOAK_NEXTCLOUD_CLIENT_SECRET=OSbJ08zyjBWChwBR7S6c1q4sU0d8zvEK
-+NEXTCLOUD_URL=https://cloud.mrx8086.com
-\ No newline at end of file
-diff --git a/scripts/setup/keycloak/setup_realm.js b/scripts/setup/keycloak/setup_realm.js
-index 9f67b1e..784d543 100644
---- a/scripts/setup/keycloak/setup_realm.js
-+++ b/scripts/setup/keycloak/setup_realm.js
-@@ -1,19 +1,26 @@
- import dotenv from 'dotenv';
- import axios from 'axios';
- 
--// Lade Umgebungsvariablen
-+// Load environment variables
- dotenv.config();
-+console.log('Environment variables loaded.');
- 
--// Konfigurationskonstanten
-+// Configuration constants
- const KEYCLOAK_URL = process.env.KEYCLOAK_URL || 'https://auth.mrx8086.com';
- const ADMIN_USERNAME = process.env.KEYCLOAK_ADMIN_USER;
- const ADMIN_PASSWORD = process.env.KEYCLOAK_ADMIN_PASSWORD;
- const REALM_NAME = 'office-automation';
- 
--// Client IDs und deren Konfiguration
-+console.log('Configuration constants set:', { KEYCLOAK_URL, ADMIN_USERNAME, REALM_NAME });
-+
-+// Client IDs and their configuration
- const CLIENTS = {
-     [process.env.NEXTCLOUD_CLIENT_ID || 'nextcloud']: {
--        redirectUris: ["https://cloud.mrx8086.com/*"]
-+        redirectUris: [
-+            `https://cloud.mrx8086.com/apps/sociallogin/custom_oidc/keycloak`,
-+            `https://cloud.mrx8086.com/apps/user_oidc/code`
-+        ],
-+        postLogoutRedirectUris: ["https://cloud.mrx8086.com/*"]
-     },
-     [process.env.PAPERLESS_CLIENT_ID || 'paperless']: {
-         redirectUris: ["https://docs.mrx8086.com/*"]
-@@ -23,16 +30,18 @@ const CLIENTS = {
-     }
- };
- 
--// Hilfsfunktion für API-Fehlerbehandlung
-+console.log('CLIENTS configuration:', CLIENTS);
-+
-+// Helper function for API error handling
- const handleAxiosError = (error, operation, config, response) => {
-     console.error(`Error during ${operation}:`);
-     if (config) {
--      console.error('Request:', {
--          method: config.method,
--          url: config.url,
--          headers: config.headers,
--          data: config.data,
--      });
-+        console.error('Request:', {
-+            method: config.method,
-+            url: config.url,
-+            headers: config.headers,
-+            data: config.data,
-+        });
-     }
-     if (error.response) {
-         console.error('Response:', {
-@@ -45,8 +54,9 @@ const handleAxiosError = (error, operation, config, response) => {
-     throw error;
- };
- 
--// Admin Token abrufen
-+// Get Admin Token
- async function getAdminToken() {
-+    console.log('Getting admin token...');
-     try {
-         const response = await axios.post(
-             `${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token`,
-@@ -62,165 +72,146 @@ async function getAdminToken() {
-                 }
-             }
-         );
-+        console.log('Admin token received.');
-         return response.data.access_token;
-     } catch (error) {
--         handleAxiosError(error, 'getting admin token');
-+        handleAxiosError(error, 'getting admin token');
-     }
- }
- 
--// Prüfen ob Realm existiert
-+// Check if Realm exists
- async function checkRealmExists(token) {
-+    console.log(`Checking if realm ${REALM_NAME} exists...`);
-     try {
--        await axios.get(
--            `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}`,
--            {
--                headers: {
--                    'Authorization': `Bearer ${token}`
--                }
--            }
--        );
-+        await axios.get(`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}`, {
-+            headers: { 'Authorization': `Bearer ${token}` }
-+        });
-+        console.log(`Realm ${REALM_NAME} exists.`);
-         return true;
-     } catch (error) {
-         if (error.response?.status === 404) {
-+            console.log(`Realm ${REALM_NAME} does not exist.`);
-             return false;
-         }
-         handleAxiosError(error, 'checking realm existence');
-     }
- }
- 
--
--// Funktion um Client Infos abzufragen
--async function getClient(token, clientId) {
-+// Function to get client information by clientId
-+async function getClientByClientId(token, clientId) {
-+    console.log(`Getting client information for ${clientId}...`);
-     try {
--        const response = await axios.get(
--            `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients`,
--            {
--                headers: {
--                    'Authorization': `Bearer ${token}`
--                },
--                params: {
--                    clientId: clientId
--                }
--            }
--        );
-+        const response = await axios.get(`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients`, {
-+            headers: { 'Authorization': `Bearer ${token}` },
-+            params: { clientId }
-+        });
-         if (response.data.length === 0) {
--            console.error(`Client ${clientId} not found`);
-+            console.log(`Client ${clientId} not found`);
-             return null;
-         }
--
-+        console.log(`Client ${clientId} found.`);
-         return response.data[0];
-     } catch (error) {
--       handleAxiosError(error, `getting client ${clientId}`);
-+        handleAxiosError(error, `getting client ${clientId}`);
-+        return null;
-     }
- }
- 
--// Prüfen ob Client existiert
--async function checkClientExists(token, clientId) {
--    const client = await getClient(token, clientId);
--    return !!client;
--}
--
-+// Check if client exists
-+const checkClientExists = async (token, clientId) => !!await getClientByClientId(token, clientId);
- 
-+// Get client mappers by client ID
- async function getClientMappers(token, clientId) {
-+    console.log(`Getting client mappers for ${clientId}...`);
-+    const client = await getClientByClientId(token, clientId);
-+    if (!client) {
-+        console.log(`Client ${clientId} not found, no mappers to get.`);
-+        return [];
-+    }
-     try {
--        const client = await getClient(token, clientId);
--        if (!client) {
--            return [];
--        }
-         const response = await axios.get(
-             `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}/protocol-mappers/models`,
--            {
--                headers: {
--                    'Authorization': `Bearer ${token}`
--                }
--            }
-+            { headers: { 'Authorization': `Bearer ${token}` } }
-         );
-+        console.log(`Client mappers for ${clientId} retrieved.`);
-         return response.data;
-     } catch (error) {
--      handleAxiosError(error, `getting client mappers for ${clientId}`, error.config, error.response);
-+        handleAxiosError(error, `getting client mappers for ${clientId}`, error.config, error.response);
-         return [];
-     }
- }
- 
--async function getClientScopes(token, clientId){
-+// Get client scopes for a client
-+async function getClientScopes(token, clientId) {
-+    console.log(`Getting client scopes for ${clientId}...`);
-+    const client = await getClientByClientId(token, clientId);
-+    if (!client) {
-+        console.log(`Client ${clientId} not found, no client scopes to get.`);
-+        return [];
-+    }
-     try {
--        const client = await getClient(token, clientId);
--        if(!client)
--            return [];
--
-         const response = await axios.get(
-             `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}/client-scopes`,
--            {
--                 headers: {
--                        'Authorization': `Bearer ${token}`
--                    }
--            }
-+            { headers: { 'Authorization': `Bearer ${token}` } }
-         );
--
-+        console.log(`Client scopes for ${clientId} retrieved.`);
-         return response.data;
--
--    } catch(error){
--      handleAxiosError(error, `getting client scopes for ${clientId}`, error.config, error.response);
-+    } catch (error) {
-+        handleAxiosError(error, `getting client scopes for ${clientId}`, error.config, error.response);
-         return [];
-     }
- }
- 
-+// Get a specific client scope by name
- async function getClientScope(token, scopeName) {
-+    console.log(`Getting client scope "${scopeName}"...`);
-     try {
-         const response = await axios.get(
-             `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/client-scopes`,
--             {
--                 headers: {
--                     'Authorization': `Bearer ${token}`
--                 },
--                params: {
--                  name: scopeName
--                }
--              }
-+            { headers: { 'Authorization': `Bearer ${token}` } }
-         );
--
--        if(response.data.length === 0){
--            console.error(`Client Scope ${scopeName} not found`);
-+        const foundScope = response.data.find(scope => scope.name === scopeName);
-+        if (!foundScope) {
-+            console.log(`Client Scope "${scopeName}" not found`);
-             return null;
-         }
--
--        return response.data[0]
--    } catch (error){
-+        console.log(`Client scope "${scopeName}" found:`, foundScope);
-+        return foundScope;
-+    } catch (error) {
-         handleAxiosError(error, `getting client scope ${scopeName}`, error.config, error.response);
-         return null;
-     }
- }
- 
--async function addDefaultClientScope(token, clientId, scopeName){
-+// Add a default client scope to a client
-+async function addDefaultClientScope(token, clientId, scopeName) {
-+    console.log(`Adding client scope "${scopeName}" as default for client "${clientId}"...`);
-+    const client = await getClientByClientId(token, clientId);
-+    const scope = await getClientScope(token, scopeName);
-+    if (!client || !scope) {
-+        console.log(`Client "${clientId}" or scope "${scopeName}" not found, cannot add as default scope.`);
-+        return;
-+    }
-     try {
--        const client = await getClient(token, clientId);
--        const scope = await getClientScope(token, scopeName);
--         if(!client || !scope){
--           return null;
--         }
--
--
-         await axios.put(
--                `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}/default-client-scopes/${scope.id}`,
--                null,
--                {
--                  headers: {
--                        'Authorization': `Bearer ${token}`,
--                        'Content-Type': 'application/json'
--                    }
--               }
-+            `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}/default-client-scopes/${scope.id}`,
-+            null,
-+            {
-+                headers: {
-+                    'Authorization': `Bearer ${token}`,
-+                    'Content-Type': 'application/json'
-+                }
-+            }
-         );
--
--        console.log(`Client scope ${scopeName} added as default scope for client ${clientId}`)
--
--    } catch(error){
--       handleAxiosError(error, `adding client scope ${scopeName} as default for client ${clientId}`);
-+        console.log(`Client scope "${scopeName}" added as default scope for client "${clientId}"`);
-+    } catch (error) {
-+        handleAxiosError(error, `adding client scope "${scopeName}" as default for client "${clientId}"`);
-     }
- }
- 
--
--// Realm erstellen
-+// Create Realm
- async function createRealm(token) {
-+    console.log(`Creating realm ${REALM_NAME}...`);
-     const realmConfig = {
-         realm: REALM_NAME,
-         enabled: true,
-@@ -250,67 +241,47 @@ async function createRealm(token) {
-         webAuthnPolicyUserVerificationRequirement: "preferred",
-         webAuthnPolicyCreateTimeout: 0,
-         webAuthnPolicyAvoidSameAuthenticatorRegister: false,
--        defaultDefaultClientScopes: [
--            "email",
--            "profile",
--            "roles",
--            "web-origins"
--        ],
--        defaultOptionalClientScopes: [
--            "address",
--            "phone",
--            "offline_access",
--            "microprofile-jwt"
--        ]
-+        defaultDefaultClientScopes: ["email", "profile", "roles", "web-origins"],
-+        defaultOptionalClientScopes: ["address", "phone", "offline_access", "microprofile-jwt"]
-     };
--
-     try {
--        await axios.post(
--            `${KEYCLOAK_URL}/admin/realms`,
--            realmConfig,
--            {
--                headers: {
--                    'Authorization': `Bearer ${token}`,
--                    'Content-Type': 'application/json'
--                }
-+        await axios.post(`${KEYCLOAK_URL}/admin/realms`, realmConfig, {
-+            headers: {
-+                'Authorization': `Bearer ${token}`,
-+                'Content-Type': 'application/json'
-             }
--        );
-+        });
-         console.log('Realm created successfully');
-     } catch (error) {
-         handleAxiosError(error, 'creating realm');
-     }
- }
- 
--// Client erstellen
-+// Create client and manage mappers
- async function createClient(token, clientId, clientName, redirectUris) {
--    let client;
--    const clientExists = await checkClientExists(token, clientId);
-+    console.log(`Creating client "${clientId}"...`);
-+    let client = await getClientByClientId(token, clientId);
- 
--    if (!clientExists) {
-+    if (!client) {
-         const clientConfig = {
-             clientId: clientId,
-             name: clientName,
-             enabled: true,
-             protocol: "openid-connect",
-             publicClient: false,
--            authorizationServicesEnabled: true,
--            serviceAccountsEnabled: true,
-+            authorizationServicesEnabled: false,
-+            serviceAccountsEnabled: false,
-             standardFlowEnabled: true,
-             implicitFlowEnabled: false,
-             directAccessGrantsEnabled: true,
-             redirectUris: redirectUris,
--            webOrigins: ["+"],
--            defaultClientScopes: [
--                "roles"
--            ],
--            optionalClientScopes: [
--                "address",
--                "phone",
--                "offline_access",
--                "microprofile-jwt"
--            ]
-+             webOrigins: ["+"],
-+            defaultClientScopes: ["roles"],
-+            optionalClientScopes: ["address", "phone", "offline_access", "microprofile-jwt"],
-+            rootUrl: process.env.NEXTCLOUD_URL,
-+            baseUrl: process.env.NEXTCLOUD_URL,
-+            adminUrl: process.env.NEXTCLOUD_URL,
-         };
--
-         try {
-             const response = await axios.post(
-                 `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients`,
-@@ -322,21 +293,34 @@ async function createClient(token, clientId, clientName, redirectUris) {
-                     }
-                 }
-             );
--            console.log(`Client ${clientId} created successfully`);
-+            console.log(`Client "${clientId}" created successfully`);
-             client = response.data;
-         } catch (error) {
-             handleAxiosError(error, `creating client: ${clientId}`);
-             return;
-         }
-     } else {
--        client = await getClient(token, clientId);
--        console.log(`Client ${clientId} already exists, checking mappers`);
-+        console.log(`Client "${clientId}" already exists, checking mappers`);
-     }
- 
--     if (client) {
--        
--        const existingMappers = await getClientMappers(token, clientId)
-+    if (client) {
-+        try {
-+            await axios.put(
-+                `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}`,
-+                { ...client, secret: process.env[`KEYCLOAK_${clientId.replace(/[^a-zA-Z0-9]/g, '').toUpperCase()}_CLIENT_SECRET`] },
-+                {
-+                    headers: {
-+                        'Authorization': `Bearer ${token}`,
-+                        'Content-Type': 'application/json'
-+                    }
-+                }
-+            );
-+            console.log(`Set client secret for client: ${clientId}`);
-+        } catch (error) {
-+            handleAxiosError(error, `setting client secret for client: ${clientId}`, error.config, error.response);
-+        }
- 
-+        const existingMappers = await getClientMappers(token, clientId);
-         const requiredMappers = [
-             {
-                 name: "groups",
-@@ -367,10 +351,8 @@ async function createClient(token, clientId, clientName, redirectUris) {
- 
-         for (const mapper of requiredMappers) {
-             const existingMapper = existingMappers.find(m => m.name === mapper.name);
--            
-             try {
-                 if (existingMapper) {
--                    // Update existierenden Mapper
-                     await axios.put(
-                         `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}/protocol-mappers/models/${existingMapper.id}`,
-                         { ...existingMapper, ...mapper },
-@@ -381,9 +363,8 @@ async function createClient(token, clientId, clientName, redirectUris) {
-                             }
-                         }
-                     );
--                    console.log(`Mapper ${mapper.name} updated for client ${clientId}`);
-+                    console.log(`Mapper "${mapper.name}" updated for client "${clientId}"`);
-                 } else {
--                    // Erstelle neuen Mapper
-                     await axios.post(
-                         `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}/protocol-mappers/models`,
-                         mapper,
-@@ -394,70 +375,69 @@ async function createClient(token, clientId, clientName, redirectUris) {
-                             }
-                         }
-                     );
--                    console.log(`Mapper ${mapper.name} created for client ${clientId}`);
-+                    console.log(`Mapper "${mapper.name}" created for client "${clientId}"`);
-                 }
-             } catch (error) {
--                handleAxiosError(error, `managing mapper ${mapper.name} for client ${clientId}`, error.config, error.response);
--                // Wir werfen den Fehler nicht weiter, damit andere Mapper noch verarbeitet werden können
-+                handleAxiosError(error, `managing mapper "${mapper.name}" for client "${clientId}"`, error.config, error.response);
-+            }
-+        }
-+
-+        if (clientId.includes("nextcloud")) {
-+            await addDefaultClientScope(token, clientId, "openid");
-+            await addDefaultClientScope(token, clientId, "profile");
-+            await addDefaultClientScope(token, clientId, "groups-nextcloud");
-+
-+            try {
-+                await axios.put(
-+                    `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}`,
-+                    { ...client, defaultClientScopes: client.defaultClientScopes.filter(c => c !== "nextcloud-dedicated") },
-+                    {
-+                        headers: {
-+                            'Authorization': `Bearer ${token}`,
-+                            'Content-Type': 'application/json'
-+                        }
-+                    }
-+                );
-+                console.log(`Removed client scope nextcloud-dedicated from client: ${clientId}`);
-+            } catch (error) {
-+                handleAxiosError(error, `removing client scope nextcloud-dedicated from client: ${clientId}`, error.config, error.response);
-             }
-         }
-     }
- }
- 
--
--// Gruppen erstellen
-+// Create default groups
- async function createDefaultGroups(token) {
-+    console.log('Creating default groups...');
-     const groups = [
--        {
--            name: "Administrators",
--            path: "/Administrators",
--            attributes: {
--                "description": ["Full system access"]
--            }
--        },
--        {
--            name: "Users",
--            path: "/Users",
--            attributes: {
--                "description": ["Regular system users"]
--            }
--        }
-+        { name: "nextcloud-admins", path: "/nextcloud-admins", attributes: { "description": ["Nextcloud administrators"] } },
-+        { name: "nextcloud-users", path: "/nextcloud-users", attributes: { "description": ["Nextcloud regular users"] } },
-+        { name: "nextcloud-youpi", path: "/nextcloud-youpi", attributes: { "description": ["Nextcloud youpi"] } },
-+        { name: "nextcloud-service", path: "/nextcloud-service", attributes: { "description": ["Nextcloud service user"] } }
-     ];
- 
-     for (const group of groups) {
-         try {
--            // Prüfen ob Gruppe existiert
--            const existingGroup = await axios.get(
--                `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/groups`,
--                {
--                    headers: {
--                        'Authorization': `Bearer ${token}`
--                    },
--                    params: {
--                        search: group.name,
--                    }
--                }
--            );
-+            const existingGroups = await axios.get(`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/groups`, {
-+                headers: { 'Authorization': `Bearer ${token}` },
-+                params: { search: group.name, exact: true } // Added exact: true for precise matching
-+            });
- 
--            if (existingGroup.data.length > 0) {
--                console.log(`Group ${group.name} already exists`);
-+            if (existingGroups.data.length > 0) {
-+                console.log(`Group "${group.name}" already exists`);
-                 continue;
-             }
- 
--            await axios.post(
--                `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/groups`,
--                group,
--                {
--                    headers: {
--                        'Authorization': `Bearer ${token}`,
--                        'Content-Type': 'application/json'
--                    }
-+            await axios.post(`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/groups`, group, {
-+                headers: {
-+                    'Authorization': `Bearer ${token}`,
-+                    'Content-Type': 'application/json'
-                 }
--            );
--            console.log(`Group ${group.name} created successfully`);
-+            });
-+            console.log(`Group "${group.name}" created successfully`);
-         } catch (error) {
-             if (error.response?.status === 409) {
--                console.log(`Group ${group.name} already exists`);
-+                console.log(`Group "${group.name}" already exists`);
-             } else {
-                 handleAxiosError(error, `creating group: ${group.name}`);
-             }
-@@ -465,44 +445,44 @@ async function createDefaultGroups(token) {
-     }
- }
- 
--
-+// Create a test token for a user
- async function createTestToken(token, username) {
-+    console.log(`Creating test token for user "${username}"...`);
-     try {
--         const nextcloudClientId = Object.keys(CLIENTS).find(key => key.includes('nextcloud')) || 'nextcloud';
--         const client = await getClient(token, nextcloudClientId);
-+        const nextcloudClientId = Object.keys(CLIENTS).find(key => key.includes('nextcloud')) || 'nextcloud';
-+        const client = await getClientByClientId(token, nextcloudClientId);
- 
--        if (!client)
-+        if (!client) {
-+            console.log(`Client "${nextcloudClientId}" not found, cannot create test token.`);
-             return null;
-+        }
-         const response = await axios.post(
-             `${KEYCLOAK_URL}/realms/${REALM_NAME}/protocol/openid-connect/token`,
-             new URLSearchParams({
-                 'client_id': nextcloudClientId,
--                'client_secret': process.env.KEYCLOAK_NEXTCLOUD_CLIENT_SECRET,
-+                'client_secret': process.env[`KEYCLOAK_${nextcloudClientId.replace(/[^a-zA-Z0-9]/g, '').toUpperCase()}_CLIENT_SECRET`],
-                 'username': username,
-                 'password': process.env.TESTADMIN_PASSWORD || "initial123!",
-                 'grant_type': 'password'
-             }),
--            {
--                headers: {
--                    'Content-Type': 'application/x-www-form-urlencoded'
--                }
--            }
-+            { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
-         );
-+        console.log(`Test token for user "${username}" created.`);
-         return response.data.access_token;
-     } catch (error) {
-         handleAxiosError(error, `getting test token for ${username}`, error.config, error.response);
-+        return null;
-     }
- }
- 
--// Funktion zum Decodieren eines JWT-Tokens
-+// Function to decode a JWT token
- function decodeToken(token) {
-     try {
-         const base64Url = token.split('.')[1];
-         const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
--        const jsonPayload = decodeURIComponent(atob(base64).split('').map(function (c) {
--            return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
--        }).join(''));
--
-+        const jsonPayload = decodeURIComponent(atob(base64).split('').map(c =>
-+            '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
-+        ).join(''));
-         return JSON.parse(jsonPayload);
-     } catch (error) {
-         console.error("Error decoding token:", error.message);
-@@ -510,87 +490,128 @@ function decodeToken(token) {
-     }
- }
- 
--// Test-User erstellen
-+// Create initial users
- async function createInitialUsers(token) {
-+    console.log('Creating initial users...');
-     const users = [
--        {
--            username: "testadmin",
--            enabled: true,
--            emailVerified: true,
--            firstName: "Test",
--            lastName: "Admin",
--            email: "testadmin@mrx8086.com",
--            credentials: [{
--                type: "password",
--                value: process.env.TESTADMIN_PASSWORD || "initial123!",
--                temporary: true
--            }],
--            groups: ["/Administrators"]
--        },
--        {
--            username: "testuser",
--            enabled: true,
--            emailVerified: true,
--            firstName: "Test",
--            lastName: "User",
--            email: "testuser@mrx8086.com",
--            credentials: [{
--                type: "password",
--                value: process.env.TESTUSER_PASSWORD || "initial123!",
--                temporary: true
--            }],
--            groups: ["/Users"]
--        }
-+        { username: "testadmin", enabled: true, emailVerified: true, firstName: "Test", lastName: "Admin", email: "testadmin@mrx8086.com", credentials: [{ type: "password", value: process.env.TESTADMIN_PASSWORD || "initial123!", temporary: false }], groups: ["nextcloud-admins", "nextcloud-users"] },
-+        { username: "testuser", enabled: true, emailVerified: true, firstName: "Test", lastName: "User", email: "testuser@mrx8086.com", credentials: [{ type: "password", value: process.env.TESTUSER_PASSWORD || "initial123!", temporary: false }], groups: ["nextcloud-users", "nextcloud-youpi"] },
-+        { username: "testserviceuser", enabled: true, emailVerified: true, firstName: "Test", lastName: "Service User", email: "testserviceuser@mrx8086.com", credentials: [{ type: "password", value: process.env.TESTSERVICEUSER_PASSWORD || "initial123!", temporary: false }], groups: ["nextcloud-service"] }
-     ];
- 
-     for (const user of users) {
-         try {
--            // Prüfen ob User existiert
--            const existingUsers = await axios.get(
--                `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/users`,
--                {
--                    headers: {
--                        'Authorization': `Bearer ${token}`
--                    },
--                    params: {
--                        username: user.username,
--                        exact: true
--                    }
--                }
--            );
-+            const existingUsers = await axios.get(`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/users`, {
-+                headers: { 'Authorization': `Bearer ${token}` },
-+                params: { username: user.username, exact: true } // Added exact: true for precise matching
-+            });
- 
-             if (existingUsers.data.length > 0) {
--                console.log(`User ${user.username} already exists`);
-+                console.log(`User "${user.username}" already exists`);
-                 continue;
-             }
- 
--            // User erstellen
--            await axios.post(
--                `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/users`,
--                user,
-+            await axios.post(`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/users`, user, {
-+                headers: {
-+                    'Authorization': `Bearer ${token}`,
-+                    'Content-Type': 'application/json'
-+                }
-+            });
-+            console.log(`User "${user.username}" created successfully`);
-+
-+        } catch (error) {
-+            handleAxiosError(error, `creating user: ${user.username}`, error.config, error.response);
-+        }
-+    }
-+}
-+
-+async function createGroupsNextcloudScope(token) {
-+    const scopeName = "groups-nextcloud";
-+    const mapperName = "groups-mapper";
-+    console.log(`Starting createGroupsNextcloudScope`);
-+    let clientScope = await getClientScope(token, scopeName);
-+
-+    if (!clientScope) {
-+        try {
-+            console.log(`Creating client scope "${scopeName}"`);
-+            clientScope = await axios.post(`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/client-scopes`,
-+                {
-+                    "name": scopeName,
-+                    "protocol": "openid-connect",
-+                    "description": "Provides access to user group information for Nextcloud.", // Hinzugefügt: Beschreibung
-+                    "attributes": {},
-+                    "consentScreenText": "Grant access to user groups in Nextcloud",
-+                    "includeInTokenScope": true
-+                },
-                 {
-                     headers: {
-                         'Authorization': `Bearer ${token}`,
-                         'Content-Type': 'application/json'
-                     }
--                }
--            );
--            console.log(`User ${user.username} created successfully`);
-+                });
-+            console.log(`Client scope "${scopeName}" created successfully`);
-+            clientScope = response.data;
-+        } catch (error) {
-+            console.error(`Error creating client scope "${scopeName}":`, error);
-+            handleAxiosError(error, `creating ${scopeName} client scope`, error.config, error.response);
-+            return;
-+        }
-+    } else {
-+        console.log(`Client scope "${scopeName}" exists, skipping creation`);
-+    }
- 
-+    console.log("Client scope creation step finished");
-+    if (clientScope) {
-+        console.log(`Check for mapper "${mapperName}" in scope "${scopeName}"`);
-+        try {
-+            const mappersResponse = await axios.get(`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/client-scopes/${clientScope.id}/protocol-mappers/models`,
-+                { headers: { 'Authorization': `Bearer ${token}` } }
-+            );
-+            if (!mappersResponse.data.find(m => m.name === mapperName)) {
-+                try {
-+                    await axios.post(`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/client-scopes/${clientScope.id}/protocol-mappers/models`,
-+                        {
-+                            "name": mapperName,
-+                            "protocol": "openid-connect",
-+                            "protocolMapper": "oidc-group-membership-mapper",
-+                            "config": {
-+                                "full.path": "false",
-+                                "id.token.claim": "false",
-+                                "access.token.claim": "true",
-+                                "userinfo.token.claim": "true",
-+                                "claim.name": "groups",
-+                                "add.to.introspection": "false"
-+                            }
-+                        },
-+                        {
-+                            headers: {
-+                                'Authorization': `Bearer ${token}`,
-+                                'Content-Type': 'application/json'
-+                            }
-+                        });
-+                    console.log(`Mapper "${mapperName}" created for client scope "${scopeName}"`);
-+                } catch (error) {
-+                    console.error(`Error creating mapper "${mapperName}" for scope "${scopeName}":`, error);
-+                    handleAxiosError(error, `creating mapper for ${scopeName}`, error.config, error.response);
-+                }
-+            } else {
-+                console.log(`Mapper "${mapperName}" exists in client scope "${scopeName}", skipping creation`);
-+            }
-         } catch (error) {
--            handleAxiosError(error, `creating user: ${user.username}`, error.config, error.response);
-+            console.error("Error checking for mappers:", error);
-+            handleAxiosError(error, `checking mappers for ${scopeName}`, error.config, error.response);
-         }
-     }
-+    console.log("Finished createGroupsNextcloudScope");
- }
- 
--
--// Hauptfunktion
-+// Main function
- async function setupRealm() {
-     try {
-         console.log('Starting Keycloak setup...');
-         const token = await getAdminToken();
- 
--        // Prüfe ob Realm existiert
-+        // Check if realm exists
-         const realmExists = await checkRealmExists(token);
- 
-         if (!realmExists) {
-@@ -600,41 +621,37 @@ async function setupRealm() {
-             console.log('Realm already exists, skipping base setup');
-         }
- 
--        // Clients erstellen
-+        // Create client scope groups-nextcloud
-+        await createGroupsNextcloudScope(token);
-+
-+        // Create clients
-         for (const clientId in CLIENTS) {
-             await createClient(token, clientId, clientId, CLIENTS[clientId].redirectUris);
-         }
- 
--       const nextcloudClientId = Object.keys(CLIENTS).find(key => key.includes('nextcloud')) || 'nextcloud';
--        await addDefaultClientScope(token, nextcloudClientId, "openid");
--
--        // Gruppen erstellen
-+        // Create groups
-         await createDefaultGroups(token);
- 
--        // Test User erstellen
-+        // Create test users
-         await createInitialUsers(token);
- 
--
--        // Konfiguration des Office-Automation Realms mit Admin Token auslesen
-+        // Read the configuration of the Office-Automation realm with Admin Token
-         if (token) {
-             console.log("Master Realm Admin Token:", token);
--
-             try {
-                 const realmConfig = await axios.get(`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}`, {
--                    headers: {
--                        'Authorization': `Bearer ${token}`
--                    }
-+                    headers: { 'Authorization': `Bearer ${token}` }
-                 });
--                console.log("Office Automation Realm Configuration:", realmConfig.data)
-+                console.log("Office Automation Realm Configuration:", realmConfig.data);
-             } catch (error) {
-                 handleAxiosError(error, 'getting office realm configuration', error.config, error.response);
-             }
-         } else {
--            console.error("Error getting Master Realm admin token")
-+            console.error("Error getting Master Realm admin token");
-         }
- 
--        // Test Token erstellen
--        const testToken = await createTestToken(token, "testadmin@mrx8086.com");
-+        // Create Test Token
-+        const testToken = await createTestToken(token, "testadmin");
- 
-         if (testToken) {
-             console.log("Test Token generated successfully!");
-@@ -644,7 +661,6 @@ async function setupRealm() {
-         } else {
-             console.error("Error generating Test Token");
-         }
--
-         console.log('Setup completed successfully');
- 
-     } catch (error) {
-@@ -653,5 +669,5 @@ async function setupRealm() {
-     }
- }
- 
--// Script ausführen
-+// Execute the script
- setupRealm();
-\ No newline at end of file
-diff --git a/scripts/setup/keycloak/test_realm.js b/scripts/setup/keycloak/test_realm.js
-index 78c03a4..11e3254 100644
---- a/scripts/setup/keycloak/test_realm.js
-+++ b/scripts/setup/keycloak/test_realm.js
-@@ -6,7 +6,7 @@ dotenv.config();
- const KEYCLOAK_URL = process.env.KEYCLOAK_URL || 'https://auth.mrx8086.com';
- const NEXTCLOUD_CLIENT_ID = process.env.NEXTCLOUD_CLIENT_ID || 'nextcloud';
- const TESTADMIN_USERNAME = "testadmin@mrx8086.com";
--const TESTADMIN_PASSWORD = process.env.TESTADMIN_PASSWORD || "initial123!";
-+const TESTADMIN_PASSWORD = process.env.TESTADMIN_PASSWORD;
- const REALM_NAME = 'office-automation';
- const CLIENT_SECRET = process.env.KEYCLOAK_NEXTCLOUD_CLIENT_SECRET;
- 
-@@ -74,28 +74,33 @@ function decodeToken(token) {
-     }
- }
- 
-+// Prüfe ob ein Admin Token korrekt generiert werden kann
- async function testKeycloakLogin() {
--  try {
--    const accessToken = await getAccessToken(TESTADMIN_USERNAME, TESTADMIN_PASSWORD);
-+    try {
-+        const accessToken = await getAccessToken(TESTADMIN_USERNAME, TESTADMIN_PASSWORD);
- 
--    if (!accessToken) {
--      console.error('Failed to get access token.');
--      return;
--    }
--    console.log('Access Token:', accessToken);
--    const decodedToken = decodeToken(accessToken);
--    if (decodedToken) {
--      console.log('Decoded Access Token:', decodedToken);
--      if (decodedToken.groups.includes('/Administrators')){
--        console.log("Admin Group is set correctly!")
--      } else {
--          console.error("Admin Group is not set correctly!")
--      }
--    }
-+        if (!accessToken) {
-+            console.error('Failed to get access token.');
-+            return;
-+        }
-+        console.log('Access Token:', accessToken);
-+        const decodedToken = decodeToken(accessToken);
-+        if(decodedToken) {
-+             console.log('Decoded Access Token:', decodedToken);
-+            if (Array.isArray(decodedToken.groups) && decodedToken.groups.includes('/nextcloud-admins')){
-+                console.log("Admin Group is set correctly!")
-+            } else if (typeof decodedToken.groups === 'string' && decodedToken.groups.includes('/nextcloud-admins')) {
-+                console.log("Admin Group is set correctly!")
-+            }
-+             else {
-+                 console.error("Admin Group is not set correctly!")
-+            }
-+        }
- 
--  } catch (error) {
--    console.error('An error occurred:', error);
--  }
-+
-+    } catch (error) {
-+        console.error('An error occurred:', error);
-+    }
- }
- 
- testKeycloakLogin();
-\ No newline at end of file
+Vielen Dank für die Bereitstellung der Dateien. Ich habe alle notwendigen Informationen, um die Dokumentation zu erstellen. Hier ist der Inhalt der finalen Dokumentationsdateien:
+
+```markdown
+# Automatisiertes Büro 2.0 - Projektdokumentation
+
+## Inhaltsverzeichnis
+1.  [Projektübersicht](#projektübersicht)
+2.  [Systemarchitektur](#systemarchitektur)
+3.  [Entwicklungsumgebung](#entwicklungsumgebung)
+4.  [Konfigurationen](#konfigurationen)
+5.  [Installationsanleitung](#installationsanleitung)
+6.  [Workflows](#workflows)
+7.  [Sicherheitskonzept](#sicherheitskonzept)
+8.  [Wartung und Monitoring](#wartung-und-monitoring)
+9.  [Troubleshooting](#troubleshooting)
+
+## Projektübersicht
+
+### Projektziele
+- Vollständige Automatisierung administrativer und kaufmännischer Prozesse
+- Kostensenkung durch lokale Open-Source-Lösungen
+- Unabhängigkeit von externen Diensten
+- Zentralisierte Verwaltung aller Geschäftsprozesse
+
+### Projektumfang
+- Integration aller Kommunikationskanäle
+- Automatisierte Dokumentenverarbeitung
+- Prozessautomatisierung
+- Zentrale Authentifizierung
+- KI-gestützte Korrespondenz
+
+#### Keycloak-Rolle
+Keycloak spielt eine zentrale Rolle als Authentifizierungsstelle. Es stellt die Single-Sign-On (SSO) Funktionalität für alle Dienste der zentralen Plattform bereit und sichert somit den Zugriff auf die verschiedenen Anwendungen.
+
+## Systemarchitektur
+
+#### Architekturübersicht
+
+```mermaid
+graph TB
+    subgraph Eingangssysteme
+        Email[E-Mail]
+        WhatsApp[WhatsApp]
+        Post[Physische Post]
+        Teams[Teams/Webex]
+    end
+
+    subgraph Zentrale_Plattform
+        NC[Nextcloud - Dokumentenverwaltung]
+        PL[Paperless - Dokumentenmanagement]
+        NR[Node-RED - Prozessautomatisierung]
+        KC[Keycloak - SSO]
+        OUI[OpenWebUI - KI-Korrespondenz]
+		KI[Kimai - Zeiterfassung]
+    end
+
+    subgraph Monitoring_Analytics
+        ELK[ELK Stack - Logging & Analyse]
+    end
+
+    subgraph Geschäftsprozesse
+        TP[Task-Priorisierung]
+        subgraph Finanzen
+            RE[Rechnungserstellung]
+            ZA[Zahlungsabwicklung]
+            BA[Banken-API]
+        end
+        subgraph Verwaltung
+            KV[Kundenverwaltung]
+            ZE[Zeiterfassung]
+            DO[Dokumentenarchiv]
+        end
+    end
+
+    Email --> NC
+    WhatsApp --> NC
+    Post --> PL
+    Teams --> NC
+
+    NC --> NR
+    PL --> NR
+    NR --> TP
+	KI --> TP
+
+    TP --> RE
+    TP --> ZA
+    TP --> KV
+    TP --> ZE
+    TP --> DO
+
+    ZA <--> BA
+
+    KC -.->|Authentifizierung| NC
+    KC -.->|Authentifizierung| PL
+    KC -.->|Authentifizierung| NR
+    KC -.->|Authentifizierung| OUI
+	KC -.->|Authentifizierung| KI
+
+    NR --> ELK
+    OUI --> NR
+
+    classDef container fill:#e1f5fe,stroke:#01579b
+    classDef process fill:#e8f5e9,stroke:#2e7d32
+    classDef auth fill:#fff3e0,stroke:#ef6c00
+    classDef monitoring fill:#fce4ec,stroke:#c2185b
+
+    class NC,PL,NR,KI container
+    class TP,RE,ZA,KV,ZE,DO process
+    class KC auth
+    class ELK monitoring
+```
+
+#### Beschreibung der Architekturkomponenten
+
+Die Systemarchitektur ist in vier Hauptbereiche gegliedert:
+
+1.  **Eingangssysteme:**
+    - Erfassen verschiedene Kommunikationskanäle zentral.
+    - Sorgen für eine einheitliche Weiterverarbeitung aller Eingänge.
+
+2.  **Zentrale Plattform:**
+    - **Nextcloud (NC):** Dient als zentraler Hub für die Dateiverwaltung und Kollaboration.
+    - **Paperless (PL):** Zuständig für das Dokumentenmanagement und die optische Zeichenerkennung (OCR).
+    - **Node-RED (NR):** Automatisierung von Workflows und Geschäftsprozessen.
+    - **Keycloak (KC):** Bereitstellung von Single-Sign-On (SSO) und Identitätsmanagement.
+        - Keycloak wird als zentrale Authentifizierungsstelle für alle Dienste der zentralen Plattform verwendet, wodurch ein sicherer und zentralisierter Zugriff gewährleistet wird.
+    - **OpenWebUI (OUI):** KI-gestützte Kommunikation und Integration in die Workflow-Automatisierung
+    - **Kimai (KI):** Zeiterfassungslösung zur Verwaltung von Arbeitszeiten und Projekten.
+
+3.  **Geschäftsprozesse:**
+    - Automatisierte Task-Priorisierung (TP) für eine effiziente Aufgabenverteilung.
+    - Integrierte Finanzprozesse mit Bankenanbindung (RE, ZA, BA).
+    - Zentralisierte Verwaltungsprozesse (KV, ZE, DO).
+
+4.  **Monitoring & Analytics:**
+    - **ELK Stack (ELK):** Ermöglicht umfassendes Logging und Analyse in Echtzeit zur Überwachung aller Systeme.
+
+#### Containerstruktur
+- Docker als Containerisierungsplattform
+- Microservices-Architektur
+- Interne Netzwerkkonfiguration
+
+### Komponenten
+#### Nextcloud
+- Funktion: Zentrale Dateiverwaltung und Kollaboration
+- Version: `[VERSION]`
+- Besondere Konfigurationen:
+  - [Wird ergänzt]
+
+#### Paperless
+- Funktion: Dokumentenmanagement und OCR
+- Version: `[VERSION]`
+- Besondere Konfigurationen:
+  - [Wird ergänzt]
+
+#### Keycloak
+- Funktion: Single-Sign-On und Identitätsmanagement
+- Version: `[VERSION]`
+- Besondere Konfigurationen:
+  - [Wird ergänzt]
+
+#### Node-RED
+- Funktion: Workflow-Automatisierung
+- Version: `[VERSION]`
+- Implementierte Flows:
+  - [Wird ergänzt]
+
+#### ELK Stack
+- Funktion: Logging und Monitoring
+- Version: `[VERSION]`
+- Besondere Konfigurationen:
+  - [Wird ergänzt]
+
+#### OpenWebUI
+- Funktion: KI-gestützte Kommunikation
+- Version: `[VERSION]`
+- Integrationen:
+  - [Wird ergänzt]
+
+#### Kimai
+- Funktion: Zeiterfassung
+- Version: `[VERSION]`
+- Besondere Konfigurationen:
+  - [Wird ergänzt]
+
+## Entwicklungsumgebung
+
+### Systemvoraussetzungen
+- Windows mit WSL (Windows Subsystem for Linux)
+- Visual Studio Code
+- Docker Desktop
+- NGINX Proxy Manager (läuft in WSL)
+
+#### Domain-Konfiguration
+Die Entwicklungsumgebung nutzt die Domain `mrx8086.com` mit verschiedenen Subdomains für die einzelnen Services. Diese werden lokal über die Windows-Hosts-Datei (`C:\Windows\System32\drivers\etc\hosts`) aufgelöst.
+
+```plaintext
+# Development Environment Host Entries
+127.0.0.1 mrx8086.com
+127.0.0.1 proxy.mrx8086.com     # NGINX Proxy Manager
+172.23.171.133 auth.mrx8086.com # Keycloak
+127.0.0.1 cloud.mrx8086.com     # Nextcloud
+127.0.0.1 docs.mrx8086.com      # Paperless
+127.0.0.1 time.mrx8086.com      # Kimai
+127.0.0.1 automate.mrx8086.com  # n8n
+```
+
+#### Service-Übersicht
+| Subdomain | Service | Beschreibung |
+|-----------|---------|--------------|
+| proxy.mrx8086.com | NGINX Proxy Manager | Reverse Proxy und SSL-Management (lokal in der Development Umgebung) |
+| auth.mrx8086.com | Keycloak | Zentrale Authentifizierung |
+| cloud.mrx8086.com | Nextcloud | Dokumentenverwaltung |
+| docs.mrx8086.com | Paperless | Dokumentenmanagement |
+| time.mrx8086.com | Kimai | Zeiterfassung |
+| automate.mrx8086.com | n8n | Workflow-Automatisierung |
+
+#### WSL-Konfiguration
+- NGINX Proxy Manager läuft in WSL
+- IP-Adresse des WSL-Systems: 172.23.171.133 (Beispiel, kann sich ändern)
+- Alle Docker-Container werden innerhalb von WSL betrieben
+
+#### `setup_realm.js`
+Dieses Skript wird verwendet, um den Keycloak-Realm, die zugehörigen Clients und Testbenutzer automatisiert zu erstellen. Es verwendet eine `.env`-Datei zur Konfiguration.
+
+#### Verwendung von .env
+Die Konfigurationen für das setup_realm.js Script werden in einer .env Datei gespeichert. Die benötigten Umgebungsvariablen sind unten aufgelistet:
+- `KEYCLOAK_URL`: Die URL zum Keycloak Server (z.B. https://auth.mrx8086.com)
+- `KEYCLOAK_ADMIN_USER`: Der Benutzername des Keycloak Administrators (z.B. admin).
+- `KEYCLOAK_ADMIN_PASSWORD`: Das Passwort des Keycloak Administrators.
+- `NEXTCLOUD_CLIENT_ID`: Die Client ID für Nextcloud. (z.B. nextcloud)
+- `PAPERLESS_CLIENT_ID`: Die Client ID für Paperless (z.B. paperless).
+- `NODERED_CLIENT_ID`: Die Client ID für Node-RED (z.B. nodered).
+- `TESTADMIN_PASSWORD`: Das Passwort für den Testadmin User.
+- `TESTUSER_PASSWORD`: Das Passwort für den Testuser User.
+- `TESTSERVICEUSER_PASSWORD`: Das Passwort für den Testserviceuser User.
+- `KEYCLOAK_NEXTCLOUD_CLIENT_SECRET`: Das Client Secret für Nextcloud.
+- `NEXTCLOUD_URL`: Die URL zur Nextcloud Instanz (z.B. https://cloud.mrx8086.com).
+
+#### NGINX-Konfigurationen
+Für jeden Service existiert eine dedizierte NGINX-Konfiguration. In der Development Umgebung wird der **NGINX Proxy Manager** verwendet. Für **Staging** und **Production** werden die entsprechenden NGINX Konfigurationsdateien in `/config/nginx` abgelegt.
+
+## Konfigurationen
+
+### Netzwerkkonfiguration
+- Interne Netzwerkstruktur (noch zu definieren)
+- Reverse Proxy Konfiguration über den Nginx Proxy Manager in der Development Umgebung, und Nginx Server Config in Staging und Production.
+- SSL/TLS-Setup: Selbsignierte Zertifikate in der Development Umgebung, Letsencrypt in Staging und Production.
+- Konfigurationsdateien für NGINX Server Config sind unter `/config/nginx/sites-available/` zu finden.
+- Für die Entwicklungsumgebung werden die Konfigurationen über den Nginx Proxy Manager konfiguriert.
+- Die SSL Zertifikate für die Development Umgebung werden als selbsignierte Zertifikate generiert und in `/config/nginx/ssl/mrx8086.com/` abgelegt.
+- In der Staging und Production Umgebung werden die Zertifikate über Let's Encrypt oder eine andere Zertifizierungsstelle verwaltet.
+
+#### Umgebungsvariablen
+Es werden zwei `.env`-Dateien verwendet:
+Eine im `docker/` Verzeichnis für die Docker-Konfiguration und eine im `scripts/setup/keycloak/` für das `setup_realm.js`-Skript.
+
+**`docker/.env`:**
+```env
+# Generated on 2024-12-12_18-12-36
+# Keycloak Admin
+KEYCLOAK_ADMIN_PASSWORD=9aD5Fddh457QqmvQqr6Rb8bu
+
+# Keycloak Database
+KC_DB_USERNAME=keycloak
+KC_DB_PASSWORD=p47616y763z101f3
+```
+
+**`scripts/setup/keycloak/.env`:**
+```env
+KEYCLOAK_URL=https://auth.mrx8086.com
+KEYCLOAK_ADMIN_USER=admin
+KEYCLOAK_ADMIN_PASSWORD=9aD5Fddh457QqmvQqr6Rb8bu
+NEXTCLOUD_CLIENT_ID=nextcloud
+PAPERLESS_CLIENT_ID=paperless
+NODERED_CLIENT_ID=nodered
+TESTADMIN_PASSWORD=TestAdminPwd
+TESTUSER_PASSWORD=TestUserPwd
+TESTSERVICEUSER_PASSWORD=TestServiceUserPwd
+KEYCLOAK_NEXTCLOUD_CLIENT_SECRET=OSbJ08zyjBWChwBR7S6c1q4sU0d8zvEK
+NEXTCLOUD_URL=https://cloud.mrx8086.com
+```
+
+Die Passwörter in der `.env` Datei im `scripts/setup/keycloak` werden vom `setup_environment.sh` Skript generiert.
+
+#### Keycloak-Konfiguration
+
+Keycloak ist als zentrale Authentifizierungsstelle konfiguriert. Die Realm-Konfiguration, Clients und Benutzer werden über das `setup_realm.js` Skript definiert, die Konfiguration wird über die `.env` Datei im `/scripts/setup/keycloak` Verzeichnis angepasst.
+
+##### Realm-Konfiguration
+- **Realm-Name:** `office-automation`
+- **Anzeige Name:** `Office Automation`
+- **SSL ist erforderlich**: `external`
+- **Registrierung ist nicht erlaubt**: `false`
+- **Login mit Email ist erlaubt**: `true`
+- **Doppelte Email ist nicht erlaubt**: `false`
+- **Passwort Reset ist erlaubt**: `true`
+- **Username Bearbeitung ist nicht erlaubt**: `false`
+- **Brute Force Schutz ist aktiviert**: `true`
+- **Permanente Sperrung ist nicht aktiviert**: `false`
+- **Standard Signature Algorithm**: `RS256`
+- **WebAuthn Policy Signatur Algorithmen**: `ES256`
+- **WebAuthn Policy Attestation Conveyance Preference**: `none`
+- **WebAuthn Policy Authenticator Attachment**: `cross-platform`
+- **WebAuthn Policy Require Resident Key**: `not specified`
+- **WebAuthn Policy User Verification Requirement**: `preferred`
+- **WebAuthn Policy Create Timeout**: `0`
+- **WebAuthn Policy Avoid Same Authenticator Register**: `false`
+- **Default Default Client Scopes**: `email`, `profile`, `roles`, `web-origins`
+- **Default Optional Client Scopes**: `address`, `phone`, `offline_access`, `microprofile-jwt`
+
+##### Clients
+Die folgenden Clients werden über das Skript konfiguriert:
+- **Nextcloud:**
+  -   **Client ID:** `nextcloud`
+  -   **Name:** `Nextcloud`
+   -   **Redirect URIs:**
+        - `https://cloud.mrx8086.com/apps/sociallogin/custom_oidc/keycloak`
+        - `https://cloud.mrx8086.com/apps/user_oidc/code`
+    -   **Post Logout Redirect URIs:** `https://cloud.mrx8086.com/*`
+- **Paperless:**
+  -   **Client ID:** `paperless`
+  -   **Name:** `Paperless`
+  -   **Redirect URIs:** `https://docs.mrx8086.com/*`
+- **Node-RED:**
+  -   **Client ID:** `nodered`
+  -   **Name:** `Node-RED`
+  -   **Redirect URIs:** `https://automate.mrx8086.com/*`
+
+##### Gruppen
+- **nextcloud-admins:** Gruppe für Nextcloud Benutzer mit Admin Rechten.
+- **nextcloud-users:** Gruppe für reguläre Nextcloud Benutzer.
+- **nextcloud-youpi:** Gruppe für Nextcloud Youpi Benutzer.
+- **nextcloud-service:** Gruppe für Nextcloud Service Benutzer.
+
+##### Benutzer
+- **testadmin:**
+  - Benutzername: `testadmin`
+  - Passwort: wird entweder aus der Umgebungsvariable `TESTADMIN_PASSWORD` gelesen oder standardmäßig `initial123!` gesetzt.
+  - Gruppen: `nextcloud-admins`,`nextcloud-users`
+- **testuser:**
+  - Benutzername: `testuser`
+  - Passwort: wird entweder aus der Umgebungsvariable `TESTUSER_PASSWORD` gelesen oder standardmäßig `initial123!` gesetzt.
+  - Gruppen: `nextcloud-users`,`nextcloud-youpi`
+- **testserviceuser:**
+    - Benutzername: `testserviceuser`
+    - Passwort: wird entweder aus der Umgebungsvariable `TESTSERVICEUSER_PASSWORD` gelesen oder standardmäßig `initial123!` gesetzt.
+    - Gruppen: `nextcloud-service`
+
+#### SSL/TLS-Setup
+-   **Entwicklungsumgebung (`dev`):** Für lokale Entwicklung werden selbsignierte SSL-Zertifikate verwendet.
+-   **Staging/Produktionsumgebung (`staging`, `production`):** Hier werden SSL-Zertifikate über Let's Encrypt oder eine ähnliche Zertifizierungsstelle verwaltet.
+
+## Installationsanleitung
+#### Voraussetzungen
+- Docker Version: `[VERSION]`
+- Minimal Systemanforderungen:
+  - CPU: `[ANFORDERUNG]`
+  - RAM: `[ANFORDERUNG]`
+  - Speicher: `[ANFORDERUNG]`
+- Für die Ausführung des `setup_realm.js` Skriptes wird `Node.js` benötigt
+- Die genaue Version von `Node.js` wird zu einem späteren Zeitpunkt dokumentiert.
+- Vor dem starten müssen die Passwörter generiert und die `.env` Dateien erstellt werden. Hierzu wird das `setup_environment.sh` verwendet. Dieses Skript sollte **nicht in der Produktionsumgebung** verwendet werden.
+- Das Skript erstellt die benötigten Verzeichnisse, generiert zufällige Passwörter, speichert diese verschlüsselt ab, und erstellt die benötigten `.env` Dateien.
+- Für die Ausführung des `setup_realm.js` Skriptes wird `Node.js` und `npm` benötigt
+- Die benötigten Node Module `dotenv` und `axios` können mit `npm install dotenv axios` installiert werden.
+- Die `.env` Dateien werden im `/docker` und im `/scripts/setup/keycloak` Verzeichnis abgelegt.
+
+#### Keycloak-Installation
+1.  Führe das `setup_environment.sh` Skript aus, um die .env Dateien, die Passwörter zu generieren und die Credentials zu verschlüsseln.
+2.  Kopiere die `docker-compose.yml` Datei in das `docker/` Verzeichnis.
+3.  Kopiere die `.env` Datei in das `docker/` Verzeichnis.
+4.  Führe `docker-compose up -d` im `docker/` Verzeichnis aus um Keycloak zu starten.
+
+#### Ausführen von `setup_realm.js`
+1.  Stelle sicher, dass Node.js und npm installiert sind.
+2.  Wechsle in das `/scripts/setup/keycloak` Verzeichnis.
+3.  Installiere die benötigten npm Pakete mit `npm install dotenv axios`.
+4.  Führe das Skript mit `node setup_realm.js` aus. Dies erstellt den Keycloak-Realm, die Clients und die Testbenutzer.
+    * Das Script befindet sich im `/scripts/setup/keycloak` Verzeichnis
+
+## Workflows
+#### Dokumentenverarbeitung
+- Eingangsverarbeitung
+- OCR-Prozess
+- Kategorisierung
+- Archivierung
+
+#### Geschäftsprozesse
+- Rechnungsstellung
+- Zahlungsabwicklung
+- Kundenmanagement
+- Zeiterfassung
+
+#### Authentifizierungsflow
+Die Authentifizierung der Benutzer für alle Dienste wird über Keycloak abgewickelt. Details zu den Authentifizierungsabläufen werden in einem späteren Schritt dokumentiert.
+
+## Sicherheitskonzept
+#### Zugriffsmanagement
+- Rollenkonzept (wird in späteren schritten dokumentiert)
+- Berechtigungsmatrix (wird in späteren schritten dokumentiert)
+- Authentifizierungsflows (siehe Workflows)
+- Keycloak wird als zentrale Authentifizierungsstelle verwendet, Passwortrichtlinien werden in Keycloak definiert.
+
+#### Datensicherheit
+- Verschlüsselung (wird in späteren schritten dokumentiert)
+- Backup-Strategie (wird in späteren schritten dokumentiert)
+- Notfallwiederherstellung (wird in späteren schritten dokumentiert)
+
+## Wartung und Monitoring
+#### Regelmäßige Wartungsaufgaben
+- Backup-Überprüfung
+- Updates
+- Performance-Monitoring
+
+Details zur Wartung und dem Monitoring werden in späteren Schritten dokumentiert.
+
+## Troubleshooting
+#### Bekannte Probleme
+- [Wird ergänzt mit auftretenden Problemen]
+
+#### Debugging
+- Log-Analyse
+- Fehlerbehandlung
+- Support-Prozesse
+Details zu bekannten Problemen und zur Fehlerbehandlung werden in späteren Schritten dokumentiert.
+```
+```markdown
+# Automated Office 2.0 - Current Project State
+
+## Overview
+Project to automate all administrative and commercial processes within the company, using open-source solutions.
+
+## Current Implementation Status
+
+### 1. Project Structure
+- Basic directory structure created
+- Ansible roles established (common, docker, nginx, services)
+- Configuration directories set up for all services
+- Documentation structure established
+
+### 2. Environment Setup
+- Development environment using WSL
+- NGINX running in WSL for development using NGINX Proxy Manager
+- Docker environment being set up
+- SSL certificates in place for development (self-signed)
+- Staging and Production environment will use NGINX Server Configs and letsencrypt SSL certificates
+
+### 3. Service Status
+
+#### Keycloak (auth.mrx8086.com)
+- NGINX configuration complete
+- Docker setup complete
+- Keycloak is running behind a reverse proxy
+- Implemented setup_realm.js script for automated realm, client and user setup
+- SSL certificates configured (self-signed)
+- `setup_realm.js` configures the `office-automation` realm, `nextcloud`, `paperless`, and `nodered` clients.
+- Test users `testadmin`, `testuser` and `testserviceuser` are also created.
+- Client Scopes for `openid`, `profile` and `groups-nextcloud` are added to the nextcloud client.
+- Groups `nextcloud-admins`, `nextcloud-users`, `nextcloud-youpi` and `nextcloud-service` are created.
+
+#### Nextcloud (cloud.mrx8086.com)
+- NGINX configuration complete
+- Docker setup complete
+- SSL certificates configured
+- Nextcloud is configured to use Keycloak for authentication
+
+#### Paperless (docs.mrx8086.com)
+- NGINX configuration complete
+- Docker setup pending
+- SSL certificates configured
+
+#### Node-RED (automate.mrx8086.com)
+- NGINX configuration complete
+- Docker setup pending
+- SSL certificates configured
+- Chosen over n8n for better open-source compatibility
+
+### 4. Security
+- Automated password generation implemented
+- Encrypted credentials storage system in place
+- SSL certificates managed and deployed
+
+### 5. Development Decisions
+- Using WSL for development environment
+- NGINX running directly in WSL for development
+- Docker containers for all services
+- Focusing on completely open-source solutions
+- Development environment uses Nginx Proxy Manager
+- Staging and Production will use Nginx Server Config files
+
+## Next Steps
+1. Complete Paperless and Node-RED docker setup
+2. Test Paperless and Node-RED authentication against Keycloak
+3. Proceed with remaining service deployments
+4. Setup Letsencrypt SSL Certificates in the Staging Environment
+
+## Important Files Location
+- NGINX configs: /config/nginx/sites-available/
+- SSL certificates: /config/nginx/ssl/mrx8086.com/
+- Docker compose: /docker/docker-compose.yml
+- Environment variables: /config/.env
+- Encrypted credentials: /config/credentials/
+- Keycloak setup script: /scripts/install/setup_realm.js
+
+## Development Environment
+- Domain: mrx8086.com
+- SSL certificates in place (self-signed)
+- NGINX running in WSL
+- Docker running in WSL
+```
+```markdown
+# Ansible Setup Documentation
+
+## Overview
+Ansible wird für das automatisierte Deployment des Automated Office Systems verwendet.
+
+## Roles Structure
+
+### Common Role
+- Basis-Systemkonfiguration
+- Sicherheitseinstellungen (fail2ban, UFW)
+- Grundlegende Systempakete
+
+```yaml
+# Standardvariablen
+timezone: "Europe/Berlin"
+fail2ban_bantime: 600
+fail2ban_findtime: 600
+fail2ban_maxretry: 3
+```
+
+### Docker Role
+- Docker Installation und Konfiguration
+- Docker Compose Setup
+- Docker Netzwerk-Konfiguration
+
+```yaml
+# Docker Standardvariablen
+docker_version: "latest"
+docker_compose_version: "2.21.0"
+docker_users: ["{{ ansible_user }}"]
+```
+
+### NGINX Role
+- NGINX Installation
+- SSL/TLS Setup
+- Virtual Host Konfiguration
+
+```yaml
+# NGINX Standardvariablen
+nginx_worker_processes: auto
+nginx_worker_connections: 1024
+nginx_client_max_body_size: "100M"
+```
+
+### Services Role
+- Deployment der Docker-Container
+- Service-spezifische Konfigurationen
+- Datenpersistenz-Setup
+
+## Inventory Structure
+```plaintext
+inventory/
+├── production/
+└── staging/
+    └── hosts
+```
+
+## Variables
+```yaml
+# vars/defaults/main.yml
+base_domain: "example.com"
+ssl_email: "admin@example.com"
+
+services:
+  keycloak: true
+  nextcloud: true
+  paperless: true
+  nodered: true
+```
+```yaml
+# ansible/roles/common/defaults/main.yml
+---
+# System
+timezone: "Europe/Berlin"
+
+# Security
+fail2ban_bantime: 600
+fail2ban_findtime: 600
+fail2ban_maxretry: 3
+
+# Firewall ports to open
+ufw_allowed_ports:
+  - { port: 22, proto: tcp }  # SSH
+  - { port: 80, proto: tcp }  # HTTP
+  - { port: 443, proto: tcp } # HTTPS
+```
+```yaml
+# ansible/roles/docker/defaults/main.yml
+---
+# Docker Standardvariablen
+docker_version: "latest"
+docker_compose_version: "2.21.0"
+docker_users: ["{{ ansible_user }}"]
+```
+```yaml
+# ansible/roles/nginx/defaults/main.yml
+---
+# NGINX Standardvariablen
+nginx_worker_processes: auto
+nginx_worker_connections: 1024
+nginx_client_max_body_size: "100M"
+```
+```yaml
+# ansible/vars/defaults/main.yml
+---
+# Domain-Konfiguration
+base_domain: "example.com"
+ssl_email: "admin@example.com"
+
+# Aktivierte Services
+services:
+  keycloak: true
+  nextcloud: true
+  paperless: true
+  nodered: true
+
+# Ports
+keycloak_port: 8080
+nextcloud_port: 8081
+paperless_port: 8000
+nodered_port: 1880
+
+# Docker-Konfiguration
+docker_compose_version: "2.21.0"
+```
+## Deployment Flow
+1. Common Role: Systemvorbereitung
+2. Docker Role: Container-Runtime
+3. NGINX Role: Reverse Proxy
+4. Services Role: Anwendungen
+
+## Wichtige Befehle
+```bash
+# Staging Deployment
+ansible-playbook -i inventory/staging site.yml
+
+# Production Deployment
+ansible-playbook -i inventory/production site.yml
+```
+
+## Sicherheitsaspekte
+- Automatische Passwortverwaltung
+- SSL/TLS-Konfiguration
+- Firewall-Einstellungen
+- Fail2ban-Integration
+
+## Entwicklungshinweise
+- Lokales Testing über WSL
+- Staging-Umgebung für Tests
+- Produktionsumgebung für finale Deployments
+
+## Updates und Wartung
+- Regelmäßige Updates über Ansible
+- Backup-Integration
+- Monitoring-Setup
+```
+```bash
+#!/bin/bash
+set -e
+
+# Ensure we're in the project root directory
+PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
+
+# Define directories relative to project root
+CREDENTIALS_DIR="${PROJECT_ROOT}/config/credentials"
+DOCKER_DIR="${PROJECT_ROOT}/docker"
+KEYCLOAK_SETUP_DIR="${PROJECT_ROOT}/scripts/setup/keycloak"
+ANSIBLE_PLAYBOOK="${PROJECT_ROOT}/ansible/site.yml"
+ANSIBLE_INVENTORY="${PROJECT_ROOT}/ansible/inventory/staging/hosts"
+NEXTCLOUD_DATA_DIR="${PROJECT_ROOT}/data/nextcloud/data"
+TEMP_FILE=$(mktemp)
+KEYCLOAK_DB_DIR="${PROJECT_ROOT}/data/keycloak-db"
+
+# Create necessary directories
+sudo mkdir -p "${CREDENTIALS_DIR}"
+sudo mkdir -p "${DOCKER_DIR}"
+sudo mkdir -p "${KEYCLOAK_SETUP_DIR}"
+
+# Initialize password variables
+KEYCLOAK_ADMIN_PASSWORD=""
+KC_DB_PASSWORD=""
+TESTADMIN_PASSWORD=""
+TESTUSER_PASSWORD=""
+TESTSERVICEUSER_PASSWORD=""
+KEYCLOAK_NEXTCLOUD_CLIENT_SECRET=""
+
+# Function to read a password from a .env file
+read_password_from_env() {
+    local env_file="$1"
+    local variable_name="$2"
+    if [ -f "$env_file" ]; then
+        grep "^${variable_name}=" "$env_file" | cut -d '=' -f2
+    fi
+}
+
+# Function to generate secure passwords
+generate_password() {
+    openssl rand -base64 32
+}
+
+# Function to generate password if empty
+generate_password_if_empty() {
+    local variable_name="$1"
+    eval "local value=\$$variable_name"
+    if [ -z "$value" ]; then
+        eval "$variable_name=\"$(generate_password)\""
+        echo ">>> Generiertes Passwort für: $variable_name"
+    fi
+}
+
+# Function to create .env file
+create_env_file() {
+    local env_file="$1"
+    local content="$2"
+    if [ ! -f "$env_file" ]; then
+        echo "$content" > "$env_file"
+        echo ">>> .env file created: $env_file"
+    else
+        echo ">>> .env file already exists: $env_file"
+    fi
+}
+
+echo ">>> Überprüfe bestehende .env Dateien und lese Passwörter..."
+
+# Try reading passwords from existing .env files
+if [ -f "$DOCKER_DIR/.env" ]; then
+    KC_DB_PASSWORD=$(read_password_from_env "$DOCKER_DIR/.env" "KC_DB_PASSWORD")
+    KEYCLOAK_ADMIN_PASSWORD=$(read_password_from_env "$DOCKER_DIR/.env" "KEYCLOAK_ADMIN_PASSWORD")
+fi
+
+if [ -f "$KEYCLOAK_SETUP_DIR/.env" ]; then
+    KEYCLOAK_ADMIN_PASSWORD=$(read_password_from_env "$KEYCLOAK_SETUP_DIR/.env" "KEYCLOAK_ADMIN_PASSWORD") # Überschreibt ggf. den Wert aus docker/.env
+    TESTADMIN_PASSWORD=$(read_password_from_env "$KEYCLOAK_SETUP_DIR/.env" "TESTADMIN_PASSWORD")
+    TESTUSER_PASSWORD=$(read_password_from_env "$KEYCLOAK_SETUP_DIR/.env" "TESTUSER_PASSWORD")
+    TESTSERVICEUSER_PASSWORD=$(read_password_from_env "$KEYCLOAK_SETUP_DIR/.env" "TESTSERVICEUSER_PASSWORD")
+    KEYCLOAK_NEXTCLOUD_CLIENT_SECRET=$(read_password_from_env "$KEYCLOAK_SETUP_DIR/.env" "KEYCLOAK_NEXTCLOUD_CLIENT_SECRET")
+fi
+
+echo ">>> Generiere neue Passwörter für fehlende Werte..."
+
+# Generate passwords if they are still empty
+generate_password_if_empty KEYCLOAK_ADMIN_PASSWORD
+generate_password_if_empty KC_DB_PASSWORD
+generate_password_if_empty TESTADMIN_PASSWORD
+generate_password_if_empty TESTUSER_PASSWORD
+generate_password_if_empty TESTSERVICEUSER_PASSWORD
+generate_password_if_empty KEYCLOAK_NEXTCLOUD_CLIENT_SECRET
+
+# Date for documentation
+SETUP_DATE=$(date '+%Y-%m-%d_%H-%M-%S')
+
+# Create credentials content
+CREDENTIALS_CONTENT=$(cat <<EOL
+Setup Date: ${SETUP_DATE}
+
+Keycloak Admin Credentials:
+Username: admin
+Password: ${KEYCLOAK_ADMIN_PASSWORD}
+
+Keycloak Database Credentials:
+Username: keycloak
+Password: ${KC_DB_PASSWORD}
+
+Test User Credentials:
+Admin Password: ${TESTADMIN_PASSWORD}
+User Password: ${TESTUSER_PASSWORD}
+Service User Password: ${TESTSERVICEUSER_PASSWORD}
+Nextcloud Client Secret: ${KEYCLOAK_NEXTCLOUD_CLIENT_SECRET}
+
+EOL
+)
+
+# Store credentials hash
+CREDENTIALS_HASH=$(echo "$CREDENTIALS_CONTENT" | sha256sum | awk '{print $1}')
+echo "$CREDENTIALS_HASH" > "${CREDENTIALS_DIR}/credentials_hash.txt"
+echo ">>> Credentials hash stored in: ${CREDENTIALS_DIR}/credentials_hash.txt"
+
+# Set GPG PASSPHRASE
+export GPG_PASSPHRASE=$(generate_password)
+# Set GPG agent environment variable
+export GPG_TTY=$(tty)
+
+echo ">>> Trying openssl encryption first"
+# Alternative Verschlüsselung mit Openssl
+echo "$CREDENTIALS_CONTENT" > "$TEMP_FILE"
+          if openssl enc -aes-256-cbc -pbkdf2 -salt -in "$TEMP_FILE" -out "${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt.enc" -k "$GPG_PASSPHRASE" ; then
+             echo ">>> Credentials encrypted successfully using openssl"
+             mv  "${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt.enc" "${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt.gpg"
+        else
+           echo ">>> Openssl encryption failed, trying gpg"
+
+          # Attempt to kill existing gpg agent
+          gpgconf --kill gpg-agent 2>/dev/null
+          echo ">>> Attempting to manually start gpg-agent with pinentry-curses"
+          gpg-agent --daemon --pinentry-program /usr/bin/pinentry-curses
+          gpg-connect-agent /bye 2>/dev/null
+          eval $(gpg-agent --daemon)
+          gpg-connect-agent updatestartuptty /bye 2>/dev/null
+
+          # Attempt to encrypt credentials using GPG with error handling
+          if echo "$CREDENTIALS_CONTENT" | gpg --symmetric --cipher-algo AES256 -vvv -o "${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt.gpg" ; then
+                echo ">>> Credentials encrypted successfully using gpg."
+          else
+             echo
+
+
+
+
+
+
+             ```

+ 2 - 2
scripts/setup/keycloak/test_realm.js

@@ -87,9 +87,9 @@ async function testKeycloakLogin() {
         const decodedToken = decodeToken(accessToken);
         if(decodedToken) {
              console.log('Decoded Access Token:', decodedToken);
-            if (Array.isArray(decodedToken.groups) && decodedToken.groups.includes('/nextcloud-admins')){
+            if (Array.isArray(decodedToken.groups) && decodedToken.groups.includes('nextcloud-admins')){
                 console.log("Admin Group is set correctly!")
-            } else if (typeof decodedToken.groups === 'string' && decodedToken.groups.includes('/nextcloud-admins')) {
+            } else if (typeof decodedToken.groups === 'string' && decodedToken.groups.includes('nextcloud-admins')) {
                 console.log("Admin Group is set correctly!")
             }
              else {