Wieder anfassen nach Monaten

This commit is contained in:
chk
2026-06-06 17:14:11 +02:00
parent 87557db09b
commit 263e9b4565
16 changed files with 705 additions and 0 deletions

11
.claude/launch.json Normal file
View File

@@ -0,0 +1,11 @@
{
"version": "0.0.1",
"configurations": [
{
"name": "portal-static",
"runtimeExecutable": "python",
"runtimeArgs": ["-m", "http.server", "8099", "--directory", "public"],
"port": 8099
}
]
}

352
README.md Normal file
View File

@@ -0,0 +1,352 @@
# appServer Portal UI
Übersichts-Webserver für **`server.schooltech.ch`**. Er fasst eine Reihe von
Web-Diensten (Roboter-Steuerungen, Guacamole-Remotedesktops, Portainer,
VS Code, Nextcloud …) unter **einer Domain** zusammen und macht sie über
sprechende Subdomains erreichbar.
Alle Dienste laufen als Subdomain von `.server.schooltech.ch` egal ob sie
auf dem Server selbst, auf einem Gerät im LAN oder hinter einem SSH-Tunnel
stehen. Ein nginx-Reverse-Proxy nimmt jede Subdomain auf 443 entgegen,
terminiert TLS (Let's Encrypt) und leitet an den passenden Upstream weiter.
---
## Inhalt
- [Architektur-Übersicht](#architektur-übersicht)
- [Die Portal-Seite (`server.schooltech.ch`)](#die-portal-seite-serverschooltechch)
- [Die Dienste / Subdomains](#die-dienste--subdomains)
- [`forwarding.conf` das Herzstück](#forwardingconf--das-herzstück)
- [`connect-proxies.sh` Konfig-Generator](#connect-proxiessh--konfig-generator)
- [Authentifizierung](#authentifizierung)
- [TLS / Let's Encrypt](#tls--lets-encrypt)
- [Container & Betrieb](#container--betrieb)
- [Neuen Dienst hinzufügen](#neuen-dienst-hinzufügen)
- [Dateiübersicht](#dateiübersicht)
---
## Architektur-Übersicht
Alles hängt unter `*.server.schooltech.ch`. Der Unterschied zwischen den
Diensten ist nicht die Domain, sondern **wohin der Upstream zeigt**:
![Architektur-Übersicht](doc/Architektur.svg)
> Diagramm-Quelle: [`doc/Architektur.svg`](doc/Architektur.svg) (daraus lässt
> sich bei Bedarf eine PDF/PNG erzeugen).
**Bausteine (Docker-Container, siehe `docker-compose.yaml`):**
| Container | Image | Rolle |
|---|---|---|
| `appServer_PortalUI` | `nginx:alpine` | Reverse-Proxy + Auslieferung der Portal-Seite (Ports 80/443) |
| `AppServerAuth` | `node:24-alpine` | Login-/Session-Service (`auth/auth.js`, Port 3000 intern) |
| `appServer_TunnelHead` | `linuxserver/openssh-server` | SSH-Hub: entfernte Geräte bauen Reverse-Tunnel hierher auf |
| `appServer_guacamole` | `abesnier/guacamole` | Lokaler Guacamole-Remotedesktop |
| `appServer_LetsEncryptFetcher` | `certbot/certbot` | Holt/erneuert die TLS-Zertifikate |
Alle Container hängen im Docker-Netzwerk **`appRobotNet`** und sprechen sich
per Container-Namen an (z. B. `appserverauth:3000`).
---
## Die Portal-Seite (`server.schooltech.ch`)
Ruft man die nackte Domain `server.schooltech.ch` auf, erscheint die
**Portal-Oberfläche** eine schlanke Single-Page-App aus `public/`:
- **`public/index.html`** Grundgerüst: oben eine **Navigationsleiste**
(`<header>` mit Logo *„schooltech“*, der Service-Navigation `#services`
und einem Login/Logout-Button), darunter ein **`<iframe>`**, in dem der
gewählte Dienst eingeblendet wird.
- **`public/app.js`** die Logik:
- hält eine Liste der verlinkten Dienste (`services`-Array),
- prüft beim Laden via `GET /api/status`, ob eine Session besteht,
- blendet **nach dem Login** für jeden Dienst einen **Button in die
Navigationsleiste** ein,
- öffnet beim Klick den Dienst im `<iframe>` (und meldet das Ereignis an
`POST /api/event`),
- Login/Logout laufen über `POST /api/login` bzw. `POST /api/logout`.
- **`public/style.css`** Styling der Leiste, der Buttons und des
Login-Dialogs.
**Ablauf aus Nutzersicht:**
![Portal-Ansicht](doc/Portal.svg)
*Oben die Navigationsleiste mit Logo und Dienst-Buttons, darunter der gewählte
Dienst im iFrame. (Quelle: [`doc/Portal.svg`](doc/Portal.svg), auch als
[`doc/Portal.pdf`](doc/Portal.pdf).)*
1. Seite öffnen → ist man nicht eingeloggt, zeigt der Button **„Login“**.
2. Login (User/Passwort) → der Auth-Service setzt ein Session-Cookie für die
gesamte Domain `.server.schooltech.ch`.
3. Die Navigationsleiste füllt sich mit den Dienst-Buttons.
4. Klick auf einen Button blendet den Dienst im iFrame ein.
> **Hinweis:** Die im Portal angezeigten Buttons stehen **hardcodiert** in
> `public/app.js` (`services`-Array, aktuell 10 Einträge) sie werden *nicht*
> automatisch aus `forwarding.conf` erzeugt. Ein neuer Dienst in
> `forwarding.conf` taucht also erst dann in der Navigationsleiste auf, wenn er
> auch ins `services`-Array eingetragen wird (siehe
> [Neuen Dienst hinzufügen](#neuen-dienst-hinzufügen)).
Aktuell im Portal verlinkte Dienste (`public/app.js`):
| Button | Ziel-Subdomain |
|---|---|
| Control GamePad | `tccontrol.server.schooltech.ch` |
| Guacamole | `rp5guac.server.schooltech.ch` |
| Simulation | `tcSimulation.server.schooltech.ch` |
| Video | `robotVideo.server.schooltech.ch` |
| Homing | `robotHoming.server.schooltech.ch` |
| RobotBase | `robotBase.server.schooltech.ch` |
| RobotEllbow | `robotEllbow.server.schooltech.ch` |
| RobotHand | `robotHand.server.schooltech.ch` |
| RobotDriver | `robotDriver.server.schooltech.ch` |
| VSCode | `robotVSCode.server.schooltech.ch` |
---
## Die Dienste / Subdomains
Quelle der Wahrheit ist `forwarding.conf`. Aus jeder Zeile generiert
`connect-proxies.sh` einen nginx-vHost. Gruppiert nach Upstream-Ziel:
### Portal selbst
| Subdomain | Upstream | Bemerkung |
|---|---|---|
| `server.schooltech.ch` | `local` | liefert die Portal-Seite aus `public/` |
### Lokale Container (auf dem Server / „RP5“)
| Subdomain | Upstream | WebSocket |
|---|---|---|
| `rp5Guac.server.schooltech.ch` | `http://appServer_guacamole:8080` | ✓ |
| `rp5Portainer.server.schooltech.ch` | `http://portainer:9000` | ✓ |
### Gerät im LAN (direkte IP)
| Subdomain | Upstream | WebSocket |
|---|---|---|
| `nextcloud.server.schooltech.ch` | `http://192.168.0.210:9183` | ✓ |
### Über `appServer_TunnelHead` (SSH-Tunnel-Hub)
Entfernte Geräte bauen einen SSH-Reverse-Tunnel zum `appServer_TunnelHead`
auf; deren Web-Oberflächen erscheinen dort auf festen Ports.
**InformatikWeb (99xx):**
| Subdomain | Upstream | WS |
|---|---|---|
| `infPortainer.server.schooltech.ch` | `http://appServer_TunnelHead:9903` | ✓ |
| `infGuac.server.schooltech.ch` | `http://appServer_TunnelHead:9980` | ✓ |
**RP3 Raspi für die SCARA-Roboter (81xx):**
| Subdomain | Upstream | WS |
|---|---|---|
| `rp3Portainer.server.schooltech.ch` | `http://appServer_TunnelHead:8100` | ✓ |
| `rp3Guac.server.schooltech.ch` | `http://appServer_TunnelHead:8180` | ✓ |
| `fluidncRed.server.schooltech.ch` | `http://appServer_TunnelHead:8120` | ✓ |
| `fluidncWhite.server.schooltech.ch` | `https://appServer_TunnelHead:8104` | ✓ |
**ThinkCentre MiniPC neben einem Roboter (97xx):**
| Subdomain | Upstream | WS | Auth |
|---|---|---|---|
| `tcGuac.server.schooltech.ch` | `http://appServer_TunnelHead:9780` | | |
| `tcPortainer.server.schooltech.ch` | `http://appServer_TunnelHead:9703` | ✓ | |
| `tcSimulation.server.schooltech.ch` | `https://appServer_TunnelHead:9712` | ✓ | |
| `tcControl.server.schooltech.ch` | `https://appServer_TunnelHead:9710` | ✓ | **✓** |
| `robotHoming.server.schooltech.ch` | `https://appServer_TunnelHead:9793` | ✓ | |
| `robotVideo.server.schooltech.ch` | `https://appServer_TunnelHead:9743` | ✓ | **✓** |
| `robotBase.server.schooltech.ch` | `https://appServer_TunnelHead:9725` | ✓ | |
| `robotEllbow.server.schooltech.ch` | `https://appServer_TunnelHead:9726` | ✓ | |
| `robotHand.server.schooltech.ch` | `https://appServer_TunnelHead:9727` | ✓ | |
| `robotDriver.server.schooltech.ch` | `https://appServer_TunnelHead:9798` | ✓ | **✓** |
| `robotVSCode.server.schooltech.ch` | `http://appServer_TunnelHead:9744` | ✓ | |
> Die Port-Konvention am Tunnel-Hub: **81xx → RP3**, **97xx → ThinkCentre**,
> **99xx → InformatikWeb**.
---
## `forwarding.conf` das Herzstück
Eine Zeile pro Subdomain. Spalten (durch Whitespace getrennt):
```
server_name upstream_url http_behavior websockets verify_upstream_tls [cert_domain] [listen_port] [auth_required]
```
| # | Spalte | Werte | Default | Bedeutung |
|---|---|---|---|---|
| 1 | `server_name` | FQDN | | die Subdomain, z. B. `tcGuac.server.schooltech.ch` |
| 2 | `upstream_url` | `http(s)://host:port` oder `local` | | Ziel; `local` = Portal-Seite statisch ausliefern |
| 3 | `http_behavior` | `redirect` / sonst | `redirect` | `redirect` erzeugt zusätzlich einen 80→443-Redirect-vHost |
| 4 | `websockets` | `true` / `false` | `false` | bei `true` werden `Upgrade`/`Connection`-Header durchgereicht |
| 5 | `verify_upstream_tls` | `true` / `false` | `false` | nur bei `https`-Upstream: Zertifikat des Upstreams prüfen |
| 6 | `cert_domain` | Ordnername | = `server_name` | Let's-Encrypt-Verzeichnis unter `/etc/letsencrypt/live/<cert_domain>/` |
| 7 | `listen_port` | Port | `443` | abweichender HTTPS-Listen-Port |
| 8 | `auth_required` | `true` / `false` | `false` | bei `true` schützt nginx den vHost via `auth_request` |
Zeilen, die mit `#` beginnen, sowie Leerzeilen werden ignoriert.
---
## `connect-proxies.sh` Konfig-Generator
Läuft **automatisch beim Containerstart** des nginx-Containers (liegt als
`/docker-entrypoint.d/40-connect-proxies.sh`). Pro `forwarding.conf`-Zeile:
1. **Globale Map + Resolver** schreiben (`_globals.generated.conf`) nötig für
WebSockets und für die DNS-Auflösung der Upstreams zur Laufzeit.
2. **Alte generierte Configs** löschen (`*-https.generated.conf`,
`*-http-redirect.generated.conf`).
3. Pro Dienst eine vHost-Datei erzeugen mit Fallunterscheidung:
- **`local`** → statischer Server, der `public/` ausliefert (+ `/api/`-Proxy
zum Auth-Service).
- **Reverse-Proxy** → `proxy_pass` auf den Upstream; optional WebSocket-Header,
optional `proxy_ssl_verify`, optional `auth_request`-Schutz.
- **DNS nicht auflösbar** → statt Proxy ein **503-Platzhalter** („Service
nicht erreichbar“), damit nginx trotzdem startet.
- **Kein Zertifikat vorhanden** → vHost wird **übersprungen** (keine 443-Conf).
- Bei `http_behavior = redirect` zusätzlich ein **80→443-Redirect** (mit
Ausnahme für die ACME-Challenge).
4. Abschließend **`nginx -t`** zur Syntaxprüfung.
So ist das System robust: fehlt ein Zertifikat oder ein Backend, fällt nur der
betroffene Dienst aus nginx selbst läuft weiter.
> **Aktiver Pfad vs. Referenz:** Die vHosts werden zur Laufzeit von
> `connect-proxies.sh` aus `forwarding.conf` erzeugt. Die statischen Dateien in
> `nginxPages/` sind **nicht** in `docker-compose.yaml` gemountet und dienen nur
> als Referenz/Vorlage (z. B. `10-server-schooltech.conf` zeigt eine Variante
> der Portal-vHost-Konfig).
---
## Authentifizierung
Der **Auth-Service** (`auth/auth.js`, Express) verwaltet Login und Sessions:
- **`POST /api/login`** prüft User/Passwort gegen `auth/users.json`
(bcrypt-Hashes) und setzt bei Erfolg ein Cookie **`SESSIONID`**.
- **`POST /api/logout`** löscht die Session und das Cookie.
- **`GET /api/status`** sagt dem Frontend, ob eine Session aktiv ist.
- **`GET /internal/auth`** Endpoint für nginx `auth_request`: liefert `200`
bei gültiger Session, sonst `401`.
- **`POST /api/event`** einfaches Logging der Button-Klicks.
Das Cookie wird auf **`domain: .server.schooltech.ch`** gesetzt und gilt damit
für **alle Subdomains**. Dienste mit `auth_required = true` (z. B. `tcControl`,
`robotVideo`, `robotDriver`) lässt nginx nur durch, wenn `auth_request` gegen
`/internal/auth` ein `200` zurückgibt sonst landet der Nutzer nicht auf dem
Dienst.
> Sessions liegen **in-memory** im Auth-Service ein Neustart des Containers
> meldet alle Nutzer ab.
>
> Benutzer/Passwörter pflegt man in `auth/users.json`; einen neuen
> bcrypt-Hash erzeugt `auth/cretePassword.js`.
---
## TLS / Let's Encrypt
- Zertifikate liegen unter `letsencrypt/conf/live/<domain>/` und werden in den
nginx-Container gemountet (`/etc/letsencrypt`).
- **`letsEncrypt.sh`** holt/erneuert per `certbot certonly --webroot` ein
Zertifikat pro Domain (über den `appServer_LetsEncryptFetcher`-Container,
HTTP-01-Challenge unter `/.well-known/acme-challenge/`).
- **`letsEncrypt_crontab.txt`** enthält den Cron-Eintrag für die automatische
Erneuerung.
---
## Container & Betrieb
Alles wird per `docker-compose.yaml` orchestriert. Wichtige Host-Pfade (auf dem
Server unter `/home/chk/Documents/appServerPortalUI/`):
| Host-Pfad | Mount im Container | Zweck |
|---|---|---|
| `nginx.conf` | `…/conf.d/default.conf` | Basis-vHost (Port 80, ACME, `/api/`, SPA) |
| `public/` | `…/nginx/html` | die Portal-Seite |
| `forwarding.conf` | `/etc/nginx/forwarding.conf` | Dienst-Definitionen |
| `connect-proxies.sh` | `/docker-entrypoint.d/40-…` | vHost-Generator |
| `letsencrypt/conf` | `/etc/letsencrypt` | Zertifikate |
| `letsencrypt/www` | `/var/www/certbot` | ACME-Webroot |
| `auth/` | `/usr/src/app` (Auth-Container) | Auth-Service-Code |
**Start / Neuladen:**
```bash
# Alles starten
docker compose up -d
# nginx-vHosts neu generieren (nach Änderung an forwarding.conf):
docker restart appServer_PortalUI
# Logs des Generators ansehen
docker logs appServer_PortalUI | grep connect-proxies
```
---
## Neuen Dienst hinzufügen
1. **Zeile in `forwarding.conf`** ergänzen, z. B.:
```
meinDienst.server.schooltech.ch http://appServer_TunnelHead:9999 redirect true false
```
2. **DNS:** sicherstellen, dass die Subdomain auf den Server zeigt
(Wildcard `*.server.schooltech.ch` oder A-Record).
3. **Zertifikat** holen Domain in `letsEncrypt.sh` aufnehmen und Skript laufen
lassen (sonst überspringt `connect-proxies.sh` den vHost).
4. **nginx neu starten:** `docker restart appServer_PortalUI`.
5. **Optional im Portal sichtbar machen:** Eintrag im `services`-Array in
`public/app.js` ergänzen:
```js
{ id: "mein", name: "Mein Dienst", url: "https://meinDienst.server.schooltech.ch/" }
```
6. **Optional Login erzwingen:** in `forwarding.conf` als 8. Spalte
`… server.schooltech.ch 443 true` setzen.
---
## Dateiübersicht
```
appServerPortalUI/
├── docker-compose.yaml # Orchestrierung aller Container
├── nginx.conf # Basis-vHost (Port 80, ACME, /api/, SPA)
├── forwarding.conf # ← Dienst-Definitionen (Quelle der Wahrheit)
├── connect-proxies.sh # generiert die nginx-vHosts beim Start
├── letsEncrypt.sh # Zertifikate holen/erneuern
├── letsEncrypt_crontab.txt # Cron-Eintrag für Auto-Renewal
├── public/ # die Portal-Seite (server.schooltech.ch)
│ ├── index.html # Grundgerüst + Navigationsleiste
│ ├── app.js # Logik: Login, Dienst-Buttons, iFrame
│ └── style.css # Styling
├── auth/ # Auth-Service (Node/Express)
│ ├── auth.js # Login/Logout/Status/internal-auth
│ ├── users.json # Benutzer + bcrypt-Hashes
│ ├── cretePassword.js # Hilfsskript: neuen Hash erzeugen
│ └── package.json
├── letsencrypt/ # Zertifikate + ACME-Webroot
├── certs/ # (selbstsignierte) Zertifikate
├── nginxPages/ # Referenz-vHosts (NICHT gemountet)
└── doc/ # Doku, Diagramme & Notizen
├── Architektur.svg # Architektur-Diagramm (Quelle für PDF/PNG)
├── Portal.svg / Portal.pdf # Ansicht der Portal-Seite
└── AI_Gen.* , *_q?_*.txt # weitere Notizen / generierte Doku
```
> `forwarding_running_6_6_2026.conf` ist ein Snapshot der aktuell laufenden
> Konfiguration und wird hier bewusst (noch) nicht behandelt.

97
doc/Architektur.svg Normal file
View 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

Binary file not shown.

194
doc/Portal.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 88 KiB

View File

@@ -0,0 +1,51 @@
# server_name upstream_url ttp_behavior websockets verify_upstream_tls [cert_domain] listen port [Auth_Required]
#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
#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
ai.server.schooltech.ch http://open-webui:8080 redirect true false
whisper.server.schooltech.ch http://appServer_TunnelHead:11435 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 true false
tcSimulation.server.schooltech.ch https://appServer_TunnelHead:9712 redirect true false
#tcVideocontroller.server.schooltech.ch https://tcvideo:9443 redirect true false
robotHoming.server.schooltech.ch https://appServer_TunnelHead:9793 redirect true fals
#robotVideo.server.schooltech.ch https://appServer_TunnelHead:9743 redirect true false robotVideo.server.schooltech.ch 443 true
robotVideo.server.schooltech.ch http://thinkcentre.local:8444 redirect true false
tcControl.server.schooltech.ch https://appServer_TunnelHead:9710 redirect true false tcControl.server.schooltech.ch 443 true
nextcloud.server.schooltech.ch http://192.168.0.210:9183 redirect true false
robotBase.server.schooltech.ch https://appServer_TunnelHead:9725 redirect true false
robotEllbow.server.schooltech.ch https://appServer_TunnelHead:9726 redirect true false
robotHand.server.schooltech.ch https://appServer_TunnelHead:9727 redirect true false
robotDriver.server.schooltech.ch https://appServer_TunnelHead:9798 redirect true false robotDriver.server.schooltech.ch 443 true
robotVSCode.server.schooltech.ch http://appServer_TunnelHead:9744 redirect true false
overleaf.server.schooltech.ch http://thinkcentre.local:7070 redirect true false overleaf.server.schooltech.ch 443 true
# Gian Forrer hat einen MineCraft auf dem InformatikWeb
mineCraft24454.server.schooltech.ch https://appServer_TunnelHead:24454 redirect true false
mineCraft25565.server.schooltech.ch https://appServer_TunnelHead:25565 redirect true 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