# 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)
- [Eine neue Seite ins Portal aufnehmen](#eine-neue-seite-ins-portal-aufnehmen)
- [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**:
> Bild: [`doc/Architektur.png`](doc/Architektur.png) · Vektor-Quelle:
> [`doc/Architektur.svg`](doc/Architektur.svg) (für PDF/beliebige Skalierung).
Dieselbe Übersicht als ASCII-Text
```text
Internet (HTTPS :443 / HTTP :80)
│
┌────────────────────────────────────────────────┐
│ nginx Reverse-Proxy (Container: appServer_PortalUI)
│ ein vHost pro Subdomain *.server.schooltech.ch
│ TLS-Terminierung (Let's Encrypt) · 80→443 Redirect
└────────────────────────────────────────────────┘
│
┌───────────────┬───────────┼──────────────┬────────────────────┐
▼ ▼ ▼ ▼
server. rp5*. nextcloud. inf*/rp3*/tc*/robot*/fluidnc*
schooltech.ch schooltech. schooltech. .server.schooltech.ch
Portal-UI lokale Gerät im LAN über SSH-Tunnel-Hub
(diese App) Container (direkte IP) appServer_TunnelHead
│ │ │ │
▼ ▼ ▼ ▼
public/ appServer_ 192.168.0.210 appServer_TunnelHead (SSH-Reverse-Tunnels)
index.html guacamole / ├─ 99xx InformatikWeb (inf*)
+ Auth-API portainer ├─ 81xx RP3/SCARA (rp3*, fluidnc*)
└─ 97xx ThinkCentre (tc*, robot*)
```
**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**
(`` mit Logo *„schooltech“*, der Service-Navigation `#services`
und einem Login/Logout-Button), darunter ein **`