202 lines
9.0 KiB
Markdown
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.
|