# MijnBesluit uitrollen op een Windows-server met IIS Uitgangspunt: `mijnbesluit.nl` wijst al naar de server en daar draait IIS. > **Dit is geen webservice zoals in WinDev.** IIS doet hier alleen de voordeur: hij neemt het > verzoek aan en geeft het door aan het proces van de applicatie. Er zit geen SOAP, geen extra > serialisatie en geen tweede laag tussen. De website praat vanuit dat proces rechtstreeks met > MariaDB. ## 1. Wat er op de server moet staan **MariaDB 10.6 of nieuwer.** Haal de MSI van mariadb.org, installeer met een sterk root-wachtwoord en laat "Enable networking" uit staan als de website op dezelfde server draait — dan is de database alleen via localhost te bereiken en kan niemand er van buitenaf bij. Staat de database op een andere machine, zet `bind-address` dan op het interne adres en open poort 3306 alleen voor de webserver. **Het .NET 8 Hosting Bundle** — dus niet de SDK. Zoek op "ASP.NET Core Hosting Bundle" en neem de 8.0-versie. Die installeert de runtime én de IIS-module die het verzoek doorgeeft. Daarna: ``` net stop was /y net start w3svc ``` **WebSocket Protocol in IIS.** Dit wordt vaak vergeten en dan werkt de site half: pagina's laden wel, maar knoppen doen niets. Blazor Server houdt namelijk een websocket open. Aanzetten via Serverbeheer → Rollen en onderdelen toevoegen → Webserver (IIS) → Webserver → Toepassingsontwikkeling → **WebSocket Protocol**. ## 2. Mappen op de server Zet de gegevens **buiten** de map waar je publiceert. Alles in de publicatiemap wordt bij een volgende publicatie overschreven, en juist deze drie dingen mag je nooit kwijtraken: ``` D:\MijnBesluitData\ keys\masterkey.json de sleutel van alle kluizen dataprotection\ cookiesleutels van ASP.NET kluisbestanden\ versleutelde bijlagen appsettings.secrets.json connectiereeks en e-mailinstellingen D:\Sites\mijnbesluit\ hier komt de gepubliceerde applicatie ``` ## 3. Database inrichten ### 3a. MariaDB installeren (als hij er nog niet staat) Haal de Windows-MSI van — neem de "Stable"-versie, 64 bits. Tijdens het installeren zijn er drie keuzes die er echt toe doen: **Root-wachtwoord.** Bedenk een lang, willekeurig wachtwoord en zet het in je wachtwoordbeheer. Je hebt het hierna twee keer nodig en daarna bijna nooit meer. **"Enable networking".** Draait de website op dezelfde server, **haal dit vinkje dan weg**, of zet daaronder het adres op `127.0.0.1`. De database is dan alleen vanaf de server zelf te bereiken en er is van buitenaf simpelweg geen deur om op te kloppen. Dat scheelt je de hele categorie problemen van een database die per ongeluk aan het internet hangt. **"Use UTF8 as default server's character set".** Aanvinken. Het schema zet utf8mb4 overal zelf al, maar dit voorkomt verrassingen bij alles wat je er later handmatig bij maakt. De installatie zet een Windows-service neer die `MariaDB` heet en automatisch meestart. Controleer dat even in `services.msc`. Installeer op de server meteen ook **HeidiSQL** (zit bij de MariaDB-installatie, of los te downloaden). Dat werkt prettiger dan de opdrachtregel, en omdat je toch op de server bent ingelogd hoef je de database niet open te zetten. ### 3b. Database en gebruiker aanmaken Kopieer de map `db` uit het project naar de server, bijvoorbeeld naar `D:\db`. Pas eerst in `02_applicatiegebruiker.sql` de regel met `VERVANG_DIT_WACHTWOORD` aan. Kies een ander wachtwoord dan op je ontwikkelmachine: raakt die ene uit, dan staat de andere er niet mee open. **Met HeidiSQL op de server:** verbinden als `root` op `127.0.0.1`, dan *Bestand → SQL-bestand uitvoeren* (Ctrl+Shift+O), eerst `01_schema.sql`, daarna `02_applicatiegebruiker.sql`. **Of vanaf de opdrachtregel**, in een **cmd**-venster (niet PowerShell, die kent `<` niet): ``` "C:\Program Files\MariaDB 11.4\bin\mysql.exe" -u root -p < D:\db\01_schema.sql "C:\Program Files\MariaDB 11.4\bin\mysql.exe" -u root -p < D:\db\02_applicatiegebruiker.sql ``` Het versienummer in dat pad verschilt per installatie; kijk even in `C:\Program Files`. Beide scripts mogen meerdere keren gedraaid worden; bestaande tabellen en rechten blijven staan. ### 3c. Controleren ```sql SHOW DATABASES; USE mijnbesluit; SHOW TABLES; -- hoort 14 tabellen te geven SHOW GRANTS FOR 'mijnbesluit_app'@'localhost'; ``` Bij de rechten hoort `audit_log` alleen SELECT en INSERT te hebben, de andere tabellen ook UPDATE en DELETE, en nergens CREATE, ALTER of DROP. Zo kan de applicatie het logboek wel vullen maar niet opschonen, en kan een fout in de code nooit een tabel weggooien. Test daarna of het applicatieaccount echt werkt, want daar loopt het straks op vast: ``` "C:\Program Files\MariaDB 11.4\bin\mysql.exe" -u mijnbesluit_app -p -D mijnbesluit -e "SELECT COUNT(*) FROM users;" ``` Komt daar `0` uit, dan klopt zowel het wachtwoord als de rechten. Dat wachtwoord zet je straks in `appsettings.secrets.json` (stap 6). ### 3d. Vanaf je eigen pc meekijken Zet MariaDB daarvoor **niet** open naar buiten. Twee betere manieren: werk in HeidiSQL op de server zelf via de externe-bureaubladverbinding, of maak in HeidiSQL op je eigen pc een verbinding van het type *SSH-tunnel* als er een SSH-server op de machine draait. Een database die rechtstreeks aan het internet hangt wordt binnen een dag gevonden en geprobeerd. ## 4. Publiceren — in het vervolg met twee opdrachten > Liever met vinkjes en een knop dan met twee PowerShell-vensters? Zie > [Uitrollen met een venster](Uitrollen-met-het-venster.md). Die twee programma's roepen dezelfde > scripts aan als hieronder staan; de scripts blijven dus de waarheid. Net als de "publish site" uit WinDev, maar dan in twee stappen omdat de server niet bij jouw schijven kan. **Op je eigen pc:** ```powershell cd D:\MijnBesluit VS\MijnBesluit .\tools\Publiceren.ps1 ``` Dat bouwt de Release-versie, gooit `appsettings.Development.json` eruit (die hoort niet op een server) en maakt er `D:\MijnBesluit VS\MijnBesluitpublish\mijnbesluit.zip` van. **Op de server**, in een PowerShell-venster als administrator: ```powershell .\Uitrollen.ps1 -Zip \\tsclient\D\publish\mijnbesluit.zip ``` Dat script: * haalt het pad en de toepassingsgroep uit IIS, dus je hoeft niets in te typen; * waarschuwt als de toepassingsgroep niet op "Geen beheerde code" staat; * pakt eerst helemaal uit en zet de site pas daarna een paar seconden stil; * bewaart de vorige versie in `_vorige-versies` naast de sitemap; * zet bij een fout de vorige versie automatisch terug; * controleert daarna of `https://mijnbesluit.nl/inloggen` antwoordt — het leidt dat adres af uit de bindingen van de site; * ruimt alles op behalve de laatste vijf versies. Kopieer `Uitrollen.ps1` één keer naar de server, bijvoorbeeld naar `C:\Beheer`. Werkt de stationsdoorverwijzing van je externe-bureaubladverbinding niet, kopieer dan eerst het zip-bestand naar de server en geef dat pad op: ```powershell .\Uitrollen.ps1 -Zip D:\mijnbesluit.zip ``` Terugvallen op de vorige versie kan altijd; het script laat de precieze opdrachten zien als de controle mislukt. ### Handmatig publiceren Liever zelf, of eenmalig: ## 4b. Handmatig publiceren Op je ontwikkelmachine: ``` dotnet publish src\MijnBesluit.Web -c Release -o 'D:\MijnBesluit VS\MijnBesluitpublish\mijnbesluit' ``` Kopieer de inhoud van `D:\MijnBesluit VS\MijnBesluitpublish\mijnbesluit` naar `D:\Sites\mijnbesluit` op de server. In Visual Studio kan het ook met Publiceren → Map, of met Web Deploy als dat op de server aanstaat. `dotnet publish` maakt zelf een `web.config` aan met de juiste verwijzing naar de IIS-module; daar hoef je niets aan te doen. ## 5. De site in IIS **Toepassingsgroep** aanmaken, naam `MijnBesluit`: * .NET CLR-versie: **Geen beheerde code**. Dat lijkt vreemd, maar klopt: de applicatie draait in een eigen proces en IIS hoeft er geen .NET voor te laden. * Identiteit: `ApplicationPoolIdentity` (heet dan `IIS AppPool\MijnBesluit`). * Bij Geavanceerde instellingen: **Leegloop-time-out op 0** en **Vaste interval voor recyclen op 0**. Anders wordt de applicatie na twintig minuten zonder bezoek afgesloten en duurt het eerste bezoek daarna onnodig lang. **Website** aanmaken: * Fysiek pad: `D:\Sites\mijnbesluit` * Toepassingsgroep: `MijnBesluit` * Binding: https op poort 443, hostnaam `mijnbesluit.nl`, met je certificaat. Doe hetzelfde voor `www.mijnbesluit.nl` als je die gebruikt. * Een binding op poort 80 mag, maar laat die alleen doorsturen naar https — de applicatie doet dat zelf ook. **Rechten.** Geef `IIS AppPool\MijnBesluit`: * Lezen en uitvoeren op `D:\Sites\mijnbesluit` * **Wijzigen** op `D:\MijnBesluitData` (hij moet in `dataprotection` en `kluisbestanden` kunnen schrijven, en `keys\masterkey.json` kunnen lezen) ## 6. Instellingen op de server In `appsettings.Production.json` (die is meegepubliceerd) staan alleen paden en de site-URL. De geheimen komen uit het bestand waar `ExtraConfigFile` naar wijst. Maak `D:\MijnBesluitData\appsettings.secrets.json`: ```json { "ConnectionStrings": { "MijnBesluit": "Server=localhost;Port=3306;Database=mijnbesluit;User Id=mijnbesluit_app;Password=HET-WACHTWOORD;" }, "Smtp": { "Host": "smtp.solcon.nl", "Port": 587, "UseStartTls": true, "Username": "flietstra03", "Password": "HET-MAILWACHTWOORD", "FromAddress": "jonathan@flietstra.nl", "FromName": "MijnBesluit", "WriteToDiskInsteadOfSending": false } } ``` Let op het afzenderadres. Een mailserver van een provider accepteert vaak alleen een afzender die bij het account hoort: Solcon weigert `noreply@mijnbesluit.nl` met *"Verification failed"*. Gebruik daarom voorlopig je eigen adres. Zodra mijnbesluit.nl een eigen mailbox of verzenddienst heeft, zet je `Host`, `Username`, `Password` en `FromAddress` daarop om — dat is ook nodig voor SPF en DKIM, anders komen uitnodigingen bij Gmail en Outlook in de spam. Zo test je een afzenderadres los van de applicatie, op de server zelf: ```powershell $cred = Get-Credential Send-MailMessage -SmtpServer smtp.solcon.nl -Port 587 -UseSsl -Credential $cred ` -From "jonathan@flietstra.nl" -To "jonathan@flietstra.nl" -Subject "test" -Body "test" ``` Let op: **geen** `RedirectAllTo` hier. Die hoort alleen op je testmachine; op de server moet post gewoon naar de echte ontvanger. Geef dit bestand alleen leesrecht aan `IIS AppPool\MijnBesluit` en aan beheerders. ## 7. Het sleutelbestand aanmaken Eenmalig, op de server, in een opdrachtvenster: ``` cd D:\Sites\mijnbesluit MijnBesluit.Web.exe --maak-sleutel D:\MijnBesluitData\keys\masterkey.json ``` Hij weigert een bestaand bestand te overschrijven — met opzet, want dat zou elke bestaande kluis in één klap onleesbaar maken. **Maak hier meteen een back-up van, op een andere plek dan je database-back-up.** Zonder dit bestand is niets meer te openen; mét dit bestand én een databasekopie is alles leesbaar. Die twee horen dus niet bij elkaar te liggen. ## 8. Controleren Ga naar `https://mijnbesluit.nl/inloggen`. Werkt dat, controleer dan ook: * Vraag een code aan. Komt de e-mail aan, dan klopt de SMTP-instelling. * Log in en klik door naar Kluis. Werken de knoppen, dan doet de websocket het. * Herstart de toepassingsgroep in IIS en ververs de pagina. Blijf je ingelogd, dan staat de sleutelring van ASP.NET goed. Word je uitgelogd, kijk dan naar `Security:DataProtectionPath` en de schrijfrechten op die map. ## 9. Back-up Drie dingen, en het liefst niet op dezelfde plek: 1. De database. Bijvoorbeeld dagelijks met een taak: `mysqldump -u root -p --single-transaction mijnbesluit > D:\Backup\mijnbesluit-%DATE%.sql` 2. `D:\MijnBesluitData\kluisbestanden` — de versleutelde bijlagen. 3. `D:\MijnBesluitData\keys\masterkey.json` — **apart**, en niet in dezelfde back-upset als 1 en 2. Beproef ook eens een keer of terugzetten werkt. Een back-up die je nooit hebt teruggezet is een aanname, geen back-up. ## 10. Als er iets misgaat **Kijk eerst in de log.** De applicatie schrijft zelf naar `C:\MijnBesluitData\logs`, één bestand per dag. Daar staat de échte fout in; de bezoeker krijgt alleen een nette melding. ```powershell Get-ChildItem C:\MijnBesluitData\logs | Sort-Object LastWriteTime -Descending | Select-Object -First 1 | Get-Content -Tail 60 ``` **"We konden de e-mail met je code niet versturen"** — de SMTP-instellingen. Ontbreekt het blok `Smtp` in `appsettings.secrets.json`, dan probeert de applicatie `localhost:587` en is er niets om verbinding mee te maken. Staat het blok er wel, dan zegt de log wat de mailserver antwoordde: een time-out betekent dat de poort dichtstaat op de server, en een 5xx-antwoord dat de mailserver de server niet als afzender accepteert. **HTTP 500.19** — IIS kan `web.config` niet lezen. Meestal is het Hosting Bundle niet geïnstalleerd, of is het na IIS geïnstalleerd. Installeer het (opnieuw) en herstart IIS. **HTTP 500.30** — de applicatie start niet. Zet in `web.config` tijdelijk `stdoutLogEnabled="true"` en kijk in de map `logs`. Negen van de tien keer is het de connectiereeks of een ontbrekend sleutelbestand. **HTTP 502.5** — verkeerde runtime-versie, of het Hosting Bundle ontbreekt. **De pagina laadt maar knoppen doen niets** — WebSocket Protocol staat niet aan in IIS. Zie stap 1. **HTTP 400 bij het insturen van een formulier** — de sleutelring van ASP.NET is weg. Zie stap 8. **"Access denied for user"** — het wachtwoord in `appsettings.secrets.json` komt niet overeen met dat in de database. Test het eerst met de MariaDB-client op de server zelf. ## 11. En de SocketServer? Die heb je pas nodig zodra de Android-app er is; de website werkt er los van. Wil je hem alvast draaien: publiceer `MijnBesluit.SocketServer`, zet hem op dezelfde server neer, gebruik hetzelfde `D:\MijnBesluitData` en installeer hem als service met het beheerschermpje. Open dan poort 5200 in de firewall. Zie `docs/SocketServer-installeren.md`.