Wieder anfassen nach Monaten
This commit is contained in:
11
.claude/launch.json
Normal file
11
.claude/launch.json
Normal 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
352
README.md
Normal 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**:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
> 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:**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*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
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 |
51
forwarding_running_6_6_2026.conf
Normal file
51
forwarding_running_6_6_2026.conf
Normal 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
|
||||||
Reference in New Issue
Block a user