|
@@ -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
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ ```
|