Files
MijnBesluit/docs/SocketServer-installeren.md
T

202 lines
9.0 KiB
Markdown

# De SocketServer installeren als Windows-service
`MijnBesluit.SocketServer.exe` is twee dingen tegelijk, net als je in WinDev gewend bent:
* start **Windows** hem, dan draait de server;
* start **jij** hem, dan verschijnt een beheerschermpje.
Eén exe om uit te leveren dus, en geen los installatieprogramma.
## Bouwen
```powershell
D:\MijnBesluit VS\MijnBesluit\tools\SocketServer-publiceren.ps1
```
Dat levert `D:\MijnBesluit VS\MijnBesluitpublish\mijnbesluit-socketserver.zip`. Of met de hand:
```
dotnet publish src\MijnBesluit.SocketServer -c Release -r win-x64 --self-contained false -o D:\MijnBesluit VS\MijnBesluit\publish
```
Het project staat vast op `x64`; er komt dus geen 32-bits versie uit, ook niet op een machine
waar dat de standaard zou zijn. Wil je dat er op de doelmachine helemaal geen .NET geïnstalleerd
hoeft te zijn, gebruik dan `--self-contained true`; de map wordt dan een stuk groter maar draait
op zichzelf.
## Op mijnbesluit.nl, naast de website
De SocketServer draait náást IIS op dezelfde machine en praat met dezelfde database. Drie dingen
moeten daarbij kloppen, anders krijgt de app straks een lege of onleesbare kluis te zien.
**1. Dezelfde geheimen.** `appsettings.json` wijst met `ExtraConfigFile` naar
`C:\MijnBesluitData\appsettings.secrets.json` — precies het bestand dat de website al gebruikt.
Eén plek dus voor de databasegegevens en de SMTP-instellingen, en ze overleven een nieuwe
publicatie. Ontbreekt dat bestand, dan stopt de service meteen met een duidelijke melding in
plaats van een onbegrijpelijke "Access denied" van de database.
**2. Hetzelfde sleutelbestand.** `Security:MasterKeyFile` staat op
`C:\MijnBesluitData\keys\masterkey.json`, hetzelfde bestand als de website gebruikt. Met een
ánder sleutelbestand is geen enkel kluisitem te openen dat via de website is opgeslagen. Ook
`Storage:Path` moet dezelfde map zijn.
Het serviceaccount **LocalSystem** moet dat sleutelbestand kunnen lezen.
**3. Een certificaat.** Zonder certificaat luistert de server onversleuteld, en daar gaan
wachtwoorden en pincodes overheen. Het certificaat van mijnbesluit.nl uit IIS is te hergebruiken:
De snelste weg is IIS vragen welk certificaat hij voor de site gebruikt:
```powershell
Import-Module WebAdministration
Get-ChildItem IIS:\SslBindings | Select-Object IPAddress, Port, Host, Thumbprint, Store
```
Staat er geen regel met `mijnbesluit.nl`, dan gebruikt die site de regel met IP-adres
`0.0.0.0` — dat is het certificaat voor alle sites zonder eigen regel.
Om te zien welke namen zo'n certificaat dekt:
```powershell
Get-ChildItem Cert:\LocalMachine\WebHosting |
Select-Object Subject, @{n='Namen';e={$_.DnsNameList -join ', '}}, Thumbprint, NotAfter, HasPrivateKey
```
Zet de vingerafdruk in `Socket:CertificateThumbprint`. De server kijkt in de opslagen **My** én
**WebHosting**; die laatste is waar hostingpanelen en Let's Encrypt-tools hun certificaten
neerzetten. Spaties maken niet uit. Let op de
vervaldatum: wordt het certificaat vernieuwd, dan verandert de vingerafdruk en moet deze
instelling mee — een herstart van de service is dan nodig.
**Poort 5200 open.** In de firewall van de server:
```powershell
New-NetFirewallRule -DisplayName "MijnBesluit SocketServer" -Direction Inbound `
-Protocol TCP -LocalPort 5200 -Action Allow
```
En bij je hoster, als daar nog een firewall voor staat. Controleer het daarna vanaf je eigen pc
met `Test-NetConnection mijnbesluit.nl -Port 5200`.
## Installeren
1. Klik met de **rechtermuisknop** op `MijnBesluit.SocketServer.exe` en kies
**Als administrator uitvoeren**.
2. Het schermpje verschijnt met de status *Niet geïnstalleerd*.
3. Vul het poortnummer in (standaard 5200). Onder het veld zie je meteen of die poort vrij is.
4. Klik op **Install**.
Wat er dan gebeurt:
* het poortnummer wordt in `appsettings.json` naast de exe gezet — dat bestand leest de service
straks zelf;
* de service wordt aangemaakt onder de naam `MijnBesluitSocket`, met weergavenaam
*MijnBesluit SocketServer*, opstarttype **Automatisch** en account **LocalSystem**;
* Windows krijgt de opdracht de service zelf opnieuw te starten als hij onverwacht stopt: na
10 seconden, dan na 30, en daarna elke 2 minuten;
* de service wordt meteen gestart.
## Verwijderen
Rechtermuisknop → **Als administrator uitvoeren**. Het schermpje laat nu *Draait* of
*Geïnstalleerd, gestopt* zien, en de knop heet **UnInstall**. Er wordt eerst gevraagd of je het
zeker weet; daarna wordt de service gestopt en verwijderd.
Zolang de service bestaat kun je de poort niet wijzigen — dat staat er ook bij. Wil je een andere
poort: verwijderen, poort aanpassen, opnieuw installeren.
## Zonder beheerdersrechten
Start je de exe gewoon met een dubbelklik, dan verschijnt hetzelfde schermpje, maar zijn de
knoppen uitgeschakeld en staat er een knop **Opnieuw starten als administrator**. Die start
dezelfde exe opnieuw met de UAC-vraag. Klik je die weg, dan gebeurt er niets.
Het programma vraagt dus niet uit zichzelf om rechten bij het starten: alleen als je iets wilt
doen waarvoor ze nodig zijn.
## Waar de service zijn spullen zoekt
Windows start een service met `C:\Windows\System32` als werkmap. Alle relatieve paden in
`appsettings.json` worden daarom omgezet naar paden **naast de exe**:
| Instelling | Standaard | Wordt |
|---|---|---|
| `Security:MasterKeyFile` | `keys/masterkey.json` | `<publish-map>\keys\masterkey.json` |
| `Storage:Path` | `kluisbestanden` | `<publish-map>\kluisbestanden` |
| `Smtp:DropFolder` | `mail-uitgaand` | `<publish-map>\mail-uitgaand` |
Zet je er een volledig pad neer (`D:\MijnBesluit VS\MijnBesluit\keys\masterkey.json`), dan wordt dat gebruikt
zoals het er staat. Voor het sleutelbestand is dat vaak handiger: dan overleeft het een nieuwe
publish.
Het serviceaccount **LocalSystem** moet het sleutelbestand kunnen lezen en in de map met
kluisbestanden kunnen schrijven.
## Hoe de server met verbindingen omgaat
* Eén thread accepteert verbindingen en doet verder niets.
* Elke verbinding krijgt zijn **eigen thread**, met een blokkerende leeslus. Die thread heet
`MijnBesluit-verbinding-<nummer>`, dus in een dump of in de debugger zie je meteen welke
verbinding waar hangt.
* Elk binnengekomen verzoek krijgt een **eigen thread**. De leeslus gaat meteen door met lezen,
dus een trage databaseactie houdt de volgende verzoeken van diezelfde app niet op. Per
verbinding lopen er maximaal `Socket:MaxVerzoekenPerVerbinding` tegelijk (standaard 8) en over
de hele server `Socket:MaxVerzoekenTotaal` (standaard 200); daarboven wordt er van die
verbinding even niet gelezen, wat vanzelf tegendruk geeft.
Bewust een echte thread en niet de threadpool: de afhandeling blokkeert op de database en op
bestanden, en dat zou de pool uithongeren — die groeit maar langzaam, waardoor verzoeken
alsnog achter elkaar zouden gaan lopen in plaats van naast elkaar. Een verzoekthread krijgt
512 kB stapelruimte (`Socket` hoeft daar niets voor te weten; zie `RequestThreadStackBytes`).
* Het wegschrijven van antwoorden is geserialiseerd: er kunnen nooit twee antwoorden door elkaar
over de lijn gaan. Antwoorden kunnen wél in een andere volgorde terugkomen dan de verzoeken
binnenkwamen — de app koppelt ze op `requestId`.
* Bij afsluiten wacht de server tot de lopende verzoeken van een verbinding klaar zijn, zodat er
niet naar een al opgeruimde verbinding wordt geschreven.
Omdat elk verzoek op zijn eigen thread loopt, krijgt het ook zijn eigen databasecontext: een
EF Core-context is niet bedoeld om door meerdere threads tegelijk gebruikt te worden.
## Instellingen
```json
"Socket": {
"Address": "0.0.0.0",
"Port": 5200,
"MaxVerbindingen": 500,
"MaxVerzoekenPerVerbinding": 8,
"MaxVerzoekenTotaal": 200,
"CertificateFile": "D:\\MijnBesluit\\certificaat.pfx",
"CertificatePassword": "...",
"CertificateThumbprint": ""
}
```
`CertificateThumbprint` pakt een certificaat uit de machine-certificaatopslag van Windows —
handig als je er al een hebt dat automatisch vernieuwd wordt. Spaties in de vingerafdruk maken
niet uit; die worden eruit gehaald.
## Als er iets misgaat
* **Je wilt meekijken terwijl hij draait.** Stop de service en start dezelfde exe zelf met
`--service`:
```
net stop MijnBesluitSocket
cd C:\MijnBesluitSocket
MijnBesluit.SocketServer.exe --service
```
Er verschijnt dan een venster met het logboek erin; daar komt ook langs waarom een verzoek
misging (`Fout bij '<actie>': …`). Afsluiten met Ctrl+C, en daarna `net start MijnBesluitSocket`
niet vergeten. Liever een bestand? Zet er `> log.txt 2>&1` achter; dan blijft het venster weg
en vult dat bestand zich terwijl hij draait.
* **De service start niet.** Kijk in *Logboeken → Windows-logboeken → Toepassing*. De meest
voorkomende oorzaak is dat de poort al bezet is; dat staat er dan letterlijk.
* **"De service is verwijderd maar Windows is er nog niet mee klaar."** Er staat nog een venster
*Services* open met de service geselecteerd. Sluit dat en probeer het over een halve minuut
opnieuw.
* **Het schermpje zegt dat de poort bezet is.** Met `netstat -ano | findstr :5200` zie je welk
proces erop luistert.