Wieder anfassen nach Monaten
This commit is contained in:
813
doc/2026_03_21___q1_Auth.txt
Executable file
813
doc/2026_03_21___q1_Auth.txt
Executable file
@@ -0,0 +1,813 @@
|
||||
== PortalUI mit User-Verwaltung ==
|
||||
|
||||
Mein Server hat u.A. zwei Docker Container: Einmal das Portal, und einmal einen Authentifikations-Dienst.
|
||||
|
||||
|
||||
AppServerPortalUI:
|
||||
image: nginx:alpine
|
||||
container_name: appServer_PortalUI
|
||||
volumes:
|
||||
- /home/chk/Documents/appServerPortalUI/nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
- /home/chk/Documents/appServerPortalUI/public:/usr/share/nginx/html:ro
|
||||
# Let's Encrypt mounts
|
||||
- /home/chk/Documents/appServerPortalUI/letsencrypt/conf:/etc/letsencrypt:ro
|
||||
- /home/chk/Documents/appServerPortalUI/letsencrypt/www:/var/www/certbot:ro
|
||||
# PortForwarding-Script-etc
|
||||
- /home/chk/Documents/appServerPortalUI/forwarding.conf:/etc/nginx/forwarding.conf:ro
|
||||
- /home/chk/Documents/appServerPortalUI/connect-proxies.sh:/docker-entrypoint.d/40-connect-proxies.sh:ro
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
networks:
|
||||
- appRobotNet
|
||||
restart: unless-stopped
|
||||
command: ["nginx", "-g", "daemon off;"]
|
||||
|
||||
AppServerAuth:
|
||||
image: node:24-alpine
|
||||
container_name: appServer_Auth
|
||||
volumes:
|
||||
- /home/chk/Documents/appServerPortalUI/auth:/usr/src/app
|
||||
working_dir: /usr/src/app
|
||||
command: sh -c "npm install && node auth.js"
|
||||
ports:
|
||||
- "10300:3000" # optional, für Tests
|
||||
networks:
|
||||
- default
|
||||
- appRobotNet
|
||||
restart: unless-stopped
|
||||
|
||||
|
||||
== /home/chk/Documents/appServerPortalUI/nginx.conf ==
|
||||
|
||||
# /etc/nginx/conf.d/default.conf
|
||||
# IMMER aktive HTTP-Konfiguration (Port 80)
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
|
||||
# ACME HTTP-01 Challenge (Certbot)
|
||||
location ^~ /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
default_type "text/plain; charset=utf-8";
|
||||
}
|
||||
|
||||
# Deine Default-Page
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location = / { try_files /index.html =404; }
|
||||
location / { try_files $uri $uri/ =404; }
|
||||
}
|
||||
|
||||
== nginxPages/10-server-schooltech.conf ==
|
||||
Soll die "Nutzlast" die Pages, die der User verwenden soll, weiterleiten.
|
||||
|
||||
hier gibt es eine ganze reihe weiterer pages, die bereitstehen.
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name server.schooltech.ch;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/server.schooltech.ch/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/server.schooltech.ch/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_prefer_server_ciphers on;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# UI / SPA
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# API forwarding (auth)
|
||||
location /api/ {
|
||||
proxy_pass http://appserverauth:3000/api/;
|
||||
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;
|
||||
}
|
||||
|
||||
# Internal auth endpoint for auth_request
|
||||
location = /nginxauth {
|
||||
internal;
|
||||
proxy_pass http://appserverauth:3000/internal/auth;
|
||||
proxy_set_header Cookie $http_cookie;
|
||||
proxy_set_header X-Original-URI $request_uri;
|
||||
proxy_set_header X-Original-Host $host;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
}
|
||||
|
||||
# Security headers
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||
}
|
||||
|
||||
== nginxPages/50-subdomains-userA.conf ==
|
||||
Für jeden User gibt es eine reihe von Pages, die eben hier weitergeleitet werden sollen.
|
||||
|
||||
server {
|
||||
listen 443 ssl http2 default_server;
|
||||
server_name _;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/server.schooltech.ch/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/server.schooltech.ch/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
|
||||
return 444;
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# portainer.server.schooltech.ch
|
||||
# ------------------------------------------------------------
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name portainer.server.schooltech.ch;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/server.schooltech.ch/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/server.schooltech.ch/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_prefer_server_ciphers on;
|
||||
|
||||
# Auth nur auf UI
|
||||
location / {
|
||||
auth_request /nginxauth;
|
||||
|
||||
proxy_pass http://portainer:9000;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
# iFrame-freundlich
|
||||
proxy_hide_header X-Frame-Options;
|
||||
add_header X-Frame-Options "ALLOWALL" always;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
add_header Content-Security-Policy "frame-ancestors *" always;
|
||||
}
|
||||
|
||||
location = /nginxauth {
|
||||
internal;
|
||||
proxy_pass http://appserverauth:3000/internal/auth;
|
||||
proxy_set_header Cookie $http_cookie;
|
||||
proxy_set_header X-Original-URI $request_uri;
|
||||
proxy_set_header X-Original-Host $host;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
}
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# abc.server.schooltech.ch
|
||||
# ------------------------------------------------------------
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name abc.server.schooltech.ch;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/server.schooltech.ch/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/server.schooltech.ch/privkey.pem;
|
||||
|
||||
root /usr/share/nginx/abc;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# xyz.server.schooltech.ch
|
||||
# ------------------------------------------------------------
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name xyz.server.schooltech.ch;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/server.schooltech.ch/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/server.schooltech.ch/privkey.pem;
|
||||
|
||||
root /usr/share/nginx/xyz;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
diese sind alle unter xxx.server.schooltech.ch erreichbar.
|
||||
Die ganze nginxPages/50-subdomains-userA.conf werden mit einem Script erstellt:
|
||||
|
||||
== connect-proxies.sh ==========
|
||||
|
||||
#!/bin/sh
|
||||
# /docker-entrypoint.d/40-connect-proxies.sh
|
||||
# Generiert pro Zeile in /etc/nginx/forwarding.conf:
|
||||
# - HTTPS vHost (Proxy oder local static) NUR wenn Zertifikate existieren
|
||||
# - optional 80->443 Redirect-Server (mit ACME-Ausnahme) bei http_behavior=redirect
|
||||
# Läuft automatisch beim Containerstart (nginx:alpine EntryPoint).
|
||||
|
||||
set -eu
|
||||
|
||||
CONF_DIR="/etc/nginx/conf.d"
|
||||
LIVE_DIR="/etc/letsencrypt/live"
|
||||
FWD_FILE="/etc/nginx/forwarding.conf"
|
||||
|
||||
HTTPS_SUFFIX="-https.generated.conf"
|
||||
HTTP_REDIRECT_SUFFIX="-http-redirect.generated.conf"
|
||||
GLOBALS_FILE="$CONF_DIR/_globals.generated.conf"
|
||||
|
||||
echo "[connect-proxies] start …"
|
||||
|
||||
# 0) Forwarding-Datei vorhanden?
|
||||
if [ ! -f "$FWD_FILE" ]; then
|
||||
echo "[connect-proxies] WARN: $FWD_FILE fehlt – keine Proxies zu generieren."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 1) Globale HTTP-Kontext-Map + Resolver (idempotent)
|
||||
# >>> CHANGE: Resolver NICHT hardcoden. Dynamisch aus /etc/resolv.conf ableiten, Fallback 127.0.0.11
|
||||
RESOLVERS="$(awk '/^nameserver/{print $2}' /etc/resolv.conf | xargs || true)"
|
||||
if [ -n "${RESOLVERS:-}" ]; then
|
||||
RESOLVER_LINE="resolver $RESOLVERS ipv6=off valid=30s;"
|
||||
else
|
||||
RESOLVER_LINE="resolver 127.0.0.11 ipv6=off valid=30s;"
|
||||
fi
|
||||
|
||||
cat > "$GLOBALS_FILE" <<NGINX
|
||||
# Automatisch generiert – nicht editieren
|
||||
map \$http_upgrade \$connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
$RESOLVER_LINE
|
||||
NGINX
|
||||
# <<< END CHANGE
|
||||
|
||||
# 2) Alte generierte Confs entfernen
|
||||
rm -f "$CONF_DIR/"*"$HTTPS_SUFFIX" 2>/dev/null || true
|
||||
rm -f "$CONF_DIR/"*"$HTTP_REDIRECT_SUFFIX" 2>/dev/null || true
|
||||
|
||||
# 3) Zeilen verarbeiten
|
||||
LINE_NO=0
|
||||
while IFS= read -r RAW || [ -n "$RAW" ]; do
|
||||
LINE_NO=$((LINE_NO+1))
|
||||
# trim + CR entfernen
|
||||
LINE="$(printf '%s' "$RAW" | tr -d '\r' | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')"
|
||||
[ -z "$LINE" ] && continue
|
||||
case "$LINE" in \#*) continue;; esac
|
||||
|
||||
# Spalten splitten (mindestens 2 erforderlich)
|
||||
set -- $LINE
|
||||
SERVER_NAME="${1:-}"
|
||||
UPSTREAM_URL="${2:-}"
|
||||
HTTP_BEHAVIOR="${3:-redirect}"
|
||||
WEBSOCKETS="${4:-false}"
|
||||
VERIFY_TLS="${5:-false}"
|
||||
CERT_DOMAIN="${6:-$SERVER_NAME}"
|
||||
LISTEN_PORT="${7:-443}"
|
||||
|
||||
if [ -z "$SERVER_NAME" ] || [ -z "$UPSTREAM_URL" ]; then
|
||||
echo "[connect-proxies] WARN(Line $LINE_NO): unvollständig -> $LINE"
|
||||
continue
|
||||
fi
|
||||
|
||||
FULLCHAIN="$LIVE_DIR/$CERT_DOMAIN/fullchain.pem"
|
||||
PRIVKEY="$LIVE_DIR/$CERT_DOMAIN/privkey.pem"
|
||||
|
||||
HTTPS_OUT="$CONF_DIR/${SERVER_NAME}-p${LISTEN_PORT}${HTTPS_SUFFIX}"
|
||||
HTTP_REDIRECT_OUT="$CONF_DIR/${SERVER_NAME}${HTTP_REDIRECT_SUFFIX}"
|
||||
|
||||
# >>> NEW: Upstream normalisieren (Trailing Slash entfernen) + DNS-Check vorbereiten
|
||||
SANITIZED_UPSTREAM="${UPSTREAM_URL%/}"
|
||||
DNS_OK="true"
|
||||
SCHEME=""; HOST=""; PORT=""
|
||||
|
||||
if [ "$SANITIZED_UPSTREAM" != "local" ]; then
|
||||
case "$SANITIZED_UPSTREAM" in
|
||||
http://*)
|
||||
URI="${SANITIZED_UPSTREAM#http://}"; SCHEME="http"; DEFAULT_PORT="80"
|
||||
;;
|
||||
https://*)
|
||||
URI="${SANITIZED_UPSTREAM#https://}"; SCHEME="https"; DEFAULT_PORT="443"
|
||||
;;
|
||||
*)
|
||||
echo "[connect-proxies] WARN(Line $LINE_NO): $SERVER_NAME upstream_url ungültig: '$UPSTREAM_URL' – überspringe."
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
HOSTPORT="${URI%%/*}"
|
||||
HOST="${HOSTPORT%%:*}"
|
||||
PORT="${HOSTPORT#*:}"; [ "$PORT" = "$HOSTPORT" ] && PORT="$DEFAULT_PORT"
|
||||
|
||||
if command -v getent >/dev/null 2>&1; then
|
||||
if ! getent hosts "$HOST" >/dev/null 2>&1; then
|
||||
DNS_OK="false"
|
||||
echo "[connect-proxies] [-] $SERVER_NAME: DNS nicht auflösbar ($HOST) – erzeuge 503-Placeholder statt Proxy."
|
||||
fi
|
||||
else
|
||||
DNS_OK="unknown"
|
||||
fi
|
||||
fi
|
||||
# <<< END NEW
|
||||
|
||||
if [ -f "$FULLCHAIN" ] && [ -f "$PRIVKEY" ]; then
|
||||
echo "[connect-proxies] [+] $SERVER_NAME: Zertifikat OK (cert_domain=$CERT_DOMAIN). Erzeuge 443 …"
|
||||
|
||||
# Fall A: local (statisch, kein proxy_pass)
|
||||
if [ "$SANITIZED_UPSTREAM" = "local" ]; then
|
||||
cat > "$HTTPS_OUT" <<NGINX
|
||||
# Auto-generated - 443 static site
|
||||
server {
|
||||
listen ${LISTEN_PORT} ssl http2;
|
||||
listen [::]:${LISTEN_PORT} ssl http2;
|
||||
server_name $SERVER_NAME;
|
||||
|
||||
ssl_certificate $FULLCHAIN;
|
||||
ssl_certificate_key $PRIVKEY;
|
||||
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files \$uri \$uri/ /index.html;
|
||||
}
|
||||
}
|
||||
NGINX
|
||||
|
||||
else
|
||||
# >>> NEW: Zwei Pfade – DNS_OK=false => Placeholder; sonst Proxy mit Laufzeit-Resolver
|
||||
if [ "$DNS_OK" = "false" ]; then
|
||||
# 443 Placeholder – keine Proxy-Verbindung, saubere 503
|
||||
cat > "$HTTPS_OUT" <<NGINX
|
||||
# Auto-generated - 443 placeholder (DNS failed)
|
||||
server {
|
||||
listen ${LISTEN_PORT} ssl http2;
|
||||
listen [::]:${LISTEN_PORT} ssl http2;
|
||||
server_name $SERVER_NAME;
|
||||
|
||||
ssl_certificate $FULLCHAIN;
|
||||
ssl_certificate_key $PRIVKEY;
|
||||
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
|
||||
location / {
|
||||
default_type text/html;
|
||||
return 503 "<!doctype html><html><head><meta charset='utf-8'><title>Service temporarily unavailable</title></head><body style='font-family:sans-serif;margin:3rem'><h1>\$server_name nicht erreichbar</h1><p>DNS-Auflösung fehlgeschlagen. Bitte später erneut versuchen.</p></body></html>";
|
||||
}
|
||||
}
|
||||
NGINX
|
||||
else
|
||||
# 443 Proxy – DNS ok/unknown: Laufzeit-Auflösung + freundlicher 503 bei Downstreams
|
||||
cat > "$HTTPS_OUT" <<NGINX
|
||||
# Auto-generated - 443 reverse proxy
|
||||
server {
|
||||
listen ${LISTEN_PORT} ssl http2;
|
||||
listen [::]:${LISTEN_PORT} ssl http2;
|
||||
server_name $SERVER_NAME;
|
||||
|
||||
ssl_certificate $FULLCHAIN;
|
||||
ssl_certificate_key $PRIVKEY;
|
||||
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
|
||||
# Fehler sauber abfangen und 503 liefern (statt rohe 502/504)
|
||||
proxy_intercept_errors on;
|
||||
error_page 502 503 504 = @service_down;
|
||||
|
||||
location / {
|
||||
# >>> CHANGE: variable proxy_pass -> DNS zur Laufzeit (verhindert nginx -t Crash)
|
||||
set \$target $SANITIZED_UPSTREAM;
|
||||
proxy_pass \$target;
|
||||
|
||||
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;
|
||||
|
||||
proxy_http_version 1.1;
|
||||
NGINX
|
||||
|
||||
if [ "$WEBSOCKETS" = "true" ]; then
|
||||
cat >> "$HTTPS_OUT" <<'NGINX'
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
NGINX
|
||||
else
|
||||
cat >> "$HTTPS_OUT" <<'NGINX'
|
||||
proxy_set_header Upgrade "";
|
||||
proxy_set_header Connection close;
|
||||
NGINX
|
||||
fi
|
||||
|
||||
case "$SANITIZED_UPSTREAM" in
|
||||
https://*)
|
||||
if [ "$VERIFY_TLS" = "true" ]; then
|
||||
cat >> "$HTTPS_OUT" <<'NGINX'
|
||||
proxy_ssl_verify on;
|
||||
proxy_ssl_server_name on;
|
||||
NGINX
|
||||
else
|
||||
cat >> "$HTTPS_OUT" <<'NGINX'
|
||||
proxy_ssl_verify off;
|
||||
proxy_ssl_server_name on;
|
||||
NGINX
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
cat >> "$HTTPS_OUT" <<'NGINX'
|
||||
client_max_body_size 50m;
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 60s;
|
||||
}
|
||||
|
||||
location @service_down {
|
||||
default_type text/html;
|
||||
return 503 "<!doctype html><html><head><meta charset='utf-8'><title>Service temporarily unavailable</title></head><body style='font-family:sans-serif;margin:3rem'><h1>$server_name nicht erreichbar</h1><p>Der Dienst ist momentan nicht verfügbar. Bitte später erneut versuchen.</p></body></html>";
|
||||
}
|
||||
|
||||
}
|
||||
NGINX
|
||||
fi
|
||||
# <<< END NEW
|
||||
fi
|
||||
|
||||
# 80->443 Redirect-Server nur, wenn gewünscht
|
||||
if [ "$HTTP_BEHAVIOR" = "redirect" ]; then
|
||||
cat > "$HTTP_REDIRECT_OUT" <<NGINX
|
||||
# Auto-generated – 80->443 redirect for $SERVER_NAME
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name $SERVER_NAME;
|
||||
|
||||
# ACME-Ausnahme
|
||||
location ^~ /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
default_type "text/plain; charset=utf-8";
|
||||
}
|
||||
|
||||
location / {
|
||||
return 301 https://\$host\$request_uri;
|
||||
}
|
||||
}
|
||||
NGINX
|
||||
else
|
||||
# Sicherstellen, dass kein alter Redirect liegen bleibt
|
||||
rm -f "$HTTP_REDIRECT_OUT" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
else
|
||||
echo "[connect-proxies] [-] $SERVER_NAME: keine Zertifikate (cert_domain=$CERT_DOMAIN). Entferne evtl. alte Confs."
|
||||
rm -f "$HTTPS_OUT" "$HTTP_REDIRECT_OUT" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
done < "$FWD_FILE"
|
||||
|
||||
echo "[connect-proxies] nginx -t …"
|
||||
nginx -t
|
||||
echo "[connect-proxies] done."
|
||||
|
||||
=====
|
||||
wobei die daten dann im forwarding.conf stehen:
|
||||
|
||||
|
||||
# 444 → 8121 (WS/WSS)
|
||||
# fluidncRedWs.server.schooltech.ch http://appServer_TunnelHead:8121 redirect true false fluidncRedWs.server.schooltech.ch 444
|
||||
server.schooltech.ch local static false false
|
||||
tcPortainer.server.schooltech.ch http://thinkcentre.local:9000 redirect true false
|
||||
tcGuac.server.schooltech.ch http://thinkcentre.local:9000 redirect true false
|
||||
|
||||
#inf InformatiWeb ist per Tunnel angeschlossen. Soll auf 97xx Ports gehen
|
||||
infPortainer.server.schooltech.ch http://appServer_TunnelHead:9903 redirect true false
|
||||
infGuac.server.schooltech.ch http://appServer_TunnelHead:9980 redirect true false
|
||||
|
||||
#RP5 ist "Lokal" der Server
|
||||
rp5Guac.server.schooltech.ch http://appServer_guacamole:8080 redirect true false
|
||||
rp5Portainer.server.schooltech.ch http://portainer:9000 redirect true false
|
||||
|
||||
|
||||
=== Das PortalUI stellt neben den Weiterleitungen auch noch eine Navigations-Page
|
||||
zur Verfügung: index.html mit app.js
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Service Portal</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header id="header">
|
||||
<div class="logo">schooltech</div>
|
||||
<nav id="services"></nav>
|
||||
<div class="user">
|
||||
<button id="login-btn">Login</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<h1>server.schooltech.ch - Service Portal</h1>
|
||||
|
||||
<!-- iFrame für Services -->
|
||||
<iframe id="service-frame" style="width:100%;height:80vh;display:none;"></iframe>
|
||||
|
||||
<!-- Login Modal -->
|
||||
<div id="login-modal" style="display:none;">
|
||||
<label>User: <input type="text" id="username"/></label>
|
||||
<label>Pass: <input type="password" id="password"/></label>
|
||||
<button id="login-submit">Login</button>
|
||||
<div id="login-msg"></div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
// ==== FRONTEND app.js ====
|
||||
|
||||
// Service-Liste
|
||||
const services = [
|
||||
{ id: "abc", name: "Control GamePad", url: "https://abc.server.schooltech.ch/" },
|
||||
{ id: "xyz", name: "Guacamole", url: "https://xyz.server.schooltech.ch/" },
|
||||
{ id: "sim", name: "Simulation", url: "https://simulation.server.schooltech.ch/" },
|
||||
{ id: "portainer", name: "Portainer", url: "https://portainer.server.schooltech.ch/" }
|
||||
];
|
||||
|
||||
// DOM-Elemente
|
||||
const iframe = document.getElementById("service-frame");
|
||||
const loginModal = document.getElementById("login-modal");
|
||||
const loginBtn = document.getElementById("login-btn");
|
||||
const loginSubmit = document.getElementById("login-submit");
|
||||
const loginMsg = document.getElementById("login-msg");
|
||||
const nav = document.getElementById("services");
|
||||
|
||||
const usernameInput = document.getElementById("username");
|
||||
const passwordInput = document.getElementById("password");
|
||||
|
||||
let loggedIn = false;
|
||||
|
||||
// ===========================
|
||||
// Login anzeigen
|
||||
// ===========================
|
||||
function switchToLogin() {
|
||||
loginBtn.textContent = "Login";
|
||||
loginBtn.onclick = () => {
|
||||
loginModal.style.display = "block";
|
||||
};
|
||||
}
|
||||
|
||||
// ===========================
|
||||
// Logout anzeigen
|
||||
// ===========================
|
||||
function switchToLogout() {
|
||||
loginBtn.textContent = "Logout";
|
||||
loginBtn.onclick = async () => {
|
||||
try {
|
||||
await fetch("/api/logout", { method: "POST" });
|
||||
} catch (e) {
|
||||
console.warn("Logout request failed:", e);
|
||||
}
|
||||
performLocalLogout();
|
||||
};
|
||||
}
|
||||
|
||||
// ===========================
|
||||
// Lokales Logout
|
||||
// ===========================
|
||||
function performLocalLogout() {
|
||||
loggedIn = false;
|
||||
iframe.src = "";
|
||||
iframe.style.display = "none";
|
||||
nav.innerHTML = "";
|
||||
loginModal.style.display = "block";
|
||||
switchToLogin();
|
||||
}
|
||||
|
||||
// ===========================
|
||||
// Login-Logik
|
||||
// ===========================
|
||||
async function doLogin() {
|
||||
const user = usernameInput.value;
|
||||
const pass = passwordInput.value;
|
||||
|
||||
try {
|
||||
const res = await fetch("/api/login", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ user, pass })
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
loggedIn = true;
|
||||
loginModal.style.display = "none";
|
||||
loginMsg.textContent = "";
|
||||
setupServiceButtons();
|
||||
switchToLogout();
|
||||
} else {
|
||||
loginMsg.textContent = "Login fehlgeschlagen";
|
||||
}
|
||||
} catch (e) {
|
||||
loginMsg.textContent = "Fehler: " + e.message;
|
||||
}
|
||||
}
|
||||
|
||||
loginSubmit.onclick = doLogin;
|
||||
|
||||
// Enter-Taste Login
|
||||
[usernameInput, passwordInput].forEach(input => {
|
||||
input.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
doLogin();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ===========================
|
||||
// Buttons erzeugen
|
||||
// ===========================
|
||||
function setupServiceButtons() {
|
||||
nav.innerHTML = "";
|
||||
services.forEach(svc => {
|
||||
const btn = document.createElement("button");
|
||||
btn.textContent = svc.name;
|
||||
btn.onclick = async () => {
|
||||
try {
|
||||
await fetch('/api/event', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
event: 'open',
|
||||
service: svc.id,
|
||||
url: svc.url
|
||||
})
|
||||
});
|
||||
} catch(e) {
|
||||
console.error('Event log failed', e);
|
||||
}
|
||||
openService(svc);
|
||||
};
|
||||
nav.appendChild(btn);
|
||||
});
|
||||
}
|
||||
|
||||
// ===========================
|
||||
// Service öffnen
|
||||
// ===========================
|
||||
function openService(svc) {
|
||||
iframe.src = svc.url;
|
||||
iframe.style.display = "block";
|
||||
window.scrollTo(0,0);
|
||||
}
|
||||
|
||||
// ===========================
|
||||
// Session Status beim Laden prüfen
|
||||
// ===========================
|
||||
(async function checkStatus() {
|
||||
try {
|
||||
const r = await fetch('/api/status');
|
||||
if (r.ok) {
|
||||
loggedIn = true;
|
||||
setupServiceButtons();
|
||||
switchToLogout();
|
||||
} else {
|
||||
switchToLogin();
|
||||
}
|
||||
} catch (e) {
|
||||
switchToLogin();
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
app.js muss sicherlich noch ausgebaut werden. Hier sind nur ein Bruchteil der relevanten
|
||||
Pages verlinkt. Aber das kann später erfolgen.
|
||||
|
||||
== auth/auth.js
|
||||
import express from "express";
|
||||
import cookieParser from "cookie-parser";
|
||||
import bcrypt from "bcrypt";
|
||||
import fs from "fs";
|
||||
import crypto from "crypto";
|
||||
|
||||
const USERS = JSON.parse(fs.readFileSync("./users.json"));
|
||||
const SESSIONS = {}; // in-memory session store
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
app.use(cookieParser());
|
||||
|
||||
app.post("/api/login", async (req,res)=>{
|
||||
const { user, pass } = req.body;
|
||||
|
||||
console.log(`Auth-Service login attempt for ${user}`);
|
||||
|
||||
const hash = USERS[user];
|
||||
if(!hash) return res.status(401).send({ ok:false });
|
||||
|
||||
const valid = await bcrypt.compare(pass, hash);
|
||||
if(!valid) return res.status(401).send({ ok:false });
|
||||
|
||||
// create secure random session
|
||||
const sessionID = crypto.randomBytes(32).toString("hex");
|
||||
SESSIONS[sessionID] = {
|
||||
user,
|
||||
created: Date.now()
|
||||
};
|
||||
|
||||
res.cookie("SESSIONID", sessionID, {
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
domain: ".server.schooltech.ch",
|
||||
sameSite: "None",
|
||||
path: "/"
|
||||
});
|
||||
|
||||
res.status(200).send({ ok:true });
|
||||
});
|
||||
|
||||
// Logout endpoint
|
||||
app.post("/api/logout", (req, res) => {
|
||||
const sid = req.cookies.SESSIONID;
|
||||
if (sid && SESSIONS[sid]) {
|
||||
delete SESSIONS[sid];
|
||||
}
|
||||
// Cookie löschen
|
||||
res.clearCookie("SESSIONID", {
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
domain: ".server.schooltech.ch",
|
||||
sameSite: "None",
|
||||
path: "/"
|
||||
});
|
||||
return res.status(200).send({ ok: true });
|
||||
});
|
||||
|
||||
// Event logging endpoint for frontend button presses
|
||||
app.post('/api/event', (req,res)=>{
|
||||
const svc = req.body.service || req.body.action || 'unknown';
|
||||
const user = req.cookies.SESSIONID || 'anonymous';
|
||||
|
||||
console.log(`Event: user=${user} service=${svc} payload=${JSON.stringify(req.body)}`);
|
||||
res.status(200).send({ ok:true });
|
||||
});
|
||||
|
||||
// Optional für Nginx auth_request
|
||||
app.get("/internal/auth", (req,res)=>{
|
||||
const sid = req.cookies.SESSIONID;
|
||||
if (sid && SESSIONS[sid]) {
|
||||
return res.sendStatus(200);
|
||||
}
|
||||
return res.sendStatus(401);
|
||||
});
|
||||
|
||||
// Status endpoint (unter /api so dass Nginx /api/ auf appserverauth proxyt)
|
||||
app.get("/api/status", (req, res) => {
|
||||
const sid = req.cookies.SESSIONID;
|
||||
if (sid && SESSIONS[sid]) {
|
||||
return res.status(200).send({ ok: true, user: SESSIONS[sid].user });
|
||||
}
|
||||
return res.status(401).send({ ok: false });
|
||||
});
|
||||
|
||||
app.listen(3000, ()=>console.log("Auth-Service läuft auf 3000"));
|
||||
|
||||
|
||||
|
||||
==============
|
||||
==============
|
||||
|
||||
Das läuft alles. Das ist leicht erweiterbar. Und soll eigentlich nicht geändert werden.
|
||||
Aber ich brauche Authentifikation. User sollen sich einmal einloggen, wenn sei auf
|
||||
die portalUI Index.html kommen. Das ist ja unter server.schooltech.ch erreichbar.
|
||||
|
||||
Danach soll für alle weiteren xyzABC.server.schooltech.ch Pages die Identifikation
|
||||
akzeptiert werden.
|
||||
|
||||
Soweit ich das sehe, kann ich eine cookie basierte Identifikation auf server.schooltech.ch
|
||||
laufen lassen, und dann später genau diese cookies in den einzelnen Pages überprüfen.
|
||||
|
||||
Fragen:
|
||||
|
||||
1) ist der aufbau für dich einigermassen klar? Stell Fragen falls etwas unklar ist
|
||||
|
||||
2) Wie kann ich die Page server.schooltech.ch Passwort-Schützen? Was muss ich einstellen,
|
||||
dass per default nur die "Login" Option kommt, und sonst keine Links angezeigt werden?
|
||||
|
||||
|
||||
414
doc/2026_03_21___q2_WS.txt
Executable file
414
doc/2026_03_21___q2_WS.txt
Executable file
@@ -0,0 +1,414 @@
|
||||
Ich habe einen weitergeleitete Applikation, bei dem der WSS momentan nicht durch kmmt.
|
||||
|
||||
|
||||
https://tccontrol.server.schooltech.ch/
|
||||
|
||||
WebService.js:124 WebSocket connection to 'wss://tccontrol.server.schooltech.ch/echo' failed:
|
||||
_connect @ WebService.js:124
|
||||
(anonymous) @ WebService.js:179Understand this error
|
||||
WebService.js:184 WebSocket error: Event {isTrusted: true, type: 'error', target: WebSocket, currentTarget: WebSocket, eventPhase: 2, …}
|
||||
|
||||
|
||||
Aufbau des Systems: NGinx in docker container soll auf Tunnel weiter leiten. Service der erreicht werden soll, ist unter "https://thinkcentre.local:10010/" wunderbar erreichbar.
|
||||
Von dort soll es über einen Tunnel gehen:
|
||||
|
||||
|
||||
appRobot_Tunnel:
|
||||
image: alpine:latest
|
||||
container_name: appRobot_Tunnel
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- TZ=Europe/Zurich
|
||||
volumes:
|
||||
- /home/chk/Documents/AppServerPortalUI/.ssh:/root/.ssh:ro
|
||||
command: >
|
||||
/bin/sh -c "
|
||||
apk add --no-cache openssh-client autossh &&
|
||||
autossh -M 0 -N -o StrictHostKeyChecking=no \
|
||||
-i /root/.ssh/id_ed25519 \
|
||||
-o StrictHostKeyChecking=no \
|
||||
-o ServerAliveInterval=60 \
|
||||
-o ServerAliveCountMax=10 \
|
||||
-o ExitOnForwardFailure=yes \
|
||||
-N \
|
||||
-R 0.0.0.0:9780:appRobot_guacamole:8080 \
|
||||
-R 0.0.0.0:9710:appRobot_Control:10010 \
|
||||
-R 0.0.0.0:9703:portainer:9000 \
|
||||
-R 0.0.0.0:9712:appRobot_Simulation:1003\
|
||||
-R 0.0.0.0:9793:appRobot_Homing:2093 \
|
||||
tunnel@server.schooltech.ch -p 2255
|
||||
"
|
||||
|
||||
|
||||
und dann vom nginx verarbeitet werden, denn der nginx ist von aussen per server.schooltech.ch erreichbar.
|
||||
Dazu:
|
||||
|
||||
== nginx.conf ==
|
||||
# /etc/nginx/conf.d/default.conf
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
|
||||
# ACME HTTP-01 Challenge (Certbot)
|
||||
location ^~ /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
default_type "text/plain; charset=utf-8";
|
||||
}
|
||||
|
||||
# PortalUI root
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# === API forwarding for Auth-Service ===
|
||||
location /api/ {
|
||||
proxy_pass http://appserverauth:3000/api/;
|
||||
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;
|
||||
}
|
||||
|
||||
# === SPA routing (Portal UI) ===
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
|
||||
sowie die verschiedenen subdomains, im 50-subdomains-userA.conf, die erzeugt werden über
|
||||
== connect-proxies.sh==
|
||||
#!/bin/sh
|
||||
# /docker-entrypoint.d/40-connect-proxies.sh
|
||||
# Generiert pro Zeile in /etc/nginx/forwarding.conf:
|
||||
# - HTTPS vHost (Proxy oder local static) NUR wenn Zertifikate existieren
|
||||
# - optional 80->443 Redirect-Server (mit ACME-Ausnahme) bei http_behavior=redirect
|
||||
# Läuft automatisch beim Containerstart (nginx:alpine EntryPoint).
|
||||
|
||||
set -eu
|
||||
|
||||
CONF_DIR="/etc/nginx/conf.d"
|
||||
LIVE_DIR="/etc/letsencrypt/live"
|
||||
FWD_FILE="/etc/nginx/forwarding.conf"
|
||||
|
||||
HTTPS_SUFFIX="-https.generated.conf"
|
||||
HTTP_REDIRECT_SUFFIX="-http-redirect.generated.conf"
|
||||
GLOBALS_FILE="$CONF_DIR/_globals.generated.conf"
|
||||
|
||||
echo "[connect-proxies] start …"
|
||||
|
||||
# 0) Forwarding-Datei vorhanden?
|
||||
if [ ! -f "$FWD_FILE" ]; then
|
||||
echo "[connect-proxies] WARN: $FWD_FILE fehlt – keine Proxies zu generieren."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 1) Globale HTTP-Kontext-Map + Resolver (idempotent)
|
||||
# >>> CHANGE: Resolver NICHT hardcoden. Dynamisch aus /etc/resolv.conf ableiten, Fallback 127.0.0.11
|
||||
RESOLVERS="$(awk '/^nameserver/{print $2}' /etc/resolv.conf | xargs || true)"
|
||||
if [ -n "${RESOLVERS:-}" ]; then
|
||||
RESOLVER_LINE="resolver $RESOLVERS ipv6=off valid=30s;"
|
||||
else
|
||||
RESOLVER_LINE="resolver 127.0.0.11 ipv6=off valid=30s;"
|
||||
fi
|
||||
|
||||
cat > "$GLOBALS_FILE" <<NGINX
|
||||
# Automatisch generiert – nicht editieren
|
||||
map \$http_upgrade \$connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
$RESOLVER_LINE
|
||||
NGINX
|
||||
# <<< END CHANGE
|
||||
|
||||
# 2) Alte generierte Confs entfernen
|
||||
rm -f "$CONF_DIR/"*"$HTTPS_SUFFIX" 2>/dev/null || true
|
||||
rm -f "$CONF_DIR/"*"$HTTP_REDIRECT_SUFFIX" 2>/dev/null || true
|
||||
|
||||
# 3) Zeilen verarbeiten
|
||||
LINE_NO=0
|
||||
while IFS= read -r RAW || [ -n "$RAW" ]; do
|
||||
LINE_NO=$((LINE_NO+1))
|
||||
# trim + CR entfernen
|
||||
LINE="$(printf '%s' "$RAW" | tr -d '\r' | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')"
|
||||
[ -z "$LINE" ] && continue
|
||||
case "$LINE" in \#*) continue;; esac
|
||||
|
||||
# Spalten splitten (mindestens 2 erforderlich)
|
||||
set -- $LINE
|
||||
SERVER_NAME="${1:-}"
|
||||
UPSTREAM_URL="${2:-}"
|
||||
HTTP_BEHAVIOR="${3:-redirect}"
|
||||
WEBSOCKETS="${4:-false}"
|
||||
VERIFY_TLS="${5:-false}"
|
||||
CERT_DOMAIN="${6:-$SERVER_NAME}"
|
||||
LISTEN_PORT="${7:-443}"
|
||||
|
||||
if [ -z "$SERVER_NAME" ] || [ -z "$UPSTREAM_URL" ]; then
|
||||
echo "[connect-proxies] WARN(Line $LINE_NO): unvollständig -> $LINE"
|
||||
continue
|
||||
fi
|
||||
|
||||
FULLCHAIN="$LIVE_DIR/$CERT_DOMAIN/fullchain.pem"
|
||||
PRIVKEY="$LIVE_DIR/$CERT_DOMAIN/privkey.pem"
|
||||
|
||||
HTTPS_OUT="$CONF_DIR/${SERVER_NAME}-p${LISTEN_PORT}${HTTPS_SUFFIX}"
|
||||
HTTP_REDIRECT_OUT="$CONF_DIR/${SERVER_NAME}${HTTP_REDIRECT_SUFFIX}"
|
||||
|
||||
# >>> NEW: Upstream normalisieren (Trailing Slash entfernen) + DNS-Check vorbereiten
|
||||
SANITIZED_UPSTREAM="${UPSTREAM_URL%/}"
|
||||
DNS_OK="true"
|
||||
SCHEME=""; HOST=""; PORT=""
|
||||
|
||||
if [ "$SANITIZED_UPSTREAM" != "local" ]; then
|
||||
case "$SANITIZED_UPSTREAM" in
|
||||
http://*)
|
||||
URI="${SANITIZED_UPSTREAM#http://}"; SCHEME="http"; DEFAULT_PORT="80"
|
||||
;;
|
||||
https://*)
|
||||
URI="${SANITIZED_UPSTREAM#https://}"; SCHEME="https"; DEFAULT_PORT="443"
|
||||
;;
|
||||
*)
|
||||
echo "[connect-proxies] WARN(Line $LINE_NO): $SERVER_NAME upstream_url ungültig: '$UPSTREAM_URL' – überspringe."
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
HOSTPORT="${URI%%/*}"
|
||||
HOST="${HOSTPORT%%:*}"
|
||||
PORT="${HOSTPORT#*:}"; [ "$PORT" = "$HOSTPORT" ] && PORT="$DEFAULT_PORT"
|
||||
|
||||
if command -v getent >/dev/null 2>&1; then
|
||||
if ! getent hosts "$HOST" >/dev/null 2>&1; then
|
||||
DNS_OK="false"
|
||||
echo "[connect-proxies] [-] $SERVER_NAME: DNS nicht auflösbar ($HOST) – erzeuge 503-Placeholder statt Proxy."
|
||||
fi
|
||||
else
|
||||
DNS_OK="unknown"
|
||||
fi
|
||||
fi
|
||||
# <<< END NEW
|
||||
|
||||
if [ -f "$FULLCHAIN" ] && [ -f "$PRIVKEY" ]; then
|
||||
echo "[connect-proxies] [+] $SERVER_NAME: Zertifikat OK (cert_domain=$CERT_DOMAIN). Erzeuge 443 …"
|
||||
|
||||
# Fall A: local (statisch, kein proxy_pass)
|
||||
if [ "$SANITIZED_UPSTREAM" = "local" ]; then
|
||||
cat > "$HTTPS_OUT" <<NGINX
|
||||
# Auto-generated - 443 static site
|
||||
server {
|
||||
listen ${LISTEN_PORT} ssl http2;
|
||||
listen [::]:${LISTEN_PORT} ssl http2;
|
||||
server_name $SERVER_NAME;
|
||||
|
||||
ssl_certificate $FULLCHAIN;
|
||||
ssl_certificate_key $PRIVKEY;
|
||||
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://appserverauth:3000/api/;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files \$uri \$uri/ /index.html;
|
||||
}
|
||||
}
|
||||
NGINX
|
||||
|
||||
else
|
||||
# >>> NEW: Zwei Pfade – DNS_OK=false => Placeholder; sonst Proxy mit Laufzeit-Resolver
|
||||
if [ "$DNS_OK" = "false" ]; then
|
||||
# 443 Placeholder – keine Proxy-Verbindung, saubere 503
|
||||
cat > "$HTTPS_OUT" <<NGINX
|
||||
# Auto-generated - 443 placeholder (DNS failed)
|
||||
server {
|
||||
listen ${LISTEN_PORT} ssl http2;
|
||||
listen [::]:${LISTEN_PORT} ssl http2;
|
||||
server_name $SERVER_NAME;
|
||||
|
||||
ssl_certificate $FULLCHAIN;
|
||||
ssl_certificate_key $PRIVKEY;
|
||||
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
|
||||
location / {
|
||||
default_type text/html;
|
||||
return 503 "<!doctype html><html><head><meta charset='utf-8'><title>Service temporarily unavailable</title></head><body style='font-family:sans-serif;margin:3rem'><h1>\$server_name nicht erreichbar</h1><p>DNS-Auflösung fehlgeschlagen. Bitte später erneut versuchen.</p></body></html>";
|
||||
}
|
||||
}
|
||||
NGINX
|
||||
else
|
||||
# 443 Proxy – DNS ok/unknown: Laufzeit-Auflösung + freundlicher 503 bei Downstreams
|
||||
cat > "$HTTPS_OUT" <<NGINX
|
||||
# Auto-generated - 443 reverse proxy
|
||||
server {
|
||||
listen ${LISTEN_PORT} ssl http2;
|
||||
listen [::]:${LISTEN_PORT} ssl http2;
|
||||
server_name $SERVER_NAME;
|
||||
|
||||
ssl_certificate $FULLCHAIN;
|
||||
ssl_certificate_key $PRIVKEY;
|
||||
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
|
||||
# Fehler sauber abfangen und 503 liefern (statt rohe 502/504)
|
||||
proxy_intercept_errors on;
|
||||
error_page 502 503 504 = @service_down;
|
||||
|
||||
location / {
|
||||
# >>> CHANGE: variable proxy_pass -> DNS zur Laufzeit (verhindert nginx -t Crash)
|
||||
set \$target $SANITIZED_UPSTREAM;
|
||||
proxy_pass \$target;
|
||||
|
||||
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;
|
||||
|
||||
proxy_http_version 1.1;
|
||||
NGINX
|
||||
|
||||
if [ "$WEBSOCKETS" = "true" ]; then
|
||||
cat >> "$HTTPS_OUT" <<'NGINX'
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
NGINX
|
||||
else
|
||||
cat >> "$HTTPS_OUT" <<'NGINX'
|
||||
proxy_set_header Upgrade "";
|
||||
proxy_set_header Connection close;
|
||||
NGINX
|
||||
fi
|
||||
|
||||
case "$SANITIZED_UPSTREAM" in
|
||||
https://*)
|
||||
if [ "$VERIFY_TLS" = "true" ]; then
|
||||
cat >> "$HTTPS_OUT" <<'NGINX'
|
||||
proxy_ssl_verify on;
|
||||
proxy_ssl_server_name on;
|
||||
NGINX
|
||||
else
|
||||
cat >> "$HTTPS_OUT" <<'NGINX'
|
||||
proxy_ssl_verify off;
|
||||
proxy_ssl_server_name on;
|
||||
NGINX
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
cat >> "$HTTPS_OUT" <<'NGINX'
|
||||
client_max_body_size 50m;
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 60s;
|
||||
}
|
||||
|
||||
location @service_down {
|
||||
default_type text/html;
|
||||
return 503 "<!doctype html><html><head><meta charset='utf-8'><title>Service temporarily unavailable</title></head><body style='font-family:sans-serif;margin:3rem'><h1>$server_name nicht erreichbar</h1><p>Der Dienst ist momentan nicht verfügbar. Bitte später erneut versuchen.</p></body></html>";
|
||||
}
|
||||
|
||||
}
|
||||
NGINX
|
||||
fi
|
||||
# <<< END NEW
|
||||
fi
|
||||
|
||||
# 80->443 Redirect-Server nur, wenn gewünscht
|
||||
if [ "$HTTP_BEHAVIOR" = "redirect" ]; then
|
||||
cat > "$HTTP_REDIRECT_OUT" <<NGINX
|
||||
# Auto-generated – 80->443 redirect for $SERVER_NAME
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name $SERVER_NAME;
|
||||
|
||||
# ACME-Ausnahme
|
||||
location ^~ /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
default_type "text/plain; charset=utf-8";
|
||||
}
|
||||
|
||||
location / {
|
||||
return 301 https://\$host\$request_uri;
|
||||
}
|
||||
}
|
||||
NGINX
|
||||
else
|
||||
# Sicherstellen, dass kein alter Redirect liegen bleibt
|
||||
rm -f "$HTTP_REDIRECT_OUT" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
else
|
||||
echo "[connect-proxies] [-] $SERVER_NAME: keine Zertifikate (cert_domain=$CERT_DOMAIN). Entferne evtl. alte Confs."
|
||||
rm -f "$HTTPS_OUT" "$HTTP_REDIRECT_OUT" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
done < "$FWD_FILE"
|
||||
|
||||
echo "[connect-proxies] nginx -t …"
|
||||
nginx -t
|
||||
echo "[connect-proxies] done."
|
||||
|
||||
|
||||
das connect proxies verwendet:
|
||||
=== forwaring.conf ===
|
||||
# server_name upstream_url http_behavior websockets verify_upstream_tls [cert_domain]
|
||||
|
||||
#fluidncRed.server.schooltech.ch http://appServer_TunnelHead:8120 redirect true false
|
||||
|
||||
# 444 → 8121 (WS/WSS)
|
||||
# fluidncRedWs.server.schooltech.ch http://appServer_TunnelHead:8121 redirect true false fluidncRedWs.server.schooltech.ch 444
|
||||
#server.schooltech.ch local static false false
|
||||
server.schooltech.ch local redirect false false server.schooltech.ch 443
|
||||
tcPortainer.server.schooltech.ch http://thinkcentre.local:9000 redirect true false
|
||||
tcGuac.server.schooltech.ch http://thinkcentre.local:9000 redirect true false
|
||||
|
||||
#inf InformatiWeb ist per Tunnel angeschlossen. Soll auf 97xx Ports gehen
|
||||
infPortainer.server.schooltech.ch http://appServer_TunnelHead:9903 redirect true false
|
||||
infGuac.server.schooltech.ch http://appServer_TunnelHead:9980 redirect true false
|
||||
|
||||
#RP5 ist "Lokal" der Server
|
||||
rp5Guac.server.schooltech.ch http://appServer_guacamole:8080 redirect true false
|
||||
rp5Portainer.server.schooltech.ch http://portainer:9000 redirect true false
|
||||
|
||||
|
||||
#RP3 ist Raspi für die Scara-Robots, per Tunnel angeschlossen. Er hat 81xx Ports am TunnelHead
|
||||
rp3Portainer.server.schooltech.ch http://appServer_TunnelHead:8100 redirect true false
|
||||
rp3Guac.server.schooltech.ch http://appServer_TunnelHead:8180 redirect true false
|
||||
fluidncRed.server.schooltech.ch http://appServer_TunnelHead:8120 redirect true false
|
||||
fluidncWhite.server.schooltech.ch https://appServer_TunnelHead:8104 redirect true false
|
||||
|
||||
# ThinkCentre ist ein MiniPC der neben dem einen Roboter steht. Hier sind die 97xx Ports zugewiesen
|
||||
tcGuac.server.schooltech.ch http://appServer_TunnelHead:9780 redirect false false
|
||||
tcPortainer.server.schooltech.ch http://appServer_TunnelHead:9703 redirect false false
|
||||
tcSimulation.server.schooltech.ch https://appServer_TunnelHead:9712 redirect false false
|
||||
#tcVideocontroller.server.schooltech.ch https://tcvideo:9443 redirect true false
|
||||
robotHoming.server.schooltech.ch https://appServer_TunnelHead:9793 redirect false false
|
||||
tcControl.server.schooltech.ch https://appServer_TunnelHead:9710 redirect false false
|
||||
|
||||
|
||||
# Beispiel mit abweichendem Zertifikats-Ordner (Lineage-Suffix)
|
||||
# tcGuac.server.schooltech.ch https://guac:8443 redirect true false server.schooltech.ch-0002
|
||||
|
||||
# Beispiel für WS auf port+1 (zwei Einträge, einer nur für WS-Endpunkt)
|
||||
# wsApp.server.schooltech.ch https://wsapp:443 redirect true false
|
||||
|
||||
|
||||
===============
|
||||
|
||||
Jetzt die Frage:
|
||||
|
||||
1) Siehst du, wie es aufgebaut ist? Siehst du, wie die adresse vom nginx weiter geleitet wird? Hast du fragen dazu?
|
||||
Die weiterleitung läuft. ich sehe die WebPage. Nur der WSS kommt nicht durch
|
||||
2) siehst du, woran das liegt, dass der WSS nicht durch kommt?
|
||||
358
doc/2026_03_21___q3_Auth.txt
Executable file
358
doc/2026_03_21___q3_Auth.txt
Executable file
@@ -0,0 +1,358 @@
|
||||
die Authentifizierung von server.schooltech.ch funktioniert. Ich komme wie gewünscht nur mit aktivem
|
||||
cookie rein.
|
||||
|
||||
# /etc/nginx/conf.d/default.conf
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
|
||||
# ACME HTTP-01 Challenge (Certbot)
|
||||
location ^~ /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
default_type "text/plain; charset=utf-8";
|
||||
}
|
||||
|
||||
# PortalUI root
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# === API forwarding for Auth-Service ===
|
||||
location /api/ {
|
||||
proxy_pass http://appserverauth:3000/api/;
|
||||
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;
|
||||
}
|
||||
|
||||
# === SPA routing (Portal UI) ===
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Die SubDomains werden in 50-subdomains-userA.conf verwaltet. Und bei diesen Subdomains sollen einige auch nur mit aktivem cookie erreichbar sein.
|
||||
Aber nicht alle. Deshalb: Eine weitere spalte in der forwarding.conf datei.
|
||||
|
||||
#!/bin/sh
|
||||
# /docker-entrypoint.d/40-connect-proxies.sh
|
||||
# Generiert pro Zeile in /etc/nginx/forwarding.conf:
|
||||
# - HTTPS vHost (Proxy oder local static) NUR wenn Zertifikate existieren
|
||||
# - optional 80->443 Redirect-Server (mit ACME-Ausnahme) bei http_behavior=redirect
|
||||
# Läuft automatisch beim Containerstart (nginx:alpine EntryPoint).
|
||||
#
|
||||
#
|
||||
# Die Datei: connect-proxies.sh läuft, wenn nginx docker container startet
|
||||
|
||||
set -eu
|
||||
|
||||
CONF_DIR="/etc/nginx/conf.d"
|
||||
LIVE_DIR="/etc/letsencrypt/live"
|
||||
FWD_FILE="/etc/nginx/forwarding.conf"
|
||||
|
||||
HTTPS_SUFFIX="-https.generated.conf"
|
||||
HTTP_REDIRECT_SUFFIX="-http-redirect.generated.conf"
|
||||
GLOBALS_FILE="$CONF_DIR/_globals.generated.conf"
|
||||
|
||||
echo "[connect-proxies] start …"
|
||||
|
||||
# 0) Forwarding-Datei vorhanden?
|
||||
if [ ! -f "$FWD_FILE" ]; then
|
||||
echo "[connect-proxies] WARN: $FWD_FILE fehlt – keine Proxies zu generieren."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 1) Globale HTTP-Kontext-Map + Resolver (idempotent)
|
||||
# >>> CHANGE: Resolver NICHT hardcoden. Dynamisch aus /etc/resolv.conf ableiten, Fallback 127.0.0.11
|
||||
RESOLVERS="$(awk '/^nameserver/{print $2}' /etc/resolv.conf | xargs || true)"
|
||||
if [ -n "${RESOLVERS:-}" ]; then
|
||||
RESOLVER_LINE="resolver $RESOLVERS ipv6=off valid=30s;"
|
||||
else
|
||||
RESOLVER_LINE="resolver 127.0.0.11 ipv6=off valid=30s;"
|
||||
fi
|
||||
|
||||
cat > "$GLOBALS_FILE" <<NGINX
|
||||
# Automatisch generiert – nicht editieren
|
||||
map \$http_upgrade \$connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
$RESOLVER_LINE
|
||||
NGINX
|
||||
# <<< END CHANGE
|
||||
|
||||
# 2) Alte generierte Confs entfernen
|
||||
rm -f "$CONF_DIR/"*"$HTTPS_SUFFIX" 2>/dev/null || true
|
||||
rm -f "$CONF_DIR/"*"$HTTP_REDIRECT_SUFFIX" 2>/dev/null || true
|
||||
|
||||
# 3) Zeilen verarbeiten
|
||||
LINE_NO=0
|
||||
while IFS= read -r RAW || [ -n "$RAW" ]; do
|
||||
LINE_NO=$((LINE_NO+1))
|
||||
# trim + CR entfernen
|
||||
LINE="$(printf '%s' "$RAW" | tr -d '\r' | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')"
|
||||
[ -z "$LINE" ] && continue
|
||||
case "$LINE" in \#*) continue;; esac
|
||||
|
||||
# Spalten splitten (mindestens 2 erforderlich)
|
||||
set -- $LINE
|
||||
SERVER_NAME="${1:-}"
|
||||
UPSTREAM_URL="${2:-}"
|
||||
HTTP_BEHAVIOR="${3:-redirect}"
|
||||
WEBSOCKETS="${4:-false}"
|
||||
VERIFY_TLS="${5:-false}"
|
||||
CERT_DOMAIN="${6:-$SERVER_NAME}"
|
||||
LISTEN_PORT="${7:-443}"
|
||||
|
||||
if [ -z "$SERVER_NAME" ] || [ -z "$UPSTREAM_URL" ]; then
|
||||
echo "[connect-proxies] WARN(Line $LINE_NO): unvollständig -> $LINE"
|
||||
continue
|
||||
fi
|
||||
|
||||
FULLCHAIN="$LIVE_DIR/$CERT_DOMAIN/fullchain.pem"
|
||||
PRIVKEY="$LIVE_DIR/$CERT_DOMAIN/privkey.pem"
|
||||
|
||||
HTTPS_OUT="$CONF_DIR/${SERVER_NAME}-p${LISTEN_PORT}${HTTPS_SUFFIX}"
|
||||
HTTP_REDIRECT_OUT="$CONF_DIR/${SERVER_NAME}${HTTP_REDIRECT_SUFFIX}"
|
||||
|
||||
# >>> NEW: Upstream normalisieren (Trailing Slash entfernen) + DNS-Check vorbereiten
|
||||
SANITIZED_UPSTREAM="${UPSTREAM_URL%/}"
|
||||
DNS_OK="true"
|
||||
SCHEME=""; HOST=""; PORT=""
|
||||
|
||||
if [ "$SANITIZED_UPSTREAM" != "local" ]; then
|
||||
case "$SANITIZED_UPSTREAM" in
|
||||
http://*)
|
||||
URI="${SANITIZED_UPSTREAM#http://}"; SCHEME="http"; DEFAULT_PORT="80"
|
||||
;;
|
||||
https://*)
|
||||
URI="${SANITIZED_UPSTREAM#https://}"; SCHEME="https"; DEFAULT_PORT="443"
|
||||
;;
|
||||
*)
|
||||
echo "[connect-proxies] WARN(Line $LINE_NO): $SERVER_NAME upstream_url ungültig: '$UPSTREAM_URL' – überspringe."
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
HOSTPORT="${URI%%/*}"
|
||||
HOST="${HOSTPORT%%:*}"
|
||||
PORT="${HOSTPORT#*:}"; [ "$PORT" = "$HOSTPORT" ] && PORT="$DEFAULT_PORT"
|
||||
|
||||
if command -v getent >/dev/null 2>&1; then
|
||||
if ! getent hosts "$HOST" >/dev/null 2>&1; then
|
||||
DNS_OK="false"
|
||||
echo "[connect-proxies] [-] $SERVER_NAME: DNS nicht auflösbar ($HOST) – erzeuge 503-Placeholder statt Proxy."
|
||||
fi
|
||||
else
|
||||
DNS_OK="unknown"
|
||||
fi
|
||||
fi
|
||||
# <<< END NEW
|
||||
|
||||
if [ -f "$FULLCHAIN" ] && [ -f "$PRIVKEY" ]; then
|
||||
echo "[connect-proxies] [+] $SERVER_NAME: Zertifikat OK (cert_domain=$CERT_DOMAIN). Erzeuge 443 …"
|
||||
|
||||
# Fall A: local (statisch, kein proxy_pass)
|
||||
if [ "$SANITIZED_UPSTREAM" = "local" ]; then
|
||||
cat > "$HTTPS_OUT" <<NGINX
|
||||
# Auto-generated - 443 static site
|
||||
server {
|
||||
listen ${LISTEN_PORT} ssl http2;
|
||||
listen [::]:${LISTEN_PORT} ssl http2;
|
||||
server_name $SERVER_NAME;
|
||||
|
||||
ssl_certificate $FULLCHAIN;
|
||||
ssl_certificate_key $PRIVKEY;
|
||||
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://appserverauth:3000/api/;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files \$uri \$uri/ /index.html;
|
||||
}
|
||||
}
|
||||
NGINX
|
||||
|
||||
else
|
||||
# >>> NEW: Zwei Pfade – DNS_OK=false => Placeholder; sonst Proxy mit Laufzeit-Resolver
|
||||
if [ "$DNS_OK" = "false" ]; then
|
||||
# 443 Placeholder – keine Proxy-Verbindung, saubere 503
|
||||
cat > "$HTTPS_OUT" <<NGINX
|
||||
# Auto-generated - 443 placeholder (DNS failed)
|
||||
server {
|
||||
listen ${LISTEN_PORT} ssl http2;
|
||||
listen [::]:${LISTEN_PORT} ssl http2;
|
||||
server_name $SERVER_NAME;
|
||||
|
||||
ssl_certificate $FULLCHAIN;
|
||||
ssl_certificate_key $PRIVKEY;
|
||||
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
|
||||
location / {
|
||||
default_type text/html;
|
||||
return 503 "<!doctype html><html><head><meta charset='utf-8'><title>Service temporarily unavailable</title></head><body style='font-family:sans-serif;margin:3rem'><h1>\$server_name nicht erreichbar</h1><p>DNS-Auflösung fehlgeschlagen. Bitte später erneut versuchen.</p></body></html>";
|
||||
}
|
||||
}
|
||||
NGINX
|
||||
else
|
||||
# 443 Proxy – DNS ok/unknown: Laufzeit-Auflösung + freundlicher 503 bei Downstreams
|
||||
cat > "$HTTPS_OUT" <<NGINX
|
||||
# Auto-generated - 443 reverse proxy
|
||||
server {
|
||||
listen ${LISTEN_PORT} ssl http2;
|
||||
listen [::]:${LISTEN_PORT} ssl http2;
|
||||
server_name $SERVER_NAME;
|
||||
|
||||
ssl_certificate $FULLCHAIN;
|
||||
ssl_certificate_key $PRIVKEY;
|
||||
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
|
||||
# Fehler sauber abfangen und 503 liefern (statt rohe 502/504)
|
||||
proxy_intercept_errors on;
|
||||
error_page 502 503 504 = @service_down;
|
||||
|
||||
location / {
|
||||
# >>> CHANGE: variable proxy_pass -> DNS zur Laufzeit (verhindert nginx -t Crash)
|
||||
set \$target $SANITIZED_UPSTREAM;
|
||||
proxy_pass \$target;
|
||||
|
||||
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;
|
||||
|
||||
proxy_http_version 1.1;
|
||||
NGINX
|
||||
|
||||
if [ "$WEBSOCKETS" = "true" ]; then
|
||||
cat >> "$HTTPS_OUT" <<'NGINX'
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
NGINX
|
||||
else
|
||||
cat >> "$HTTPS_OUT" <<'NGINX'
|
||||
proxy_set_header Upgrade "";
|
||||
proxy_set_header Connection close;
|
||||
NGINX
|
||||
fi
|
||||
|
||||
case "$SANITIZED_UPSTREAM" in
|
||||
https://*)
|
||||
if [ "$VERIFY_TLS" = "true" ]; then
|
||||
cat >> "$HTTPS_OUT" <<'NGINX'
|
||||
proxy_ssl_verify on;
|
||||
proxy_ssl_server_name on;
|
||||
NGINX
|
||||
else
|
||||
cat >> "$HTTPS_OUT" <<'NGINX'
|
||||
proxy_ssl_verify off;
|
||||
proxy_ssl_server_name on;
|
||||
NGINX
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
cat >> "$HTTPS_OUT" <<'NGINX'
|
||||
client_max_body_size 50m;
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 60s;
|
||||
}
|
||||
|
||||
location @service_down {
|
||||
default_type text/html;
|
||||
return 503 "<!doctype html><html><head><meta charset='utf-8'><title>Service temporarily unavailable</title></head><body style='font-family:sans-serif;margin:3rem'><h1>$server_name nicht erreichbar</h1><p>Der Dienst ist momentan nicht verfügbar. Bitte später erneut versuchen.</p></body></html>";
|
||||
}
|
||||
|
||||
}
|
||||
NGINX
|
||||
fi
|
||||
# <<< END NEW
|
||||
fi
|
||||
|
||||
# 80->443 Redirect-Server nur, wenn gewünscht
|
||||
if [ "$HTTP_BEHAVIOR" = "redirect" ]; then
|
||||
cat > "$HTTP_REDIRECT_OUT" <<NGINX
|
||||
# Auto-generated – 80->443 redirect for $SERVER_NAME
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name $SERVER_NAME;
|
||||
|
||||
# ACME-Ausnahme
|
||||
location ^~ /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
default_type "text/plain; charset=utf-8";
|
||||
}
|
||||
|
||||
location / {
|
||||
return 301 https://\$host\$request_uri;
|
||||
}
|
||||
}
|
||||
NGINX
|
||||
else
|
||||
# Sicherstellen, dass kein alter Redirect liegen bleibt
|
||||
rm -f "$HTTP_REDIRECT_OUT" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
else
|
||||
echo "[connect-proxies] [-] $SERVER_NAME: keine Zertifikate (cert_domain=$CERT_DOMAIN). Entferne evtl. alte Confs."
|
||||
rm -f "$HTTPS_OUT" "$HTTP_REDIRECT_OUT" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
done < "$FWD_FILE"
|
||||
|
||||
echo "[connect-proxies] nginx -t …"
|
||||
nginx -t
|
||||
echo "[connect-proxies] done."
|
||||
|
||||
# server_name upstream_url http_behavior websockets verify_upstream_tls [cert_domain]
|
||||
|
||||
#fluidncRed.server.schooltech.ch http://appServer_TunnelHead:8120 redirect true false
|
||||
|
||||
# 444 → 8121 (WS/WSS)
|
||||
# fluidncRedWs.server.schooltech.ch http://appServer_TunnelHead:8121 redirect true false fluidncRedWs.server.schooltech.ch 444
|
||||
#server.schooltech.ch local static false false
|
||||
server.schooltech.ch local redirect false false server.schooltech.ch 443
|
||||
tcPortainer.server.schooltech.ch http://thinkcentre.local:9000 redirect true false
|
||||
tcGuac.server.schooltech.ch http://thinkcentre.local:9000 redirect true false
|
||||
|
||||
#inf InformatiWeb ist per Tunnel angeschlossen. Soll auf 97xx Ports gehen
|
||||
infPortainer.server.schooltech.ch http://appServer_TunnelHead:9903 redirect true false
|
||||
infGuac.server.schooltech.ch http://appServer_TunnelHead:9980 redirect true false
|
||||
|
||||
#RP5 ist "Lokal" der Server
|
||||
rp5Guac.server.schooltech.ch http://appServer_guacamole:8080 redirect true false
|
||||
rp5Portainer.server.schooltech.ch http://portainer:9000 redirect true false
|
||||
|
||||
|
||||
#RP3 ist Raspi für die Scara-Robots, per Tunnel angeschlossen. Er hat 81xx Ports am TunnelHead
|
||||
rp3Portainer.server.schooltech.ch http://appServer_TunnelHead:8100 redirect true false
|
||||
rp3Guac.server.schooltech.ch http://appServer_TunnelHead:8180 redirect true false
|
||||
fluidncRed.server.schooltech.ch http://appServer_TunnelHead:8120 redirect true false
|
||||
fluidncWhite.server.schooltech.ch https://appServer_TunnelHead:8104 redirect true false
|
||||
|
||||
============
|
||||
Fragen:
|
||||
1) ist der aufbau einigermassen klar?
|
||||
|
||||
2) weisst du, wo die User-Authentifizierung eingebaut wereden könnte?
|
||||
|
||||
3) Ist es sinnvoll, das in der forwaring.conf abzuspeichern?
|
||||
24
doc/AI_Gen.aux
Executable file
24
doc/AI_Gen.aux
Executable file
@@ -0,0 +1,24 @@
|
||||
\relax
|
||||
\providecommand\hyper@newdestlabel[2]{}
|
||||
\providecommand\HyField@AuxAddToFields[1]{}
|
||||
\providecommand\HyField@AuxAddToCoFields[2]{}
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {1}Zielsetzung}{1}{section.1}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {2}Architekturübersicht (final)}{1}{section.2}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {subsection}{\numberline {2.1}Domänenstruktur}{1}{subsection.2.1}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {subsection}{\numberline {2.2}High-Level Architektur}{1}{subsection.2.2}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {subsection}{\numberline {2.3}Zentrale Prinzipien}{1}{subsection.2.3}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {3}Authentifikation (Variante A: auth\_request)}{2}{section.3}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {subsection}{\numberline {3.1}Prinzip}{2}{subsection.3.1}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {subsection}{\numberline {3.2}Ablauf}{2}{subsection.3.2}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {subsection}{\numberline {3.3}Header-Weitergabe}{2}{subsection.3.3}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {4}Nginx Konfiguration}{2}{section.4}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {subsection}{\numberline {4.1}Wildcard Server Block}{2}{subsection.4.1}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {subsection}{\numberline {4.2}Upstream Mapping}{3}{subsection.4.2}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {5}Docker Compose (Übersicht)}{3}{section.5}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {6}UI-Konzept (Navigations-Portal)}{3}{section.6}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {subsection}{\numberline {6.1}Funktion}{3}{subsection.6.1}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {subsection}{\numberline {6.2}UI-Layout}{4}{subsection.6.2}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {subsection}{\numberline {6.3}HTML-Prototyp (Portal)}{4}{subsection.6.3}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {subsection}{\numberline {6.4}Portal als Docker Container}{5}{subsection.6.4}\protected@file@percent }
|
||||
\@writefile{toc}{\contentsline {section}{\numberline {7}Zusammenfassung}{5}{section.7}\protected@file@percent }
|
||||
\gdef \@abspage@last{5}
|
||||
370
doc/AI_Gen.log
Executable file
370
doc/AI_Gen.log
Executable file
@@ -0,0 +1,370 @@
|
||||
This is pdfTeX, Version 3.141592653-2.6-1.40.27 (MiKTeX 25.4) (preloaded format=pdflatex 2025.6.3) 2 FEB 2026 04:34
|
||||
entering extended mode
|
||||
restricted \write18 enabled.
|
||||
%&-line parsing enabled.
|
||||
**./AI_Gen.tex
|
||||
(AI_Gen.tex
|
||||
LaTeX2e <2024-11-01> patch level 2
|
||||
L3 programming layer <2025-04-29>
|
||||
(C:\Program Files\MiKTeX\tex/latex/base\article.cls
|
||||
Document Class: article 2024/06/29 v1.4n Standard LaTeX document class
|
||||
(C:\Program Files\MiKTeX\tex/latex/base\size11.clo
|
||||
File: size11.clo 2024/06/29 v1.4n Standard LaTeX file (size option)
|
||||
)
|
||||
\c@part=\count272
|
||||
\c@section=\count273
|
||||
\c@subsection=\count274
|
||||
\c@subsubsection=\count275
|
||||
\c@paragraph=\count276
|
||||
\c@subparagraph=\count277
|
||||
\c@figure=\count278
|
||||
\c@table=\count279
|
||||
\abovecaptionskip=\skip49
|
||||
\belowcaptionskip=\skip50
|
||||
\bibindent=\dimen146
|
||||
)
|
||||
(C:\Program Files\MiKTeX\tex/latex/base\inputenc.sty
|
||||
Package: inputenc 2024/02/08 v1.3d Input encoding file
|
||||
\inpenc@prehook=\toks17
|
||||
\inpenc@posthook=\toks18
|
||||
)
|
||||
(C:\Program Files\MiKTeX\tex/latex/base\fontenc.sty
|
||||
Package: fontenc 2021/04/29 v2.0v Standard LaTeX package
|
||||
)
|
||||
(C:\Program Files\MiKTeX\tex/latex/geometry\geometry.sty
|
||||
Package: geometry 2020/01/02 v5.9 Page Geometry
|
||||
|
||||
(C:\Program Files\MiKTeX\tex/latex/graphics\keyval.sty
|
||||
Package: keyval 2022/05/29 v1.15 key=value parser (DPC)
|
||||
\KV@toks@=\toks19
|
||||
)
|
||||
(C:\Program Files\MiKTeX\tex/generic/iftex\ifvtex.sty
|
||||
Package: ifvtex 2019/10/25 v1.7 ifvtex legacy package. Use iftex instead.
|
||||
|
||||
(C:\Program Files\MiKTeX\tex/generic/iftex\iftex.sty
|
||||
Package: iftex 2024/12/12 v1.0g TeX engine tests
|
||||
))
|
||||
\Gm@cnth=\count280
|
||||
\Gm@cntv=\count281
|
||||
\c@Gm@tempcnt=\count282
|
||||
\Gm@bindingoffset=\dimen147
|
||||
\Gm@wd@mp=\dimen148
|
||||
\Gm@odd@mp=\dimen149
|
||||
\Gm@even@mp=\dimen150
|
||||
\Gm@layoutwidth=\dimen151
|
||||
\Gm@layoutheight=\dimen152
|
||||
\Gm@layouthoffset=\dimen153
|
||||
\Gm@layoutvoffset=\dimen154
|
||||
\Gm@dimlist=\toks20
|
||||
|
||||
(C:\Program Files\MiKTeX\tex/latex/geometry\geometry.cfg))
|
||||
(C:\Program Files\MiKTeX\tex/latex/hyperref\hyperref.sty
|
||||
Package: hyperref 2025-05-20 v7.01m Hypertext links for LaTeX
|
||||
|
||||
(C:\Program Files\MiKTeX\tex/latex/kvsetkeys\kvsetkeys.sty
|
||||
Package: kvsetkeys 2022-10-05 v1.19 Key value parser (HO)
|
||||
)
|
||||
(C:\Program Files\MiKTeX\tex/generic/kvdefinekeys\kvdefinekeys.sty
|
||||
Package: kvdefinekeys 2019-12-19 v1.6 Define keys (HO)
|
||||
)
|
||||
(C:\Program Files\MiKTeX\tex/generic/pdfescape\pdfescape.sty
|
||||
Package: pdfescape 2019/12/09 v1.15 Implements pdfTeX's escape features (HO)
|
||||
|
||||
(C:\Program Files\MiKTeX\tex/generic/ltxcmds\ltxcmds.sty
|
||||
Package: ltxcmds 2023-12-04 v1.26 LaTeX kernel commands for general use (HO)
|
||||
)
|
||||
(C:\Program Files\MiKTeX\tex/generic/pdftexcmds\pdftexcmds.sty
|
||||
Package: pdftexcmds 2020-06-27 v0.33 Utility functions of pdfTeX for LuaTeX (HO
|
||||
)
|
||||
|
||||
(C:\Program Files\MiKTeX\tex/generic/infwarerr\infwarerr.sty
|
||||
Package: infwarerr 2019/12/03 v1.5 Providing info/warning/error messages (HO)
|
||||
)
|
||||
Package pdftexcmds Info: \pdf@primitive is available.
|
||||
Package pdftexcmds Info: \pdf@ifprimitive is available.
|
||||
Package pdftexcmds Info: \pdfdraftmode found.
|
||||
))
|
||||
(C:\Program Files\MiKTeX\tex/latex/hycolor\hycolor.sty
|
||||
Package: hycolor 2020-01-27 v1.10 Color options for hyperref/bookmark (HO)
|
||||
)
|
||||
(C:\Program Files\MiKTeX\tex/latex/hyperref\nameref.sty
|
||||
Package: nameref 2023-11-26 v2.56 Cross-referencing by name of section
|
||||
|
||||
(C:\Program Files\MiKTeX\tex/latex/refcount\refcount.sty
|
||||
Package: refcount 2019/12/15 v3.6 Data extraction from label references (HO)
|
||||
)
|
||||
(C:\Program Files\MiKTeX\tex/generic/gettitlestring\gettitlestring.sty
|
||||
Package: gettitlestring 2019/12/15 v1.6 Cleanup title references (HO)
|
||||
|
||||
(C:\Program Files\MiKTeX\tex/latex/kvoptions\kvoptions.sty
|
||||
Package: kvoptions 2022-06-15 v3.15 Key value format for package options (HO)
|
||||
))
|
||||
\c@section@level=\count283
|
||||
)
|
||||
(C:\Program Files\MiKTeX\tex/latex/etoolbox\etoolbox.sty
|
||||
Package: etoolbox 2025/02/11 v2.5l e-TeX tools for LaTeX (JAW)
|
||||
\etb@tempcnta=\count284
|
||||
)
|
||||
(C:\Program Files\MiKTeX\tex/generic/stringenc\stringenc.sty
|
||||
Package: stringenc 2019/11/29 v1.12 Convert strings between diff. encodings (HO
|
||||
)
|
||||
)
|
||||
\@linkdim=\dimen155
|
||||
\Hy@linkcounter=\count285
|
||||
\Hy@pagecounter=\count286
|
||||
|
||||
(C:\Program Files\MiKTeX\tex/latex/hyperref\pd1enc.def
|
||||
File: pd1enc.def 2025-05-20 v7.01m Hyperref: PDFDocEncoding definition (HO)
|
||||
Now handling font encoding PD1 ...
|
||||
... no UTF-8 mapping file for font encoding PD1
|
||||
)
|
||||
(C:\Program Files\MiKTeX\tex/generic/intcalc\intcalc.sty
|
||||
Package: intcalc 2019/12/15 v1.3 Expandable calculations with integers (HO)
|
||||
)
|
||||
\Hy@SavedSpaceFactor=\count287
|
||||
|
||||
(C:\Program Files\MiKTeX\tex/latex/hyperref\puenc.def
|
||||
File: puenc.def 2025-05-20 v7.01m Hyperref: PDF Unicode definition (HO)
|
||||
Now handling font encoding PU ...
|
||||
... no UTF-8 mapping file for font encoding PU
|
||||
)
|
||||
Package hyperref Info: Hyper figures OFF on input line 4157.
|
||||
Package hyperref Info: Link nesting OFF on input line 4162.
|
||||
Package hyperref Info: Hyper index ON on input line 4165.
|
||||
Package hyperref Info: Plain pages OFF on input line 4172.
|
||||
Package hyperref Info: Backreferencing OFF on input line 4177.
|
||||
Package hyperref Info: Implicit mode ON; LaTeX internals redefined.
|
||||
Package hyperref Info: Bookmarks ON on input line 4424.
|
||||
\c@Hy@tempcnt=\count288
|
||||
|
||||
(C:\Program Files\MiKTeX\tex/latex/url\url.sty
|
||||
\Urlmuskip=\muskip17
|
||||
Package: url 2013/09/16 ver 3.4 Verb mode for urls, etc.
|
||||
)
|
||||
LaTeX Info: Redefining \url on input line 4763.
|
||||
\XeTeXLinkMargin=\dimen156
|
||||
|
||||
(C:\Program Files\MiKTeX\tex/generic/bitset\bitset.sty
|
||||
Package: bitset 2019/12/09 v1.3 Handle bit-vector datatype (HO)
|
||||
|
||||
(C:\Program Files\MiKTeX\tex/generic/bigintcalc\bigintcalc.sty
|
||||
Package: bigintcalc 2019/12/15 v1.5 Expandable calculations on big integers (HO
|
||||
)
|
||||
))
|
||||
\Fld@menulength=\count289
|
||||
\Field@Width=\dimen157
|
||||
\Fld@charsize=\dimen158
|
||||
Package hyperref Info: Hyper figures OFF on input line 6042.
|
||||
Package hyperref Info: Link nesting OFF on input line 6047.
|
||||
Package hyperref Info: Hyper index ON on input line 6050.
|
||||
Package hyperref Info: backreferencing OFF on input line 6057.
|
||||
Package hyperref Info: Link coloring OFF on input line 6062.
|
||||
Package hyperref Info: Link coloring with OCG OFF on input line 6067.
|
||||
Package hyperref Info: PDF/A mode OFF on input line 6072.
|
||||
|
||||
(C:\Program Files\MiKTeX\tex/latex/base\atbegshi-ltx.sty
|
||||
Package: atbegshi-ltx 2021/01/10 v1.0c Emulation of the original atbegshi
|
||||
package with kernel methods
|
||||
)
|
||||
\Hy@abspage=\count290
|
||||
\c@Item=\count291
|
||||
\c@Hfootnote=\count292
|
||||
)
|
||||
Package hyperref Info: Driver (autodetected): hpdftex.
|
||||
|
||||
(C:\Program Files\MiKTeX\tex/latex/hyperref\hpdftex.def
|
||||
File: hpdftex.def 2025-05-20 v7.01m Hyperref driver for pdfTeX
|
||||
\Fld@listcount=\count293
|
||||
\c@bookmark@seq@number=\count294
|
||||
|
||||
(C:\Program Files\MiKTeX\tex/latex/rerunfilecheck\rerunfilecheck.sty
|
||||
Package: rerunfilecheck 2022-07-10 v1.10 Rerun checks for auxiliary files (HO)
|
||||
|
||||
(C:\Program Files\MiKTeX\tex/latex/base\atveryend-ltx.sty
|
||||
Package: atveryend-ltx 2020/08/19 v1.0a Emulation of the original atveryend pac
|
||||
kage
|
||||
with kernel methods
|
||||
)
|
||||
(C:\Program Files\MiKTeX\tex/generic/uniquecounter\uniquecounter.sty
|
||||
Package: uniquecounter 2019/12/15 v1.4 Provide unlimited unique counter (HO)
|
||||
)
|
||||
Package uniquecounter Info: New unique counter `rerunfilecheck' on input line 2
|
||||
85.
|
||||
)
|
||||
\Hy@SectionHShift=\skip51
|
||||
)
|
||||
(C:\Program Files\MiKTeX\tex/latex/graphics\graphicx.sty
|
||||
Package: graphicx 2021/09/16 v1.2d Enhanced LaTeX Graphics (DPC,SPQR)
|
||||
|
||||
(C:\Program Files\MiKTeX\tex/latex/graphics\graphics.sty
|
||||
Package: graphics 2024/08/06 v1.4g Standard LaTeX Graphics (DPC,SPQR)
|
||||
|
||||
(C:\Program Files\MiKTeX\tex/latex/graphics\trig.sty
|
||||
Package: trig 2023/12/02 v1.11 sin cos tan (DPC)
|
||||
)
|
||||
(C:\Program Files\MiKTeX\tex/latex/graphics-cfg\graphics.cfg
|
||||
File: graphics.cfg 2016/06/04 v1.11 sample graphics configuration
|
||||
)
|
||||
Package graphics Info: Driver file: pdftex.def on input line 106.
|
||||
|
||||
(C:\Program Files\MiKTeX\tex/latex/graphics-def\pdftex.def
|
||||
File: pdftex.def 2024/04/13 v1.2c Graphics/color driver for pdftex
|
||||
))
|
||||
\Gin@req@height=\dimen159
|
||||
\Gin@req@width=\dimen160
|
||||
)
|
||||
(C:\Program Files\MiKTeX\tex/latex/tools\longtable.sty
|
||||
Package: longtable 2024-10-27 v4.22 Multi-page Table package (DPC)
|
||||
\LTleft=\skip52
|
||||
\LTright=\skip53
|
||||
\LTpre=\skip54
|
||||
\LTpost=\skip55
|
||||
\LTchunksize=\count295
|
||||
\LTcapwidth=\dimen161
|
||||
\LT@head=\box53
|
||||
\LT@firsthead=\box54
|
||||
\LT@foot=\box55
|
||||
\LT@lastfoot=\box56
|
||||
\LT@gbox=\box57
|
||||
\LT@cols=\count296
|
||||
\LT@rows=\count297
|
||||
\c@LT@tables=\count298
|
||||
\c@LT@chunks=\count299
|
||||
\LT@p@ftn=\toks21
|
||||
)
|
||||
(C:\Program Files\MiKTeX\tex/latex/base\textcomp.sty
|
||||
Package: textcomp 2024/04/24 v2.1b Standard LaTeX package
|
||||
)
|
||||
(C:\Program Files\MiKTeX\tex/latex/l3backend\l3backend-pdftex.def
|
||||
File: l3backend-pdftex.def 2025-04-14 L3 backend support: PDF output (pdfTeX)
|
||||
\l__color_backend_stack_int=\count300
|
||||
) (AI_Gen.aux)
|
||||
\openout1 = `AI_Gen.aux'.
|
||||
|
||||
LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 17.
|
||||
LaTeX Font Info: ... okay on input line 17.
|
||||
LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 17.
|
||||
LaTeX Font Info: ... okay on input line 17.
|
||||
LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 17.
|
||||
LaTeX Font Info: ... okay on input line 17.
|
||||
LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 17.
|
||||
LaTeX Font Info: ... okay on input line 17.
|
||||
LaTeX Font Info: Checking defaults for TS1/cmr/m/n on input line 17.
|
||||
LaTeX Font Info: ... okay on input line 17.
|
||||
LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 17.
|
||||
LaTeX Font Info: ... okay on input line 17.
|
||||
LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 17.
|
||||
LaTeX Font Info: ... okay on input line 17.
|
||||
LaTeX Font Info: Checking defaults for PD1/pdf/m/n on input line 17.
|
||||
LaTeX Font Info: ... okay on input line 17.
|
||||
LaTeX Font Info: Checking defaults for PU/pdf/m/n on input line 17.
|
||||
LaTeX Font Info: ... okay on input line 17.
|
||||
*geometry* driver: auto-detecting
|
||||
*geometry* detected driver: pdftex
|
||||
*geometry* verbose mode - [ preamble ] result:
|
||||
* driver: pdftex
|
||||
* paper: a4paper
|
||||
* layout: <same size as paper>
|
||||
* layoutoffset:(h,v)=(0.0pt,0.0pt)
|
||||
* modes:
|
||||
* h-part:(L,W,R)=(71.13188pt, 455.24411pt, 71.13188pt)
|
||||
* v-part:(T,H,B)=(71.13188pt, 702.78308pt, 71.13188pt)
|
||||
* \paperwidth=597.50787pt
|
||||
* \paperheight=845.04684pt
|
||||
* \textwidth=455.24411pt
|
||||
* \textheight=702.78308pt
|
||||
* \oddsidemargin=-1.1381pt
|
||||
* \evensidemargin=-1.1381pt
|
||||
* \topmargin=-38.1381pt
|
||||
* \headheight=12.0pt
|
||||
* \headsep=25.0pt
|
||||
* \topskip=11.0pt
|
||||
* \footskip=30.0pt
|
||||
* \marginparwidth=50.0pt
|
||||
* \marginparsep=10.0pt
|
||||
* \columnsep=10.0pt
|
||||
* \skip\footins=10.0pt plus 4.0pt minus 2.0pt
|
||||
* \hoffset=0.0pt
|
||||
* \voffset=0.0pt
|
||||
* \mag=1000
|
||||
* \@twocolumnfalse
|
||||
* \@twosidefalse
|
||||
* \@mparswitchfalse
|
||||
* \@reversemarginfalse
|
||||
* (1in=72.27pt=25.4mm, 1cm=28.453pt)
|
||||
|
||||
Package hyperref Info: Link coloring OFF on input line 17.
|
||||
(AI_Gen.out) (AI_Gen.out)
|
||||
\@outlinefile=\write3
|
||||
\openout3 = `AI_Gen.out'.
|
||||
|
||||
|
||||
(C:\Program Files\MiKTeX\tex/context/base/mkii\supp-pdf.mkii
|
||||
[Loading MPS to PDF converter (version 2006.09.02).]
|
||||
\scratchcounter=\count301
|
||||
\scratchdimen=\dimen162
|
||||
\scratchbox=\box58
|
||||
\nofMPsegments=\count302
|
||||
\nofMParguments=\count303
|
||||
\everyMPshowfont=\toks22
|
||||
\MPscratchCnt=\count304
|
||||
\MPscratchDim=\dimen163
|
||||
\MPnumerator=\count305
|
||||
\makeMPintoPDFobject=\count306
|
||||
\everyMPtoPDFconversion=\toks23
|
||||
) (C:\Program Files\MiKTeX\tex/latex/epstopdf-pkg\epstopdf-base.sty
|
||||
Package: epstopdf-base 2020-01-24 v2.11 Base part for package epstopdf
|
||||
Package epstopdf-base Info: Redefining graphics rule for `.eps' on input line 4
|
||||
85.
|
||||
|
||||
(C:\Program Files\MiKTeX\tex/latex/00miktex\epstopdf-sys.cfg
|
||||
File: epstopdf-sys.cfg 2021/03/18 v2.0 Configuration of epstopdf for MiKTeX
|
||||
))
|
||||
LaTeX Font Info: External font `cmex10' loaded for size
|
||||
(Font) <12> on input line 19.
|
||||
LaTeX Font Info: External font `cmex10' loaded for size
|
||||
(Font) <8> on input line 19.
|
||||
LaTeX Font Info: External font `cmex10' loaded for size
|
||||
(Font) <6> on input line 19.
|
||||
|
||||
|
||||
[1
|
||||
|
||||
{C:/Users/kech/AppData/Local/MiKTeX/fonts/map/pdftex/pdftex.map}]
|
||||
|
||||
[2]
|
||||
|
||||
[3]
|
||||
|
||||
[4]
|
||||
|
||||
[5] (AI_Gen.aux)
|
||||
***********
|
||||
LaTeX2e <2024-11-01> patch level 2
|
||||
L3 programming layer <2025-04-29>
|
||||
***********
|
||||
Package rerunfilecheck Info: File `AI_Gen.out' has not changed.
|
||||
(rerunfilecheck) Checksum: 98397D1D26258D766FC36BD7CFEC6FAB;2898.
|
||||
)
|
||||
Here is how much of TeX's memory you used:
|
||||
9343 strings out of 469923
|
||||
143002 string characters out of 5479241
|
||||
540633 words of memory out of 5000000
|
||||
36047 multiletter control sequences out of 15000+600000
|
||||
635136 words of font info for 58 fonts, out of 8000000 for 9000
|
||||
1141 hyphenation exceptions out of 8191
|
||||
75i,6n,79p,319b,587s stack positions out of 10000i,1000n,20000p,200000b,200000s
|
||||
<C:\Users\kech\AppData\Local\MiKTeX\fonts/pk/ljfour/jknappen/ec/dpi600\ectt1
|
||||
095.pk> <C:\Users\kech\AppData\Local\MiKTeX\fonts/pk/ljfour/jknappen/ec/dpi600\
|
||||
tcrm1095.pk> <C:\Users\kech\AppData\Local\MiKTeX\fonts/pk/ljfour/jknappen/ec/dp
|
||||
i600\ecbx1200.pk> <C:\Users\kech\AppData\Local\MiKTeX\fonts/pk/ljfour/jknappen/
|
||||
ec/dpi600\ecrm1095.pk> <C:\Users\kech\AppData\Local\MiKTeX\fonts/pk/ljfour/jkna
|
||||
ppen/ec/dpi600\ecbx1440.pk> <C:\Users\kech\AppData\Local\MiKTeX\fonts/pk/ljfour
|
||||
/jknappen/ec/dpi600\ecrm1200.pk> <C:\Users\kech\AppData\Local\MiKTeX\fonts/pk/l
|
||||
jfour/jknappen/ec/dpi600\ecrm1728.pk>
|
||||
Output written on AI_Gen.pdf (5 pages, 109855 bytes).
|
||||
PDF statistics:
|
||||
408 PDF objects out of 1000 (max. 8388607)
|
||||
30 named destinations out of 1000 (max. 500000)
|
||||
153 words of extra memory for PDF output out of 10000 (max. 10000000)
|
||||
|
||||
19
doc/AI_Gen.out
Executable file
19
doc/AI_Gen.out
Executable file
@@ -0,0 +1,19 @@
|
||||
\BOOKMARK [1][-]{section.1}{\376\377\000Z\000i\000e\000l\000s\000e\000t\000z\000u\000n\000g}{}% 1
|
||||
\BOOKMARK [1][-]{section.2}{\376\377\000A\000r\000c\000h\000i\000t\000e\000k\000t\000u\000r\000\374\000b\000e\000r\000s\000i\000c\000h\000t\000\040\000\050\000f\000i\000n\000a\000l\000\051}{}% 2
|
||||
\BOOKMARK [2][-]{subsection.2.1}{\376\377\000D\000o\000m\000\344\000n\000e\000n\000s\000t\000r\000u\000k\000t\000u\000r}{section.2}% 3
|
||||
\BOOKMARK [2][-]{subsection.2.2}{\376\377\000H\000i\000g\000h\000-\000L\000e\000v\000e\000l\000\040\000A\000r\000c\000h\000i\000t\000e\000k\000t\000u\000r}{section.2}% 4
|
||||
\BOOKMARK [2][-]{subsection.2.3}{\376\377\000Z\000e\000n\000t\000r\000a\000l\000e\000\040\000P\000r\000i\000n\000z\000i\000p\000i\000e\000n}{section.2}% 5
|
||||
\BOOKMARK [1][-]{section.3}{\376\377\000A\000u\000t\000h\000e\000n\000t\000i\000f\000i\000k\000a\000t\000i\000o\000n\000\040\000\050\000V\000a\000r\000i\000a\000n\000t\000e\000\040\000A\000:\000\040\000a\000u\000t\000h\000\137\000r\000e\000q\000u\000e\000s\000t\000\051}{}% 6
|
||||
\BOOKMARK [2][-]{subsection.3.1}{\376\377\000P\000r\000i\000n\000z\000i\000p}{section.3}% 7
|
||||
\BOOKMARK [2][-]{subsection.3.2}{\376\377\000A\000b\000l\000a\000u\000f}{section.3}% 8
|
||||
\BOOKMARK [2][-]{subsection.3.3}{\376\377\000H\000e\000a\000d\000e\000r\000-\000W\000e\000i\000t\000e\000r\000g\000a\000b\000e}{section.3}% 9
|
||||
\BOOKMARK [1][-]{section.4}{\376\377\000N\000g\000i\000n\000x\000\040\000K\000o\000n\000f\000i\000g\000u\000r\000a\000t\000i\000o\000n}{}% 10
|
||||
\BOOKMARK [2][-]{subsection.4.1}{\376\377\000W\000i\000l\000d\000c\000a\000r\000d\000\040\000S\000e\000r\000v\000e\000r\000\040\000B\000l\000o\000c\000k}{section.4}% 11
|
||||
\BOOKMARK [2][-]{subsection.4.2}{\376\377\000U\000p\000s\000t\000r\000e\000a\000m\000\040\000M\000a\000p\000p\000i\000n\000g}{section.4}% 12
|
||||
\BOOKMARK [1][-]{section.5}{\376\377\000D\000o\000c\000k\000e\000r\000\040\000C\000o\000m\000p\000o\000s\000e\000\040\000\050\000\334\000b\000e\000r\000s\000i\000c\000h\000t\000\051}{}% 13
|
||||
\BOOKMARK [1][-]{section.6}{\376\377\000U\000I\000-\000K\000o\000n\000z\000e\000p\000t\000\040\000\050\000N\000a\000v\000i\000g\000a\000t\000i\000o\000n\000s\000-\000P\000o\000r\000t\000a\000l\000\051}{}% 14
|
||||
\BOOKMARK [2][-]{subsection.6.1}{\376\377\000F\000u\000n\000k\000t\000i\000o\000n}{section.6}% 15
|
||||
\BOOKMARK [2][-]{subsection.6.2}{\376\377\000U\000I\000-\000L\000a\000y\000o\000u\000t}{section.6}% 16
|
||||
\BOOKMARK [2][-]{subsection.6.3}{\376\377\000H\000T\000M\000L\000-\000P\000r\000o\000t\000o\000t\000y\000p\000\040\000\050\000P\000o\000r\000t\000a\000l\000\051}{section.6}% 17
|
||||
\BOOKMARK [2][-]{subsection.6.4}{\376\377\000P\000o\000r\000t\000a\000l\000\040\000a\000l\000s\000\040\000D\000o\000c\000k\000e\000r\000\040\000C\000o\000n\000t\000a\000i\000n\000e\000r}{section.6}% 18
|
||||
\BOOKMARK [1][-]{section.7}{\376\377\000Z\000u\000s\000a\000m\000m\000e\000n\000f\000a\000s\000s\000u\000n\000g}{}% 19
|
||||
BIN
doc/AI_Gen.pdf
Executable file
BIN
doc/AI_Gen.pdf
Executable file
Binary file not shown.
BIN
doc/AI_Gen.synctex.gz
Executable file
BIN
doc/AI_Gen.synctex.gz
Executable file
Binary file not shown.
249
doc/AI_Gen.tex
Executable file
249
doc/AI_Gen.tex
Executable file
@@ -0,0 +1,249 @@
|
||||
\documentclass[a4paper,11pt]{article}
|
||||
|
||||
\usepackage[utf8]{inputenc}
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage{geometry}
|
||||
\usepackage{hyperref}
|
||||
\usepackage{graphicx}
|
||||
\usepackage{longtable}
|
||||
\usepackage{textcomp}
|
||||
|
||||
\geometry{margin=2.5cm}
|
||||
|
||||
\title{Service-Portal Architektur}
|
||||
\author{schooltech.ch}
|
||||
\date{\today}
|
||||
|
||||
\begin{document}
|
||||
\maketitle
|
||||
|
||||
\section{Zielsetzung}
|
||||
Ziel ist der Aufbau eines zentralen Login- und Navigations-Portals für mehrere unabhängige Web-Services,
|
||||
die in Docker-Containern betrieben werden.
|
||||
Benutzer authentifizieren sich einmal zentral und können anschließend zwischen Services wechseln,
|
||||
die jeweils unter eigenen Subdomains bereitgestellt werden.
|
||||
|
||||
\section{Architekturübersicht (final)}
|
||||
|
||||
\subsection{Domänenstruktur}
|
||||
\begin{itemize}
|
||||
\item \texttt{server.schooltech.ch} – Login- und Navigations-Portal (SPA)
|
||||
\item \texttt{<service>.server.schooltech.ch} – einzelne Services (Docker Container)
|
||||
\end{itemize}
|
||||
|
||||
\subsection{High-Level Architektur}
|
||||
\begin{verbatim}
|
||||
Internet
|
||||
|
|
||||
| HTTPS (*.server.schooltech.ch)
|
||||
v
|
||||
+---------------------+
|
||||
| Nginx ReverseProxy |
|
||||
| TLS, Auth, Routing |
|
||||
+---------------------+
|
||||
| |
|
||||
| +--> Service Container (abc)
|
||||
| abc.server.schooltech.ch
|
||||
|
|
||||
+--> Portal / Auth Service
|
||||
server.schooltech.ch
|
||||
\end{verbatim}
|
||||
|
||||
\subsection{Zentrale Prinzipien}
|
||||
\begin{itemize}
|
||||
\item Ein Einstiegspunkt (Nginx)
|
||||
\item Zentrale Authentifikation (auth\_request)
|
||||
\item Services kennen kein Login
|
||||
\item Keine iFrames, kein Subpath-Rewrite
|
||||
\item Services laufen auf Root-Pfad (\texttt{/})
|
||||
\end{itemize}
|
||||
|
||||
\section{Authentifikation (Variante A: auth\_request)}
|
||||
|
||||
\subsection{Prinzip}
|
||||
Nginx prüft jeden Request zu einem Service mittels \texttt{auth\_request} gegen den Auth-Service.
|
||||
Der Auth-Service entscheidet ausschließlich anhand der Session (Cookie).
|
||||
|
||||
\subsection{Ablauf}
|
||||
\begin{enumerate}
|
||||
\item Benutzer loggt sich am Portal ein
|
||||
\item Portal setzt Session-Cookie:
|
||||
\begin{verbatim}
|
||||
Domain=.server.schooltech.ch
|
||||
Secure; HttpOnly; SameSite=None
|
||||
\end{verbatim}
|
||||
\item Benutzer ruft Service-Subdomain auf
|
||||
\item Nginx fragt \texttt{/internal/auth} beim Auth-Service an
|
||||
\item Bei Erfolg: Request wird an Service weitergeleitet
|
||||
\end{enumerate}
|
||||
|
||||
\subsection{Header-Weitergabe}
|
||||
Optional injiziert Nginx folgende Header:
|
||||
\begin{itemize}
|
||||
\item \texttt{X-Remote-User}
|
||||
\item \texttt{X-User-Roles}
|
||||
\end{itemize}
|
||||
|
||||
\section{Nginx Konfiguration}
|
||||
|
||||
\subsection{Wildcard Server Block}
|
||||
\begin{verbatim}
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name *.server.schooltech.ch;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/server.schooltech.ch/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/server.schooltech.ch/privkey.pem;
|
||||
|
||||
location / {
|
||||
auth_request /internal/auth;
|
||||
proxy_pass http://$upstream;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
|
||||
location = /internal/auth {
|
||||
proxy_pass http://auth:3000/internal/auth;
|
||||
proxy_pass_request_body off;
|
||||
proxy_set_header Content-Length "";
|
||||
proxy_set_header X-Original-URI $request_uri;
|
||||
}
|
||||
}
|
||||
\end{verbatim}
|
||||
|
||||
\subsection{Upstream Mapping}
|
||||
\begin{verbatim}
|
||||
map $host $upstream {
|
||||
default portal:3000;
|
||||
abc.server.schooltech.ch abc:8080;
|
||||
xyz.server.schooltech.ch xyz:8080;
|
||||
}
|
||||
\end{verbatim}
|
||||
|
||||
\section{Docker Compose (Übersicht)}
|
||||
|
||||
\begin{verbatim}
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./nginx:/etc/nginx/conf.d
|
||||
- ./certs:/etc/letsencrypt
|
||||
depends_on:
|
||||
- auth
|
||||
- portal
|
||||
networks:
|
||||
- internal
|
||||
|
||||
auth:
|
||||
image: node:18
|
||||
command: node auth.js
|
||||
networks:
|
||||
- internal
|
||||
|
||||
portal:
|
||||
image: node:18
|
||||
command: node portal.js
|
||||
networks:
|
||||
- internal
|
||||
|
||||
abc:
|
||||
image: service-abc
|
||||
networks:
|
||||
- internal
|
||||
|
||||
networks:
|
||||
internal:
|
||||
driver: bridge
|
||||
\end{verbatim}
|
||||
|
||||
\section{UI-Konzept (Navigations-Portal)}
|
||||
|
||||
\subsection{Funktion}
|
||||
\begin{itemize}
|
||||
\item Login
|
||||
\item Anzeige verfügbarer Services
|
||||
\item Wechsel per Full Navigation
|
||||
\item Kollabierbare Kopfzeile
|
||||
\item Floating Launcher (Bookmark)
|
||||
\end{itemize}
|
||||
|
||||
\subsection{UI-Layout}
|
||||
\begin{verbatim}
|
||||
+------------------------------------------------+
|
||||
| LOGO | Service A | Service B | User |
|
||||
+------------------------------------------------+
|
||||
| |
|
||||
| Service Overview / Last Service / Status |
|
||||
| |
|
||||
+------------------------------------------------+
|
||||
\end{verbatim}
|
||||
|
||||
\subsection{HTML-Prototyp (Portal)}
|
||||
\begin{verbatim}
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Service Portal</title>
|
||||
<style>
|
||||
header {
|
||||
position: fixed;
|
||||
top: 0; left: 0; right: 0;
|
||||
height: 60px;
|
||||
backdrop-filter: blur(6px);
|
||||
background: rgba(0,0,0,0.3);
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
}
|
||||
main {
|
||||
padding-top: 80px;
|
||||
}
|
||||
.service {
|
||||
display: inline-block;
|
||||
margin: 10px;
|
||||
padding: 20px;
|
||||
border: 1px solid #ccc;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<strong>Portal</strong>
|
||||
</header>
|
||||
<main>
|
||||
<div class="service" onclick="go('abc')">Service ABC</div>
|
||||
<div class="service" onclick="go('xyz')">Service XYZ</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
function go(name) {
|
||||
localStorage.setItem("lastService", name);
|
||||
window.location.href = "https://" + name + ".server.schooltech.ch";
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
\end{verbatim}
|
||||
|
||||
\subsection{Portal als Docker Container}
|
||||
Das Portal wird als eigener Container betrieben und statisch oder via Node.js ausgeliefert.
|
||||
Es besitzt keine direkte Kopplung zu den Services außer über URLs.
|
||||
|
||||
\section{Zusammenfassung}
|
||||
Diese Architektur ermöglicht:
|
||||
\begin{itemize}
|
||||
\item saubere Trennung von Portal und Services
|
||||
\item zentrale Sicherheit
|
||||
\item einfache Erweiterbarkeit
|
||||
\item wartbare Reverse-Proxy-Konfiguration
|
||||
\end{itemize}
|
||||
|
||||
\end{document}
|
||||
9
doc/AI_Gen.txt
Executable file
9
doc/AI_Gen.txt
Executable file
@@ -0,0 +1,9 @@
|
||||
Ich habe eine WEbPage hinter einer Firewall. Die besteht aus mehreren Services die in Docker Containern mit jeweils eine Einstiegs-Seite (an einem offenem Port) breitstehen.
|
||||
|
||||
Jetzt will ich für Kunden eine Login-Page aufbauen. Auf der Login-Page soll in der Kopfzeile ein transparenter Balken sein, mit User- und Passwort Authentifikation. Wenn der User angemeldet ist sollen in der Kopf-Zeile verschiedene Buttons (für verschiedene Services) erscheinen. Jeder dieser Services soll auf einen Container zeigen, so dass die Kopfzeile als Navigation der Services möglich ist.
|
||||
|
||||
Wenn auf eine der Services gedrückt wird, soll die Kopfzeile zur "kollabieren" und nur noch als "offnebares-bookmark" oder so bereitstehen. Die Haupt-Page soll eben auf diesen Service zeigen.
|
||||
|
||||
Ich gehe davon aus, dass dafür 1) eine Login-Site (eventuell mit NodeJS, da ich das kenne) mit Navigation nötig ist, 2) Nginx im Hintergrund die Pages weiterleitet.
|
||||
|
||||
Skizziere erst mal einen Aufbau, bevor ich über Implementationen nachdenke.
|
||||
97
doc/Architektur.svg
Normal file
97
doc/Architektur.svg
Normal file
@@ -0,0 +1,97 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="1180" height="580" viewBox="0 0 1180 580" font-family="Segoe UI, Helvetica, Arial, sans-serif">
|
||||
|
||||
<defs>
|
||||
<marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto" markerUnits="strokeWidth">
|
||||
<path d="M0,0 L8,3 L0,6 Z" fill="#64748b"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<!-- canvas -->
|
||||
<rect x="0" y="0" width="1180" height="580" fill="#ffffff"/>
|
||||
<rect x="4" y="4" width="1172" height="572" rx="14" fill="none" stroke="#e2e8f0" stroke-width="2"/>
|
||||
|
||||
<!-- title -->
|
||||
<text x="590" y="36" text-anchor="middle" font-size="20" font-weight="700" fill="#0f172a">Architektur-Übersicht — server.schooltech.ch</text>
|
||||
|
||||
<!-- ===== Internet ===== -->
|
||||
<rect x="490" y="56" width="200" height="44" rx="10" fill="#eef2f7" stroke="#94a3b8" stroke-width="1.5"/>
|
||||
<text x="590" y="76" text-anchor="middle" font-size="15" font-weight="700" fill="#1e293b">Internet</text>
|
||||
<text x="590" y="92" text-anchor="middle" font-size="11" fill="#475569">HTTPS :443 · HTTP :80</text>
|
||||
|
||||
<!-- arrow Internet -> nginx -->
|
||||
<line x1="590" y1="100" x2="590" y2="130" stroke="#64748b" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- ===== nginx ===== -->
|
||||
<rect x="70" y="132" width="1040" height="80" rx="10" fill="#2563eb" stroke="#1d4ed8" stroke-width="1.5"/>
|
||||
<text x="590" y="160" text-anchor="middle" font-size="16" font-weight="700" fill="#ffffff">nginx Reverse-Proxy — Container: appServer_PortalUI</text>
|
||||
<text x="590" y="181" text-anchor="middle" font-size="12.5" fill="#dbeafe">ein vHost pro Subdomain *.server.schooltech.ch</text>
|
||||
<text x="590" y="199" text-anchor="middle" font-size="12.5" fill="#dbeafe">TLS-Terminierung (Let's Encrypt) · 80 → 443 Redirect</text>
|
||||
|
||||
<!-- distribution bus -->
|
||||
<line x1="590" y1="212" x2="590" y2="248" stroke="#64748b" stroke-width="2"/>
|
||||
<line x1="185" y1="248" x2="980" y2="248" stroke="#64748b" stroke-width="2"/>
|
||||
<line x1="185" y1="248" x2="185" y2="274" stroke="#64748b" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<line x1="445" y1="248" x2="445" y2="274" stroke="#64748b" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<line x1="705" y1="248" x2="705" y2="274" stroke="#64748b" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<line x1="980" y1="248" x2="980" y2="274" stroke="#64748b" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- ===== category boxes (row: Subdomain-Gruppe) ===== -->
|
||||
<!-- col1 Portal (green) -->
|
||||
<rect x="70" y="276" width="230" height="84" rx="10" fill="#dcfce7" stroke="#22c55e" stroke-width="1.5"/>
|
||||
<text x="185" y="309" text-anchor="middle" font-size="14" font-weight="700" fill="#14532d">server.schooltech.ch</text>
|
||||
<text x="185" y="331" text-anchor="middle" font-size="11.5" fill="#166534">Portal-UI (diese App)</text>
|
||||
|
||||
<!-- col2 lokale Container (amber) -->
|
||||
<rect x="330" y="276" width="230" height="84" rx="10" fill="#ffedd5" stroke="#f97316" stroke-width="1.5"/>
|
||||
<text x="445" y="309" text-anchor="middle" font-size="14" font-weight="700" fill="#7c2d12">rp5*.schooltech.ch</text>
|
||||
<text x="445" y="331" text-anchor="middle" font-size="11.5" fill="#9a3412">lokale Container</text>
|
||||
|
||||
<!-- col3 LAN (purple) -->
|
||||
<rect x="590" y="276" width="230" height="84" rx="10" fill="#f3e8ff" stroke="#a855f7" stroke-width="1.5"/>
|
||||
<text x="705" y="309" text-anchor="middle" font-size="13.5" font-weight="700" fill="#581c87">nextcloud.schooltech.ch</text>
|
||||
<text x="705" y="331" text-anchor="middle" font-size="11.5" fill="#6b21a8">Gerät im LAN · direkte IP</text>
|
||||
|
||||
<!-- col4 Tunnel-Hub (teal) -->
|
||||
<rect x="850" y="276" width="260" height="84" rx="10" fill="#ccfbf1" stroke="#14b8a6" stroke-width="1.5"/>
|
||||
<text x="980" y="303" text-anchor="middle" font-size="11.5" font-weight="700" fill="#134e4a">inf* · rp3* · tc* · robot* · fluidnc*</text>
|
||||
<text x="980" y="322" text-anchor="middle" font-size="11.5" fill="#115e59">.server.schooltech.ch</text>
|
||||
<text x="980" y="340" text-anchor="middle" font-size="11.5" fill="#115e59">über SSH-Tunnel-Hub</text>
|
||||
|
||||
<!-- arrows category -> backend -->
|
||||
<line x1="185" y1="360" x2="185" y2="390" stroke="#64748b" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<line x1="445" y1="360" x2="445" y2="390" stroke="#64748b" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<line x1="705" y1="360" x2="705" y2="390" stroke="#64748b" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<line x1="980" y1="360" x2="980" y2="390" stroke="#64748b" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- ===== backend boxes (row: Ziel / Upstream) ===== -->
|
||||
<!-- col1 -->
|
||||
<rect x="70" y="392" width="230" height="84" rx="10" fill="#f0fdf4" stroke="#4ade80" stroke-width="1.5"/>
|
||||
<text x="185" y="426" text-anchor="middle" font-size="12.5" fill="#14532d">public/index.html</text>
|
||||
<text x="185" y="446" text-anchor="middle" font-size="12.5" fill="#14532d">+ Auth-API (/api/ → :3000)</text>
|
||||
|
||||
<!-- col2 -->
|
||||
<rect x="330" y="392" width="230" height="84" rx="10" fill="#fff7ed" stroke="#fb923c" stroke-width="1.5"/>
|
||||
<text x="445" y="426" text-anchor="middle" font-size="12.5" fill="#7c2d12">appServer_guacamole</text>
|
||||
<text x="445" y="446" text-anchor="middle" font-size="12.5" fill="#7c2d12">portainer</text>
|
||||
|
||||
<!-- col3 -->
|
||||
<rect x="590" y="392" width="230" height="60" rx="10" fill="#faf5ff" stroke="#c084fc" stroke-width="1.5"/>
|
||||
<text x="705" y="427" text-anchor="middle" font-size="12.5" fill="#581c87">192.168.0.210:9183</text>
|
||||
|
||||
<!-- col4 (tunnel head, taller) -->
|
||||
<rect x="850" y="392" width="260" height="150" rx="10" fill="#f0fdfa" stroke="#2dd4bf" stroke-width="1.5"/>
|
||||
<text x="980" y="416" text-anchor="middle" font-size="13" font-weight="700" fill="#134e4a">appServer_TunnelHead</text>
|
||||
<text x="980" y="433" text-anchor="middle" font-size="10.5" font-style="italic" fill="#0f766e">SSH-Reverse-Tunnels · feste Ports</text>
|
||||
<line x1="868" y1="444" x2="1092" y2="444" stroke="#99f6e4" stroke-width="1"/>
|
||||
<text x="868" y="466" text-anchor="start" font-size="11" fill="#134e4a">99xx — InformatikWeb (inf*)</text>
|
||||
<text x="868" y="490" text-anchor="start" font-size="11" fill="#134e4a">81xx — RP3 / SCARA (rp3*, fluidnc*)</text>
|
||||
<text x="868" y="514" text-anchor="start" font-size="11" fill="#134e4a">97xx — ThinkCentre (tc*, robot*)</text>
|
||||
|
||||
<!-- row labels (left gutter) -->
|
||||
<text x="40" y="176" text-anchor="middle" font-size="10" fill="#94a3b8" transform="rotate(-90 40 176)">PROXY</text>
|
||||
<text x="40" y="318" text-anchor="middle" font-size="10" fill="#94a3b8" transform="rotate(-90 40 318)">SUBDOMAIN-GRUPPE</text>
|
||||
<text x="40" y="450" text-anchor="middle" font-size="10" fill="#94a3b8" transform="rotate(-90 40 450)">UPSTREAM / ZIEL</text>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.7 KiB |
BIN
doc/Portal.pdf
Normal file
BIN
doc/Portal.pdf
Normal file
Binary file not shown.
194
doc/Portal.svg
Normal file
194
doc/Portal.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 88 KiB |
Reference in New Issue
Block a user