{"authors":[{"name":"Remco Loup"}],"description":"Een dag uit het leven van een CEO/CTO van 333travel en een gepassioneerde UI/UX- en techliefhebber. En alles waar ik verder over schrijf.","favicon":"https://www.allsubjectsmatter.nl/images/icon.png","feed_url":"https://www.allsubjectsmatter.nl/nl/feed.json","home_page_url":"https://www.allsubjectsmatter.nl/nl/","icon":"https://www.allsubjectsmatter.nl/images/icon.png","items":[{"authors":[{"name":"Remco Loup","url":"https://www.allsubjectsmatter.nl/author/remco/"}],"content_html":"\u003cp\u003eIn \u003ca href=\"https://www.allsubjectsmatter.nl/nl/can-a-conversation-be-a-travel-channel-we-built-the-architecture-to-find-out/\" data-preview\u003eDeel 1\u003c/a\u003e schreef ik waarom we een AI-reisinterface vanaf nul bouwden, wat we leerden van onze eerste mislukte poging in 2019, en waarom een plugin je nooit een volledig geïntegreerde conversationele ervaring geeft. Dit deel gaat over het hoe. De architectuur, de zoekpipeline, het geheugenprobleem dat vier pogingen kostte om op te lossen, en het UI/UX-werk dat 25 seconden aanvoelt als 5.\u003c/p\u003e\n\u003ch2 id=\"the-architecture\"\u003eDe architectuur\u003c/h2\u003e\n\u003cp\u003eOmdat dit een techblog is en geen marketingpagina, laat ik je zien wat er echt draait. Dit is geen weekendproject. Dit is productie-infrastructuur.\u003c/p\u003e\n\u003cpre class=\"line-numbers language-text\"\u003e\u003ccode class=\"language-text\"\u003eFrontend (React \u0026#43; TypeScript \u0026#43; Zustand)\n    │\n    │  WebSocket (real-time, bidirectional)\n    │\nFastAPI Backend\n    ├── Query Orchestrator\n    │     ├── LLM Client (multi-provider with automatic fallback)\n    │     ├── Tool Registry (9 specialized tools)\n    │     └── Observer System (async post-turn analysis)\n    ├── RAG Pipeline\n    │     ├── Vector Database (3 content collections)\n    │     ├── Embedding Engine\n    │     └── Reranker (dual provider, switchable)\n    ├── Conversation Memory (dual storage)\n    │     ├── SQL (message history)\n    │     └── Vector DB (semantic search over past messages)\n    ├── Security Layer\n    │     ├── Input Guard (prompt injection defense)\n    │     ├── Output Guard (credential leak prevention)\n    │     └── Rate Limiting \u0026#43; IP Management\n    ├── MCP Server (Model Context Protocol)\n    │     └── External AI assistant access to our data\n    └── Lead Management\n          ├── Proposal Generation\n          └── Specialist Handoff\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cp\u003eDe frontend is een React-applicatie met real-time WebSocket-communicatie. Geen polling. Geen request-response-cycli waarin je op een compleet antwoord wacht. Elke token streamt in real-time, gebufferd op 50ms-intervallen om flikkering in de UI te voorkomen. Productresultaten worden naar de interface gepusht zodra ze gevonden zijn, nog voordat de LLM aan zijn antwoord begint.\u003c/p\u003e\n\u003cp\u003eDe backend is Python/FastAPI met een modulair toolsysteem. De LLM genereert niet alleen tekst. Hij heeft toegang tot 9 gespecialiseerde tools en beslist zelfstandig welke hij gebruikt. Reizen zoeken? Dan roept hij \u003ccode\u003esearch_products\u003c/code\u003e aan. Informatie over een bestemming nodig? Dan roept hij \u003ccode\u003esearch_knowledge\u003c/code\u003e aan. Terugroepen wat eerder besproken is? Dan doorzoekt hij het gespreksgeheugen semantisch.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eDe LLM genereert geen antwoorden. Hij orkestreert tools en stelt een antwoord samen op basis van echte data.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eDe LLM-laag draait met een multi-provider fallback-systeem. Valt de primaire provider uit, dan schakelt het systeem automatisch over naar de secundaire. Geen downtime. Geen foutmeldingen. De klant merkt er niets van.\u003c/p\u003e\n\u003cp\u003eEr draait al een MCP-server, het protocol dat ik beschreef in mijn Web 4.0-post. Externe AI-assistenten kunnen onze reisdata er al door bevragen. Het is nog vroeg, maar de infrastructuur voor AI-to-business-communicatie staat live. Als de AI-assistent van Lisa de producten van 333travel wil doorzoeken, bestaat het endpoint al.\u003c/p\u003e\n\u003ch3 id=\"the-rag-pipeline-where-the-real-work-happens\"\u003eDe RAG-pipeline: waar het echte werk gebeurt\u003c/h3\u003e\n\u003cp\u003eRAG, Retrieval-Augmented Generation, is de ruggengraat van het hele systeem. Het is wat \u0026ldquo;GPT met een kennisbank\u0026rdquo; scheidt van \u0026ldquo;een AI die je product echt kent.\u0026rdquo; Maar slecht uitgevoerde RAG is bijna erger dan geen RAG.\u003c/p\u003e\n\u003cp\u003eWe onderhouden drie aparte vectorcollections:\u003c/p\u003e\n\u003ctable\u003e\n\t\u003cthead\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003cth\u003eCollection\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003eWat erin zit\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003eWat het doet\u003c/th\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eTravel Products\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eRoundtrips, hotels, tours, cruises\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eDrijft de productzoekopdracht aan\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eTravel Details\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eDagprogramma\u0026rsquo;s, excursies, inbegrepen services\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eBeantwoordt specifieke vragen over reisschema\u0026rsquo;s\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eTravel Knowledge\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eBlogs, bestemmingengidsen, reisinformatie\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eLevert context en inspiratie\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003eStelt een klant een vraag, dan wordt de query ge-embed en tegen de relevante collection gegooid. Maar hier komt het cruciale deel: \u003cstrong\u003ewe pakken niet gewoon de beste vector-matches en noemen dat een resultaat.\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eVector similarity alleen is middelmatig. Het brengt je in de juiste richting, maar levert niet de beste resultaten. Daarom doen we een brede fetch, aanzienlijk meer resultaten dan nodig, en halen die door een reranker. De reranker beoordeelt elk query-documentpaar op werkelijke semantische relevantie, niet alleen op embedding-nabijheid.\u003c/p\u003e\n\u003cp\u003eHet verschil is enorm. Het is het verschil tussen \u0026ldquo;hier zijn reizen die een paar van je woorden bevatten\u0026rdquo; en \u0026ldquo;hier zijn reizen die daadwerkelijk matchen met wat je zoekt.\u0026rdquo;\u003c/p\u003e\n\u003cp\u003eMaar het gaat verder. Nog voordat de query de vectordatabase raakt, gaat hij door een fuzzy normalisatielaag. Elke land- en plaatsnaam in ons systeem wordt bij het opstarten gecachet. Typt een klant \u0026ldquo;Bali\u0026rdquo;, dan weten we dat Indonesië bedoeld wordt. Schrijft iemand \u0026ldquo;Tailand\u0026rdquo;, dan weten we wat er bedoeld wordt. Aliassen, spellingvarianten, taalverschillen: allemaal opgelost voordat de zoekopdracht begint. Zonder dit krijg je lege resultaten op queries die wel hadden moeten matchen. En daarbovenop combineert de zoekopdracht semantische similarity met metadatafilters. Land, duur, producttype: dat zijn geen velden in een database. Het zijn actieve filters die samen met de vectorsearch de resultaten verfijnen, nog voordat de reranker begint.\u003c/p\u003e\n\u003cp\u003eNa het reranken splitsen de resultaten zich in twee paden. De volledige set gererankte resultaten gaat meteen naar de frontend, maar geblurd. De klant ziet productkaarten verschijnen terwijl Joy nog aan het nadenken is. Het toont voortgang zonder te overweldigen. Ondertussen krijgt de LLM een uitgedunde versie van die resultaten, alleen de velden die hij nodig heeft om te redeneren. Deze projectie bespaart ruwweg een derde van de tokens, wat snellere antwoorden en lagere kosten betekent. Daarna stelt Joy haar antwoord samen en bespreekt ze de 4 of 5 reizen die echt passen bij de vraag. Alleen die besproken producten worden zichtbaar voor de klant. De rest verdwijnt. Wat de klant ziet is een gecureerde, doordachte selectie. Geen lijst met 10 zoekresultaten. Een aanbeveling van 5 die echt logisch zijn.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eBrede fetch → Rerank → Project. Drie stappen die het verschil maken tussen een zoekresultaat en een aanbeveling.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch3 id=\"the-memory-problem\"\u003eHet geheugenprobleem\u003c/h3\u003e\n\u003cp\u003eDit is het onderdeel dat de meeste iteraties kostte om goed te krijgen.\u003c/p\u003e\n\u003cp\u003eConversationele AI heeft een smerig geheimpje: het onthoudt niets. Elke LLM-call is stateless. Het model heeft geen idee wat er eerder gezegd is, tenzij je de context expliciet aanlevert. En er is een grens aan hoeveel context je kunt aanleveren.\u003c/p\u003e\n\u003cp\u003eVoor een simpele Q\u0026amp;A-chatbot maakt dat niet uit. Iemand stelt een vraag, krijgt een antwoord, klaar.\u003c/p\u003e\n\u003cp\u003eMaar voor een reisassistent? Geheugen is alles.\u003c/p\u003e\n\u003cp\u003eEen klant zegt: \u0026ldquo;Ik wil twee weken naar Thailand.\u0026rdquo; Drie beurten later: \u0026ldquo;Maak er eigenlijk drie weken van. En ik wil Laos erbij.\u0026rdquo; Vijf beurten later: \u0026ldquo;Wat was die reis die je eerder liet zien, met de kookles?\u0026rdquo;\u003c/p\u003e\n\u003cp\u003eZonder geheugen is elke beurt een leeg blad. Het systeem weet niets van Thailand. Niets van de wijziging naar drie weken. Niets van Laos. En al helemaal niets van de reis met de kookles van zeven berichten geleden.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eZonder geheugen is elk bericht een eerste date. Je AI heeft geen idee wat er eerder gebeurd is.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eDit losten we niet in één poging op. Niet in twee. Zelfs niet in drie.\u003c/p\u003e\n\u003cp\u003eDe eerste versie was simpel: de laatste 10 vraag-antwoordparen in de LLM-context stoppen. Dat werkte voor korte gesprekken. Maar onze gesprekken zijn niet kort. Klanten verkennen, vergelijken, komen terug op eerdere ideeën, bedenken zich. Tien beurten is niet genoeg als iemand twintig minuten over drie verschillende landen heeft gechat.\u003c/p\u003e\n\u003cp\u003eDus probeerden we op de achtergrond samenvattingen te genereren. Het idee was goed: het gesprek samenvatten tot kernpunten, zodat de LLM altijd het volledige beeld heeft zonder de hele context window vol te gooien. De praktijk was anders. De LLM begon details te hallucineren in de samenvattingen. Landen door elkaar halen. Voorkeuren toedichten die de klant nooit had genoemd. De samenvatting zou de source of truth zijn, en hij verzon dingen.\u003c/p\u003e\n\u003cp\u003eToen ontwierpen we een geheugensysteem in drie lagen: kortetermijn, tijdelijk en langetermijn. De tijdelijke laag moest bijhouden welke onderwerpen bij welke bestemming hoorden, welke voorkeuren op welk deel van de reis golden. Op papier elegant. In de praktijk een nachtmerrie. Het vereiste dat de AI complexe relationele context begreep, zoals \u0026ldquo;toen ze zeiden dat het budget flexibel was, bedoelden ze dat voor Thailand, niet voor Vietnam.\u0026rdquo; Dat kon hij niet. Te veel edge cases. Te fragiel.\u003c/p\u003e\n\u003cp\u003eWat we uiteindelijk hadden, was simpeler en robuuster. Elk bericht wordt als vector geëmbed in een aparte memory collection in dezelfde database die we voor product search gebruiken. Als de klant zegt \u0026ldquo;die reis met de kookles\u0026rdquo;, draait het systeem een semantic search over alle eerdere berichten in die sessie. Het vindt het relevante bericht, ook als de exacte woorden niet overeenkomen. Misschien noemde Joy het drie beurten eerder een \u0026ldquo;culinaire ervaring\u0026rdquo;. Vector search geeft niets om woordmatching. Het matcht betekenis. Gecombineerd met de laatste 10 uitwisselingen in de prompt voor directe context, hoeft Joy niet voor elke vraag die tool call te doen. Ze heeft het recente gesprek direct bij de hand, en de volledige historie doorzoekbaar wanneer ze die nodig heeft.\u003c/p\u003e\n\u003cp\u003eVier pogingen. Elke poging leerde ons iets over wat AI wel en niet kan doen met context. De definitieve versie werkt niet omdat het de slimste opzet is, maar omdat het het eerlijkst is over de beperkingen.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eAI-geheugen is niet één probleem. Het zijn er twee: terugroepen wat er gezegd is, en begrijpen wat er bedoeld is. We hadden twee verschillende systemen nodig om dat op te lossen.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch3 id=\"uiux-the-invisible-work\"\u003eUI/UX: het onzichtbare werk\u003c/h3\u003e\n\u003cp\u003eHier sterven de meeste AI-projecten, zonder dat ze het zelf doorhebben.\u003c/p\u003e\n\u003cp\u003eJe kunt de meest geavanceerde RAG-pipeline ter wereld hebben. De slimste retrieval. De beste reranking. Maar als de interface traag, log of verwarrend aanvoelt, doet dat er allemaal niet toe. De klant geeft niets om je architectuur. Het gaat ze om hoe het voelt.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eAls je AI slim is maar je interface traag, heb je een genie gebouwd dat opgesloten zit in een kast.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eHier zijn we diep op ingegaan. En \u0026ldquo;diep\u0026rdquo; betekent tientallen iteraties op dingen die de meeste mensen details zouden noemen. Maar de details zijn het product.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eStreaming die natuurlijk voelt.\u003c/strong\u003e LLM-antwoorden streamen token voor token over een WebSocket. Maar rauwe streaming geeft flikkerende tekst die schokkerig en onaangenaam is. We bufferen met intervallen van 50ms. Snel genoeg om real-time te voelen, langzaam genoeg om constante re-renders te voorkomen. Het verschil is subtiel, maar enorm voor de ervaren kwaliteit.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eDe afweging in snelheid.\u003c/strong\u003e Laten we eerlijk zijn: een product search met volledig antwoord duurt 20 tot 30 seconden. Dat is lang. We weten dat het lang is. Maar we hebben een bewuste keuze gemaakt: kwaliteit boven snelheid. De brede fetch, de reranking, de semantic matching, elke stap in de pipeline bestaat omdat hij de resultaten beter maakt. Haal er één weg en je krijgt snellere antwoorden met slechtere aanbevelingen. Liever dat Joy 25 seconden werkt en 5 reizen aflevert die echt passen, dan 10 middelmatige matches in 8 seconden.\u003c/p\u003e\n\u003cp\u003eDus de vraag werd: hoe zorgen we dat 25 seconden niet aanvoelen als 25 seconden? Daar kwam het UX-werk. Joy laat zien wat ze doet. Producten zoeken. Bestemmingsinformatie opzoeken. Gesprekshistorie checken. Elke tool call is zichtbaar terwijl hij gebeurt. Productkaarten schuiven vervaagd naar binnen terwijl zij nog haar antwoord opbouwt. Zodra het antwoord klaar is, worden alleen de reizen zichtbaar die Joy daadwerkelijk bespreekt. De rest vervaagt. De klant staart nooit naar een leeg scherm. Ze zien het proces ontvouwen. En het blijkt dat ergens naar kijken werken totaal anders voelt dan ergens op wachten.\u003c/p\u003e\n\u003cp\u003eEen deel van de latency is ook een taalprobleem. Nederlands is slecht vertegenwoordigd in de meeste embedding- en taalmodellen. Het semantische begrip dat in het Engels prachtig werkt, degradeert als je klanten en productdata Nederlands zijn. Dat betekent meer verwerking, zorgvuldiger matchen, meer moeite voor hetzelfde kwaliteitsniveau.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eWat is GPT-NL?\u003c/strong\u003e \u003ca href=\"https://gpt-nl.nl/\" data-external\u003eGPT-NL\u003c/a\u003e is een Nederlands initiatief om een large language model te bouwen dat specifiek getraind en geoptimaliseerd is voor de Nederlandse taal.\u003c/p\u003e\n\u003cfigure class=\"bookmark\"\u003e\u003ca class=\"bookmark__link\" href=\"https://gpt-nl.nl/\" rel=\"noopener\"\u003e\u003cspan class=\"bookmark__body\"\u003e\u003cspan class=\"bookmark__title\"\u003eGPT-NL: Een verantwoord taalmodel voor Nederland\u003c/span\u003e\u003cspan class=\"bookmark__description\"\u003eIn Nederland wordt gewerkt aan de ontwikkeling van een eigen open taalmodel genaamd GPT-NL. Wij gebruiken alleen data om het model te trainen als we die rechtmatig kunnen verkrijgen en bieden zo een verantwoord alternatief op bestaande LLMs. Dit model is essentieel voor het versterken en behouden van onze digitale soevereiniteit.\u003c/span\u003e\u003cspan class=\"bookmark__meta\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"25px\"\u003e\u003csource type=\"image/webp\" sizes=\"25px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/the-technical-architecture-behind-a-conversational-travel-channel/bookmarks/01-icon_hu_6ebbb399eb63616a.webp\" alt=\"\" class=\"bookmark__icon\" width=\"50\" height=\"50\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003cspan\u003eGPT-NL\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"bookmark__thumb\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/webp\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/avif\" sizes=\"250px\"\u003e\u003csource type=\"image/webp\" sizes=\"250px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/the-technical-architecture-behind-a-conversational-travel-channel/bookmarks/01-thumb_hu_6ec85365e1fe875f.webp\" alt=\"\" width=\"500\" height=\"435\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003c/span\u003e\u003c/a\u003e\u003c/figure\u003e\n\n\u003cp\u003eWaar huidige modellen zoals GPT en Claude Nederlands behandelen als een niet-bestaande taal, wil GPT-NL Nederlands een first-class citizen maken in AI. Voor toepassingen zoals de onze, waar zowel de klant als de productdata Nederlands zijn, kan een model dat de taal echt begrijpt de kwaliteit van semantisch zoeken aanzienlijk verbeteren en de extra verwerking verminderen die we nu nodig hebben om dat gat te compenseren. We volgen dit project op de voet.\u003c/p\u003e\n\u003cp\u003eEn dan is er de keuze van het model. De kwaliteit van het gesprek hangt sterk af van welke LLM je gebruikt. We draaien op Sonnet 4.6 als primair model, en het verschil met kleinere of goedkopere modellen is niet subtiel. Het is het verschil tussen een gesprek dat natuurlijk voelt en een dat gerepeteerd aanvoelt. Haiku, de snellere en goedkopere optie, voelt als een downgrade zodra je overschakelt. GPT dient als onze automatische fallback, maar het is een stap terug in gesprekskwaliteit. Dit zijn geen inwisselbare commodity\u0026rsquo;s. Het model dat je kiest bepaalt de ervaring die je klant krijgt.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eDirecte paginaladingen.\u003c/strong\u003e De hele conversatie blijft bewaard in local storage. Als je de pagina ververst of later terugkomt, staat je gesprek er al. Direct. Geen spinner. Geen \u0026ldquo;opnieuw verbinden\u0026hellip;\u0026quot;-melding. De sync met de server gebeurt geruisloos op de achtergrond.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eMarkdown-normalisatie.\u003c/strong\u003e LLM\u0026rsquo;s hebben meningen over opmaak. Claude is gek op vette koppen met emoji. GPT is gek op genummerde lijsten. Wij normaliseren alles server-side. Vet-met-emoji wordt omgezet naar schone koppen. Horizontale lijnen worden gestript. Font weights worden teruggeschroefd. Het resultaat oogt alsof het ontworpen is, niet gegenereerd. De klant moet nooit het gevoel hebben dat hij AI-output zit te lezen.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eSessiecontinuïteit.\u003c/strong\u003e Je sessie overleeft paginaverversingen, gesloten browsers en herverbindingen. Kom een dag later terug, je gesprek staat er nog. Je opgeslagen reizen staan er nog. En als een sessie verloopt, krijg je een nette overlay die uitlegt wat er gebeurd is, geen kapotte pagina of een cryptische foutmelding.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eDe lead flow.\u003c/strong\u003e Als een klant klaar is om met een specialist te praten, genereert het systeem een reisvoorstel dat al vooraf is ingevuld met wat het uit de conversatie weet. Besproken bestemmingen, reisvoorkeuren, bekeken producten. Minimale wrijving. Maximale context. De menselijke specialist pakt precies op waar Joy stopte. Dat is geen handoff. Dat is precies het punt. Joy bestaat om dat menselijke gesprek beter te maken, niet om het te vermijden.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eElk van deze details kostte meerdere iteraties. En geen enkele is zichtbaar voor de klant. Dat is de bedoeling.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch3 id=\"security-an-honest-note\"\u003eSecurity: een eerlijke noot\u003c/h3\u003e\n\u003cp\u003eIk houd deze sectie kort en eerlijk. Security is niet iets wat je afmaakt. Het is iets waar je continu aan werkt.\u003c/p\u003e\n\u003cp\u003eWe hebben een dubbel guardsysteem. Een input guard vóór de LLM die prompt-injection-pogingen onderschept, en een output guard ná de LLM die credential leaks of system prompt-fragmenten in antwoorden onderschept. Beide zijn deterministisch en voegen vrijwel geen latency toe.\u003c/p\u003e\n\u003cp\u003eDe system prompt is verhard met anti-jailbreak-instructies. Er is rate limiting, botbescherming en IP-beheer voor hardnevig misbruik.\u003c/p\u003e\n\u003cp\u003eIs het bulletproof? Nee. Niets is dat. Maar het is gebouwd met defense in depth in gedachten, en we itereren erop naarmate nieuwe aanvalspatronen opduiken. Als je een AI-interface bouwt die met het publiek praat en je hebt nog niet nagedacht over input/output guards, dan ligt er een probleem op de loer.\u003c/p\u003e\n\u003ch3 id=\"the-bigger-picture-an-extra-channel-not-a-replacement\"\u003eHet grotere geheel: een extra kanaal, geen vervanging\u003c/h3\u003e\n\u003cp\u003eLaat me heel duidelijk zijn over wat Joy is en wat ze niet is.\u003c/p\u003e\n\u003cp\u003eJoy is geen vervanging van onze website. Ze is geen vervanging van telefoon of e-mail. Ze is een extra kanaal. Een nieuwe manier voor klanten om onze producten te ontdekken, naast alles wat er al bestaat. Ze zal nooit een vervanging van menselijk contact zijn. Dat vinden we bij ons bedrijf het belangrijkst. Joy is een middel naar menselijk contact. Ze helpt klanten uitvinden wat ze willen, zodat het gesprek met een van onze specialisten op een compleet ander niveau begint.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eJoy is niet de bestemming. Ze is de weg die leidt naar een echt gesprek met een echt persoon.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eDe MCP-server staat al live, wat betekent dat AI-assistenten onze data al programmatisch kunnen bevragen. Als de Web 4.0-visie werkelijkheid wordt en Lisa\u0026rsquo;s AI-assistent zelfstandig op zoek gaat naar haar perfecte Thailand-reis, dan bestaat het endpoint al. Niet omdat we er haastig mee aan de slag zijn gegaan. Omdat we al meer dan een jaar met deze infrastructuur aan het experimenteren zijn.\u003c/p\u003e\n\u003cp\u003eJoy is niet het eindproduct. Ze is het begin van een kanaalstrategie die zowel mens-tot-AI- als AI-tot-AI-interacties omvat. En ze is een test, een heel serieuze, heel grondige test, of conversationele AI vandaag de dag daadwerkelijk waarde kan toevoegen voor reisklanten.\u003c/p\u003e\n\u003cp\u003eDe resultaten tot nu toe zijn beter dan ik verwachtte. Niet vlekkeloos, niets is dat, maar echt indrukwekkend in hoe natuurlijk de gesprekken voelen en hoe relevant de aanbevelingen zijn. Als je een klant een vaag idee ziet beschrijven en Joy komt met reizen die daadwerkelijk passen, dan besef je: dit is geen gimmick. Het werkt. En het werkt dankzij alles wat eronder ligt.\u003c/p\u003e\n\u003ch3 id=\"so-what-are-we-trying-to-solve-again\"\u003eDus, welk probleem lossen we ook alweer op?\u003c/h3\u003e\n\u003cp\u003eIk begon met deze vraag omdat dit de vraag was die alles heeft geleid wat we bouwden. Niet \u0026ldquo;hoe gebruiken we AI?\u0026rdquo; Niet \u0026ldquo;wat doen onze concurrenten?\u0026rdquo; Niet \u0026ldquo;hoe komen we innovatief over?\u0026rdquo;\u003c/p\u003e\n\u003cp\u003eAlleen: welk probleem lossen we op, en werkt deze oplossing daadwerkelijk?\u003c/p\u003e\n\u003cp\u003eWe bouwden Joy om daarachter te komen. Met echte architectuur. Echte data. Echte klanten. En de bereidheid om \u0026ldquo;dit is nog niet klaar\u0026rdquo; te zeggen als dat zo zou blijken.\u003c/p\u003e\n\u003cp\u003eAls je nadenkt over het toevoegen van AI aan je product, begin daar. Niet met de tool. Niet met de pitch van een leverancier. Niet met de druk om bij te blijven.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eBegin met de vraag. Bouw vanuit het antwoord.\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eJe klanten verdienen beter dan een FAQ met een gezicht. Ze verdienen een echt gesprek. En uiteindelijk een echt mens. Zorg dat je AI ze daar naartoe leidt, en niet ervandaan.\u003c/p\u003e\n\u003cp\u003eAls je \u003ca href=\"https://www.allsubjectsmatter.nl/nl/can-a-conversation-be-a-travel-channel-we-built-the-architecture-to-find-out/\" data-preview\u003eDeel 1\u003c/a\u003e nog niet gelezen hebt: daar begint het verhaal. Waarom we dit bouwden, wat we leerden van ons mislukken in 2019, en waarom je hier niet komt met een plugin.\u003c/p\u003e\n\u003cfigure class=\"bookmark\"\u003e\u003ca class=\"bookmark__link\" href=\"https://www.allsubjectsmatter.nl/nl/can-a-conversation-be-a-travel-channel-we-built-the-architecture-to-find-out/\"\u003e\u003cspan class=\"bookmark__body\"\u003e\u003cspan class=\"bookmark__title\"\u003eCan a conversation be a travel channel? Part 1: We built one to find out.\u003c/span\u003e\u003cspan class=\"bookmark__description\"\u003eWe built a full AI travel interface from scratch, not to say we do AI, but to test whether a conversation can actually be a meaningful channel for personalized travel. This is what it took, why a plugin will never get you there, and why most AI chatbots are just expensive search bars.\u003c/span\u003e\u003cspan class=\"bookmark__meta\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"25px\"\u003e\u003csource type=\"image/webp\" sizes=\"25px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/the-technical-architecture-behind-a-conversational-travel-channel/bookmarks/02-icon_hu_8a3f1fd6616d7152.webp\" alt=\"\" class=\"bookmark__icon\" width=\"50\" height=\"50\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003cspan\u003eAll Subjects Matter\u003c/span\u003e\u003cspan\u003eRemco Loup.\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"bookmark__thumb\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/webp\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/avif\" sizes=\"250px\"\u003e\u003csource type=\"image/webp\" sizes=\"250px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/the-technical-architecture-behind-a-conversational-travel-channel/bookmarks/02-thumb_hu_bbbba9d6b5c5a7aa.webp\" alt=\"\" width=\"500\" height=\"435\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003c/span\u003e\u003c/a\u003e\u003c/figure\u003e\n\n","date_modified":"2026-03-11T15:47:46+01:00","date_published":"2026-03-11T09:54:10+01:00","id":"https://www.allsubjectsmatter.nl/nl/the-technical-architecture-behind-a-conversational-travel-channel/","language":"nl","summary":"De technische deep-dive over hoe we Joy hebben gebouwd: een RAG-pipeline met vector search en reranking, een geheugensysteem dat vier iteraties kostte, en de UI/UX-keuzes die een antwoord van 25 seconden sneller laten voelen.","tags":["AI and LM","Tech"],"title":"Kan een AI-gesprek een verkoopkanaal voor reizen zijn? Deel 2: de technische architectuur","url":"https://www.allsubjectsmatter.nl/nl/the-technical-architecture-behind-a-conversational-travel-channel/"},{"authors":[{"name":"Remco Loup","url":"https://www.allsubjectsmatter.nl/author/remco/"}],"content_html":"\u003cp\u003eWe bouwden een AI-aangedreven reisinterface vanaf nul. Geen chatbot. Geen widget. Geen GPT-wrapper met een smiley op een website geplakt. Een volledige conversationele interface met een eigen vector database, een eigen retrieval pipeline, een eigen geheugensysteem en real-time streaming over WebSockets. Niet om iets te vervangen. Om te testen of conversationele AI er echt klaar voor is om een betekenisvol kanaal voor reizen te zijn. Dit is wat we ontdekten, wat het kostte, en waarom we denken dat het meeste van wat er nu is ruis is.\u003c/p\u003e\n\u003chr\u003e\n\u003cp\u003eDit is een verhaal in twee delen. Dit eerste deel gaat over het waarom: welk probleem we oplossen, wat we leerden van eerdere mislukkingen, en waarom een plugin je hier nooit zal brengen. \u003ca href=\"https://www.allsubjectsmatter.nl/nl/the-technical-architecture-behind-a-conversational-travel-channel/\" data-preview\u003eDeel 2\u003c/a\u003e gaat dieper in op de architectuur: de RAG pipeline, het geheugensysteem, de UI/UX-beslissingen en de technische afwegingen die we maakten.\u003c/p\u003e\n\u003ch2 id=\"what-are-you-actually-trying-to-solve\"\u003eWat probeer je eigenlijk op te lossen?\u003c/h2\u003e\n\u003cp\u003eDat is de vraag waar ik steeds op terugkom. Elke keer als een bedrijf zijn nieuwe \u0026ldquo;AI-powered\u0026rdquo; feature aankondigt. Elke keer als iemand me vraagt wat wij met AI doen. Elke keer als een leverancier weer een plugin komt pitchen.\u003c/p\u003e\n\u003cp\u003eHet is op dit moment de belangrijkste vraag in tech. En bijna niemand stelt hem.\u003c/p\u003e\n\u003cp\u003eIn plaats daarvan werkt de hele industrie achterstevoren: men begint met \u0026ldquo;we moeten iets met AI doen\u0026rdquo; en gaat dan op zoek naar een plek om het neer te zetten. Bedrijven horen \u0026ldquo;AI\u0026rdquo; en horen \u0026ldquo;innovatie\u0026rdquo; en denken dat ze achterlopen als ze niet nú iets doen.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eDe druk om \u0026ldquo;iets met AI te doen\u0026rdquo; is luider geworden dan de vraag wat je eigenlijk wilt bereiken.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eEn wat gebeurt er dan? Bedrijven grijpen de meest zichtbare, meest verkoopbare toepassing die ze kunnen vinden: een chatbot. Op de website plakken. Een naam en een vriendelijk gezichtje geven. Verbinden met een kennisbank. Klaar. \u0026ldquo;Wij doen nu AI.\u0026rdquo;\u003c/p\u003e\n\u003cp\u003eHet probleem: de integratie is altijd beperkt. Sommigen koppelen aan een productdatabase. Sommigen halen live beschikbaarheid op. Maar als plugin is er een plafond aan hoe diep die integratie gaat. Hij werkt altijd met de data die hij krijgt, in het formaat dat de API levert, met de context die de leverancier voldoende vond. Het resultaat is een interactie die aan de oppervlakte conversationeel oogt, maar fundamenteel beperkt blijft in wat hij kan.\u003c/p\u003e\n\u003cp\u003eEn omdat het een plugin is, kan het nooit volledig naadloos zijn. Het blijft altijd een aparte laag bovenop je website, geen onderdeel ervan. Een andere designtaal. Een ander interactiemodel. Een andere ervaring. De klant voelt dat, ook al kan hij het niet onder woorden brengen. Het is het verschil tussen een kamer die als geheel is ontworpen en een kamer waar iemand een kiosk in de hoek heeft gezet.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eEen slimmere poortwachter is nog steeds een poortwachter. Je klanten wilden een deur.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eWe zijn zelf deze weg gegaan. In 2019 bouwden we de \u003ca href=\"https://www.allsubjectsmatter.nl/nl/how-333travel-is-revolutionizing-the-travel-industry/\" data-preview\u003eeerste versie van Joy\u003c/a\u003e op Flow.ai. Een intent-gebaseerde chatbot die natuurlijke taal begreep, reisvoorkeuren uit één zin kon halen en leads genereerde voor onze specialisten. Het werkte. Technisch gezien. Maar de klantbetrokkenheid was te laag om hem in leven te houden. Begin 2022 hebben we Joy uit dienst genomen.\u003c/p\u003e\n\u003cp\u003eDe les was niet dat de technologie slecht was. De les was dat mensen hebben geleerd chatbots te wantrouwen. En terecht. Jarenlang hebben bedrijven chatbots gebruikt als barrière om contact te maken, ook toen ze het tegendeel beweerden. Klanten weten dat. Ze hebben het meegemaakt. Na drie foute antwoorden denk je niet \u0026ldquo;even opnieuw proberen.\u0026rdquo; Je denkt \u0026ldquo;hoe kom ik bij een echt mens?\u0026rdquo; Sommige mensen doen niet eens de moeite. Ze zien een chatbelletje met een gezichtje en hun eerste reactie is: weg ermee. Niet omdat ze ongeduldig zijn, maar omdat de ervaring hun heeft geleerd dat het tijdverspilling is.\u003c/p\u003e\n\u003cp\u003eDat is geen technologieprobleem. Dat is een vertrouwensprobleem. En dat los je niet op door hetzelfde formaat slimmer te maken.\u003c/p\u003e\n\u003cp\u003eDaar komt bij dat de interface van een chatbot-plugin simpelweg beperkend is. Een klein venster in de hoek van je scherm. Een paar regels tekst. Misschien een knop of twee. Daarin kun je geen rijke productkaarten tonen. Je kunt geen kaart van een reis laten zien. Je kunt iemand niet laten browsen, vergelijken, reizen bewaren voor later. Het formaat dicteert de ervaring, en het formaat is een doos.\u003c/p\u003e\n\u003cp\u003eDie ervaring is precies waarom we geen nieuwe chatbot hebben gebouwd. Geen slimmere. Geen betere. Iets fundamenteel anders. Een volledige conversationele interface, vanaf nul gebouwd, ontworpen om te inspireren. Waar iemand bestemmingen kan verkennen, reizen kan ontdekken waarvan hij het bestaan niet kende, en waar hij — als hij er klaar voor is — na een gesprek dat echt goed voelde in contact komt met een specialist. Niet na drie mislukte pogingen om langs een poortwachter te komen.\u003c/p\u003e\n\u003cp\u003eDe echte vraag was nooit \u0026ldquo;hoe voegen we AI toe aan onze website?\u0026rdquo; De echte vraag is: waar creëert AI echt waarde die eerder niet mogelijk was? Dat zijn we gaan testen.\u003c/p\u003e\n\u003ch2 id=\"why-i-built-what-i-built\"\u003eWaarom ik bouwde wat ik bouwde\u003c/h2\u003e\n\u003cp\u003eIk heb dit niet gebouwd omdat ik denk dat websites dood zijn, of omdat ik wilde kunnen zeggen dat we \u0026ldquo;iets met AI doen\u0026rdquo;. Ik heb eerder geschreven over \u003ca href=\"https://www.allsubjectsmatter.nl/nl/lets-stop-talking-about-ai-and-start-solving-real-problems/\" data-preview\u003ehoeveel ik een hekel heb aan die mentaliteit\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eIk heb dit gebouwd vanwege een vraag die ik al meer dan een jaar toets: is conversationele AI er echt klaar voor om een echt kanaal te zijn?\u003c/p\u003e\n\u003cp\u003eIn mijn \u003ca href=\"https://www.allsubjectsmatter.nl/nl/web-4-0-and-the-autonomous-web-the-rise-of-ai-driven-customers/\" data-preview\u003eWeb 4.0-post\u003c/a\u003e schreef ik over het autonome web, een wereld waarin AI-assistenten niet alleen informatie ophalen, maar optreden als onafhankelijke digitale consumenten namens hun mensen. Waar Lisa tegen haar AI-assistent zegt \u0026ldquo;zoek een reis van twee weken naar Thailand\u0026rdquo; en de AI via MCP-interfaces contact opneemt met reisorganisaties, onderhandelt, verfijnt en opties presenteert. Zonder dat Lisa ook maar een browser opent.\u003c/p\u003e\n\u003cp\u003eDat klinkt misschien alsof de mens uit het plaatje verdwijnt. Dat is niet zo. De techniek maakt volledige autonomie mogelijk. Maar dat betekent niet dat we de mens willen weghalen. Het betekent dat de klant mag kiezen. En onze default is en blijft: naar een mens leiden. Daar later meer over.\u003c/p\u003e\n\u003cp\u003eDie toekomst komt. Misschien niet morgen, maar hij komt. En als je een reisorganisatie bent die zichzelf serieus neemt, kun je er maar beter op voorbereid zijn.\u003c/p\u003e\n\u003cp\u003eDus bouwde ik Joy, een AI-reisassistent voor 333travel. Niet als productlancering, niet als marketingstunt, maar als een serieuze test. Kunnen we een conversatie-interface bouwen die écht waarde toevoegt? Die onze producten begrijpt? Die minder hallucineert? Die een echt nuttige ervaring neerzet als extra kanaal naast onze website, telefoon en e-mail?\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eIk heb dit niet gebouwd om onze website te vervangen. Ik heb dit gebouwd om een vraag te beantwoorden: kan een gesprek een betekenisvol kanaal zijn voor gepersonaliseerd reizen?\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"what-this-actually-is\"\u003eWat het eigenlijk is\u003c/h2\u003e\n\u003cp\u003eLaat me concreet zijn over wat Joy doet:\u003c/p\u003e\n\u003cp\u003eJoy zit bovenop onze volledige productdatabase. Elke rondreis, elk hotel, elke excursie, elke bestemming, allemaal als vectoren geëmbed in een eigen database. Als een klant zegt \u0026ldquo;ik wil iets avontuurlijks in Zuidoost-Azië, zo\u0026rsquo;n twee weken, niet te toeristisch,\u0026rdquo; dan gaat Joy niet keyword-matchen tegen een PDF. Ze doorzoekt semantisch honderden producten, rerankt de resultaten op werkelijke relevantie en komt met opties die echt aansluiten bij de intentie.\u003c/p\u003e\n\u003cp\u003eEn ze onthoudt het gesprek. Niet alleen het laatste bericht, de hele thread. Ze weet welke reizen besproken zijn, wat bewaard is, wat verworpen. Als je zegt \u0026ldquo;die reis die je eerder noemde met de kookles,\u0026rdquo; snapt ze wat je bedoelt, ook al noemde ze het zelf een \u0026ldquo;culinaire ervaring.\u0026rdquo;\u003c/p\u003e\n\u003cp\u003eMaar hier zit het cruciale verschil met elke willekeurige chatbot: ze weet niet alleen iets van reizen in het algemeen. Ze kent ónze reizen. Onze specifieke producten. Onze specifieke reisschema\u0026rsquo;s. Onze bestemmingen die ons team zelf heeft bezocht. Dat kun je niet zomaar met een plugin erop plakken, hoe slim de LLM erachter ook is.\u003c/p\u003e\n\u003cp\u003eEn op het moment dat je menselijk contact wilt? Dan geeft Joy de volledige context door aan een echte reisspecialist die precies weet wat je hebt verkend. Geen \u0026ldquo;kun je je vraag nog eens omschrijven.\u0026rdquo; Geen koude overdracht. Volledige continuïteit.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eJoy vervangt gesprekken met specialisten niet. Ze maakt ze beter.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eWat ze (nog) niet doet: klantenservicevragen, luchtvaartinformatie of support na boeking afhandelen. Dit is puur een kanaal voor productontdekking en inspiratie. Daar komen we nog wel. Maar we gaan niet doen alsof ze dingen kan die ze niet kan. En eerlijk gezegd: als iemand bij de klantenservice terechtkomt, is er al een probleem. Dat is het slechtst denkbare moment om een machine tussen jou en je klant te zetten. Dan moet je er zelf zijn. Persoonlijk. Dus we zijn nog steeds aan het discussiëren over wat we haar daar laten doen. De nuance zit altijd in de details.\u003c/p\u003e\n\u003ch2 id=\"why-you-cant-get-here-with-a-plugin\"\u003eWaarom je hier niet komt met een plugin\u003c/h2\u003e\n\u003cp\u003eWat wij gebouwd hebben, bereik je niet door een plugin te installeren. Niet met ChatGPT. Niet met een of andere standaard \u0026ldquo;AI-widget.\u0026rdquo; Niet met een SaaS-tool die belooft \u0026ldquo;je website in minuten slimmer te maken.\u0026rdquo;\u003c/p\u003e\n\u003cp\u003eEn dat komt niet doordat de AI-modellen niet goed genoeg zijn. De modellen zijn prima. Het probleem zit in alles eromheen.\u003c/p\u003e\n\u003cp\u003eHet hele systeem draait op data die wij zelf bezitten, structureren en beheersen. Elke reis in onze database is door ons eigen team bezocht. Elk reisschema is onze eigen creatie. Elk stuk bestemmingenkennis leeft in onze eigen systemen. Dit is geen data die we van een derde partij licentiëren of uit een brochurecatalogus trekken.\u003c/p\u003e\n\u003cp\u003eDat betekent dat we het kunnen embedden. Dat we het precies zo kunnen structureren als we nodig hebben voor semantisch zoeken. Dat we het kunnen bijwerken op het moment dat er iets verandert. Wij beheersen de volledige pipeline, van ruwe productdata naar vector embedding naar retrieval naar antwoord.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eJe kunt geen slimme interface bouwen bovenop bergen domme data.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eAls je productdata in andermans systeem leeft, als je reizen uit een catalogus doorverkoopt, als je \u0026ldquo;database\u0026rdquo; een verzameling PDF\u0026rsquo;s van touroperators is, dan ben je afhankelijk van welke structuur iemand anders heeft bedacht. Je kunt geen intelligentie bouwen op data die je niet begrijpt of beheerst.\u003c/p\u003e\n\u003cp\u003eEn veel bedrijven starten niet alleen vanaf nul, ze starten met een achterstand. Hun bestaande systemen zijn al onvoldoende. De booking engine is verouderd. De productdata ligt verspreid over spreadsheets en PDF\u0026rsquo;s. De customer journey heeft overal gaten. En in plaats van die fundamenten te fixen, vinden ze het slim om er een AI-chatbot bovenop te leggen. Alsof intelligentie bovenop disfunctie iets functioneels oplevert. Doet het niet. Het maakt de disfunctie alleen moeilijker te diagnosticeren.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eJe lost geen probleem op. Je versiert het.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eWat wij gebouwd hebben, vereiste drie dingen:\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eOnze eigen data.\u003c/strong\u003e Elke reis is van ons. Bezocht door ons team. Gestructureerd in onze database. Embedded in onze vector store. Je kunt geen semantic search bouwen over data die je niet zelf structureert.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eOnze eigen systemen.\u003c/strong\u003e Onze eigen backend, frontend, deployment pipeline. Toen we een dual reranker nodig hadden, bouwden we die. Toen we semantic conversation memory nodig hadden, bouwden we het. Toen we een MCP server nodig hadden voor Web 4.0-readiness, bouwden we die. Geen goedkeuring van een vendor nodig. Geen feature requests. Geen afhankelijkheid van andermans roadmap.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eDe hele keten in eigen hand.\u003c/strong\u003e Van het moment dat een klant de chat opent tot het moment dat ze met een specialist praten, is elke stap van ons. De AI kent onze producten omdat we de producten zelf hebben gemaakt. De specialist kent het gesprek omdat ons systeem het aan hen doorgeeft.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eDe AI is de laatste 10% van het werk. De eerste 90% is een bedrijf bouwen dat het kan dragen.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eIn \u003ca href=\"https://www.allsubjectsmatter.nl/nl/the-technical-architecture-behind-a-conversational-travel-channel/\" data-preview\u003edeel 2\u003c/a\u003e laat ik zien hoe het onder de motorkap echt werkt. De architectuur, de search pipeline, het geheugensysteem dat vele pogingen kostte om goed te krijgen, en waarom 25 seconden wachten aanvoelt als 5 als je het goed ontwerpt.\u003c/p\u003e\n\u003cfigure class=\"bookmark\"\u003e\u003ca class=\"bookmark__link\" href=\"https://www.allsubjectsmatter.nl/nl/the-technical-architecture-behind-a-conversational-travel-channel/\"\u003e\u003cspan class=\"bookmark__body\"\u003e\u003cspan class=\"bookmark__title\"\u003eCan a conversation be a travel channel? Part 2: The technical architecture\u003c/span\u003e\u003cspan class=\"bookmark__description\"\u003eThe technical deep-dive into how we built Joy: a RAG pipeline with vector search and reranking, a memory system that took four iterations, and the UI/UX decisions that make a 25-second response feel instant.\u003c/span\u003e\u003cspan class=\"bookmark__meta\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"25px\"\u003e\u003csource type=\"image/webp\" sizes=\"25px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/can-a-conversation-be-a-travel-channel-we-built-the-architecture-to-find-out/bookmarks/01-icon_hu_8a3f1fd6616d7152.webp\" alt=\"\" class=\"bookmark__icon\" width=\"50\" height=\"50\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003cspan\u003eAll Subjects Matter\u003c/span\u003e\u003cspan\u003eRemco Loup.\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"bookmark__thumb\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/webp\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/avif\" sizes=\"250px\"\u003e\u003csource type=\"image/webp\" sizes=\"250px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/can-a-conversation-be-a-travel-channel-we-built-the-architecture-to-find-out/bookmarks/01-thumb_hu_cb9ae8a41b04a42d.webp\" alt=\"\" width=\"500\" height=\"435\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003c/span\u003e\u003c/a\u003e\u003c/figure\u003e\n\n","date_modified":"2026-03-11T15:47:56+01:00","date_published":"2026-03-10T13:46:34+01:00","id":"https://www.allsubjectsmatter.nl/nl/can-a-conversation-be-a-travel-channel-we-built-the-architecture-to-find-out/","language":"nl","summary":"We bouwden een volledige AI-reisinterface vanaf nul. Niet om te kunnen zeggen dat we \"met AI bezig zijn\", maar om te testen of een gesprek echt een betekenisvol kanaal kan zijn voor gepersonaliseerde reizen. Dit was ervoor nodig, waarom een plugin je daar nooit brengt, en waarom de meeste AI-chatbots gewoon dure zoekbalken zijn.","tags":["AI and LM"],"title":"Kan een AI-conversatie een reiskanaal zijn? Deel 1: we bouwden er een om erachter te komen.","url":"https://www.allsubjectsmatter.nl/nl/can-a-conversation-be-a-travel-channel-we-built-the-architecture-to-find-out/"},{"authors":[{"name":"Remco Loup","url":"https://www.allsubjectsmatter.nl/author/remco/"}],"content_html":"\u003cp\u003eIk heb een \u0026ldquo;productie\u0026rdquo;-app gebouwd met vibecoding. 97.089 regels gegenereerd. Slechts 25.056 regels haalden productie. Vier complete refactors. De AI faalde niet door slechte prompts. Het faalde omdat AI dingen vergeet, context kwijtraakt en duplicaatlogica creëert, hoe goed je het ook stuurt. Vibecoding gaat er niet om AI voor je te laten bouwen. Het gaat erom dat je je systeem goed genoeg kent om de ruis te filteren. Zonder die kennis ship je alleen maar gissingen.\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"what-vibecoding-actually-means\"\u003e\u003cstrong\u003eWat vibecoding eigenlijk betekent\u003c/strong\u003e\u003c/h2\u003e\n\u003cp\u003eMensen denken dat vibecoding betekent dat AI je app bouwt terwijl jij toekijkt. Reality check: dat is helemaal niet het geval als je een daadwerkelijk werkend product nodig hebt met elke keer het verwachte resultaat.\u003c/p\u003e\n\u003cp\u003eVibecoding betekent nog steeds dat jij de controle hebt. Je prompt met precisie. Je experimenteert. Je stuurt. Je filtert. Het blijft handwerk, alleen met andere hefboomwerking.\u003c/p\u003e\n\u003cblockquote class=\"pull-quote\"\u003e\u003cp\u003e\u0026ldquo;A fool with a tool is still a fool\u0026rdquo;\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eEn als je niet weet wat je doet? Dan is het resultaat hetzelfde als altijd: een zooitje. Vibecoding zonder de juiste kennis is gewoon fout. En het grootste probleem met AI is dat het maar blijft produceren: code na code na code. Het doet gewoon wat je vraagt. Het is daar niet kritisch over. Dus als je niet weet wat je doet, spreek je jezelf de hele tijd tegen met geprogrammeerde functionaliteit die nooit samenwerkt binnen je app.\u003c/p\u003e\n\u003cp\u003eAI is koning in fallbacks. Ik had voor elke mislukte API-call 5 verschillende fallbacks gekregen. Zouden we niet gewoon de juiste manier aan de praat moeten krijgen in plaats van fallbacks bouwen? AI is ook lui: het onderdrukt liever notices dan dat het ze oplost. Zeker als het niet in één keer lukt.\u003c/p\u003e\n\u003ch2 id=\"i-built-a-production-app-with-ai-but-not-because-of-ai\"\u003e\u003cstrong\u003eIk bouwde een productie-app met AI (maar niet dankzij AI)\u003c/strong\u003e\u003c/h2\u003e\n\u003cp\u003eIk heb Windsurf gebruikt om een complete applicatie te bouwen. Geen menselijke developers. Geen code die ik direct heb geschreven. We gebruiken Sonnet 4.5 als onze kennisbron.\u003c/p\u003e\n\u003cp\u003eHet resultaat na veel frustratie: een volwaardig productiespel met backend-logica, login-flows, async data writes, mail queues, admin security, monitoring en cartesische algoritmes die updaten op basis van gebruikersinteractie.\u003c/p\u003e\n\u003cp\u003eDe AI heeft alles geschreven. Ik heb geen enkele regel code aangeraakt. Zelfs de simpelste CSS-wijziging liet ik aan AI over.\u003c/p\u003e\n\u003cp\u003eMaar wat niemand je vertelt: ik heb het hele project vier keer compleet moeten refactoren. En niet omdat ik slecht promptte. Ik gebruikte KISS-principes, DRY-standaarden, PSR-richtlijnen. We schreven vooraf beslissingen uit en maakten implementatiedocumenten. We gebruikten richtlijnen voor de talen die we gebruikten.\u003c/p\u003e\n\u003cp\u003eUiteindelijk maakte het niets uit.\u003c/p\u003e\n\u003cp\u003eAI vergeet. Het verliest context. Het begint duplicaatfuncties te maken zonder door te hebben dat het die logica al drie files terug schreef. Hoe complexer je project wordt, hoe meer het afdrijft. En hoe vaker het niet in één keer goed gaat, hoe meer AI denkt backwards compatibility te moeten handhaven, waardoor je code nog complexer wordt. Het maakt niet uit hoe je het de eerste keer prompt, of met project rules.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eNa vier complete refactors leerde ik: AI vergeet de context niet. Het heeft die nooit gehad ;).\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eDe enige reden dat de definitieve versie werkt, is dat ik precies wist wat ik aan het bouwen was. Ik had het systeem in mijn hoofd. Ik zag wanneer AI zichzelf herhaalde. Ik wist wanneer ik moest stoppen, bijsturen of opnieuw beginnen. Ik controleerde elke file op duplicaat fallback-data, duplicaten enzovoort. Ik gebruikte ook het zeer handige npm-package knip.\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://www.npmjs.com/package/knip\" data-external\u003ehttps://www.npmjs.com/package/knip\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eJe kunt niet iets de productie in vibecoden zonder je product te begrijpen. Punt. En je moet accepteren dat bugfixen betekent dat je op AI leunt om het op te lossen zonder iets anders te breken. Veel succes daarmee\u0026hellip;\u003c/p\u003e\n\u003ch2 id=\"the-data-tells-the-rest-of-our-story\"\u003e\u003cstrong\u003eDe data vertelt de rest van ons verhaal\u003c/strong\u003e\u003c/h2\u003e\n\u003cp\u003eTijdens ons vibecoden produceerde Windsurf:\u003c/p\u003e\n\u003cfigure class=\"figure\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003csource type=\"image/webp\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/i-vibecoded-an-app-and-this-is-what-i-learned/images/Screenshot-2025-11-10-at-09.28.05_hu_8c902f8cc6fdc18d.webp\" alt=\"\" width=\"560\" height=\"442\" data-zoom-src=\"https://www.allsubjectsmatter.nl/i-vibecoded-an-app-and-this-is-what-i-learned/images/Screenshot-2025-11-10-at-09.28.05_hu_f69c6e1b7aae2732.webp\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003c/figure\u003e\n\n\u003cul\u003e\n\u003cli\u003e97.089 regels code gegenereerd\u003c/li\u003e\n\u003cli\u003e2.925 prompts verstuurd\u003c/li\u003e\n\u003cli\u003e5.693 credits gebruikt\u003c/li\u003e\n\u003cli\u003e15.734 tool calls (waaronder 7.000 code-edits)\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eWat er daadwerkelijk in productie is gegaan:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e25.056 regels code\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eMeer dan 70.000 regels zijn geschrapt, vervangen of gerefactord.\u003c/p\u003e\n\u003cp\u003eNu denk je misschien: dan moet je AI dus gewoon \u0026ldquo;beter sturen\u0026rdquo; of \u0026ldquo;preciezer prompten\u0026rdquo;. Dat is niet het punt dat ik wilde maken.\u003c/p\u003e\n\u003cp\u003eHet punt is: \u003cstrong\u003eje kunt AI-gebral op dit moment niet voorkomen.\u003c/strong\u003e Hoe goed je ook prompt. Hoe gestructureerd je aanpak ook is. AI overproduceert. Het genereert redundante code. Het raakt de tel kwijt van wat het al gebouwd heeft. Het creëert ruis. AI is gebouwd om altijd een vorm van AI-verbaal diarree te produceren.\u003c/p\u003e\n\u003cp\u003eDie 70k+ regels verspilde output? Dat is geen bug. Dat is de huidige staat van vibecoding.\u003c/p\u003e\n\u003cp\u003eEn als je je systeem niet goed genoeg kent om te herkennen wat troep is en wat niet, ship je alles en kun je zelfs met AI niet meer debuggen. Of erger: je besteedt persoonlijk weken aan het debuggen van een codebase die je niet snapt.\u003c/p\u003e\n\u003cp\u003eDe kosten per 1000 credits liggen rond de 35 dollar. De ontwikkeling van dit spel kostte me 210 dollar. Elk bedrijf dat Large Language Models levert verliest geld. De code die ik produceerde had makkelijk 210 x 10 = 2.100 dollar moeten kosten, minimaal. Ik vraag me af of ze dan überhaupt geld verdiend hadden.\u003c/p\u003e\n\u003cp\u003eLos van het geld: is dit onze kostbare elektriciteit echt waard? Ik ben benieuwd hoe haalbaar dit is als je de echte prijs betaalt.\u003c/p\u003e\n\u003ch2 id=\"what-people-get-wrong\"\u003e\u003cstrong\u003eWat mensen verkeerd begrijpen\u003c/strong\u003e\u003c/h2\u003e\n\u003cp\u003eAI is geen magie. Het is een \u0026ldquo;slimme\u0026rdquo;, snelle assistent. Maar hij begrijpt niet wat er voor jouw bedrijf telt. Hij heeft geen grip op edge cases, constraints, risico\u0026rsquo;s, performance-implicaties of security-kwetsbaarheden.\u003c/p\u003e\n\u003cp\u003eDat is jouw werk!\u003c/p\u003e\n\u003cp\u003eIn mijn geval hielp AI om iets echts te bouwen. Maar de AI beveiligde het adminpaneel niet. Die mail queue-logica ontwierp hij niet. Hij besliste niet wanneer async writes gedebounced moesten worden. Dat deed ik. Door kritisch te zijn en de juiste vragen te stellen dankzij bouwervaring van vroeger.\u003c/p\u003e\n\u003cp\u003eZonder die richting was het hele ding in elkaar gestort.\u003c/p\u003e\n\u003cp\u003eEn ik zie dit patroon overal. Teams die AI-producten bouwen zonder ownership of richting, alleen maar om te kunnen zeggen dat ze \u0026ldquo;iets met AI gedaan hebben\u0026rdquo;. Dat is oppervlakkig. Dat is broos. Het schaalt niet.\u003c/p\u003e\n\u003cblockquote class=\"pull-quote\"\u003e\u003cp\u003e\u003cem\u003e\u0026ldquo;You\u0026rsquo;re not building products. You\u0026rsquo;re laundering prompts into deployments\u0026rdquo;\u003c/em\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eHoe vaak hoor je niet van bovenaf: we moeten iets met AI doen. We raken achter. Achter op wat?\u003c/p\u003e\n\u003ch2 id=\"the-customer-service-trap\"\u003e\u003cstrong\u003eDe customer service-val\u003c/strong\u003e\u003c/h2\u003e\n\u003cp\u003eDan is er nog de hype rond AI voor klantenservice. Bedrijven beweren dat het perfect is voor 24/7 support, snellere antwoorden, minder mensen nodig.\u003c/p\u003e\n\u003cp\u003eMaar als je een bot nodig hebt om met je klanten te praten, ligt je echte probleem waarschijnlijk ergens anders. AI repareert kapotte processen niet. Het verstopt ze. Tijdelijk.\u003c/p\u003e\n\u003cp\u003eAls je minder menselijk contact als winst ziet, heb je al verloren. Als het beantwoorden van vragen lang duurt, moet je misschien ergens anders kijken waarom mensen zoveel vragen hebben, en dat eerst verbeteren.\u003c/p\u003e\n\u003ch2 id=\"the-actual-point\"\u003e\u003cstrong\u003eHet eigenlijke punt\u003c/strong\u003e\u003c/h2\u003e\n\u003cp\u003eVibecoding is gewoon weer een tool. Meer niet. Wel een heel give tool in de juiste handen, maar toch een tool. En vooral goed voor prototyping. Maar met een flinke dosis kennis kun je er misschien standalone apps mee bouwen die écht werken, die je op z\u0026rsquo;n minst zelf kunt testen en waarvan het resultaat op een transparante manier te herleiden is naar de geschreven code.\u003c/p\u003e\n\u003cp\u003eHet vervangt geen denken. Het zou je denken moeten versterken, en het zou je niet minder moeten laten nadenken.\u003c/p\u003e\n\u003cp\u003eGoed gebruikt kan het krachtig zijn. Maar je moet nog steeds weten wat je wilt bouwen. Je moet de structuur ownen. Je moet de ruis filteren. Je moet AI-geklets herkennen als je het ziet. Je moet het proces leiden.\u003c/p\u003e\n\u003cp\u003eAnders bouw je geen prototype. Dan genereer je alleen gokken.\u003c/p\u003e\n\u003cp\u003eDe toekomst is niet AI-first. De bedrijven die ertoe doen blijven human-first. Zij begrijpen het probleem voordat ze een tool kiezen. En ze blijven de enige vraag stellen die telt, voordat ze AI te hulp roepen:\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eWat lossen we nu eigenlijk op?\u003c/strong\u003e\u003c/p\u003e\n","date_modified":"2025-11-10T11:50:09+01:00","date_published":"2025-11-10T11:37:31+01:00","id":"https://www.allsubjectsmatter.nl/nl/i-vibecoded-an-app-and-this-is-what-i-learned/","language":"nl","summary":"Ik bouwde een \"productie\"-app met vibecoding. 97.089 regels gegenereerd. Slechts 25.056 kwamen in productie. Vier complete refactors. De AI faalde niet door slechte prompts. Het faalde omdat AI dingen vergeet, context kwijtraakt en dubbele logica creëert, hoe goed je het ook stuurt.","tags":["AI and LM","Tech"],"title":"Ik vibecodede een app en dit heb ik ervan geleerd.","url":"https://www.allsubjectsmatter.nl/nl/i-vibecoded-an-app-and-this-is-what-i-learned/"},{"authors":[{"name":"Remco Loup","url":"https://www.allsubjectsmatter.nl/author/remco/"}],"content_html":"\u003cp\u003eArtificial Intelligence is er om te blijven, daar gaat het hier niet over.\u003c/p\u003e\n\u003cp\u003eHet probleem is dat \u003cstrong\u003eiedereen over AI praat\u003c/strong\u003e, maar dat weinig mensen nadenken over hoe je het écht goed inzet. Ergens onderweg is \u0026ldquo;wij gebruiken AI\u0026rdquo; een marketingterm geworden in plaats van een technische beslissing. Iedereen wil meedoen met de AI-golf, anders hoor je er niet bij. Als je de big tech moet geloven, die het iedereen door de strot probeert te duwen.\u003c/p\u003e\n\u003cp\u003eMaar als je een klein stapje terugneemt, valt iets vreemds op:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eBedrijven lossen met AI problemen op die ze eigenlijk nooit hadden mogen hebben.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eSlecht ontworpen systemen, ontbrekend ownership en spaghetti-infrastructuur worden nu \u0026ldquo;opgelost\u0026rdquo; door er LLM\u0026rsquo;s bovenop te gooien. Met als gevolg dat systemen nog moeilijker te onderhouden zijn, duurder worden en lastiger te begrijpen.\u003c/p\u003e\n\u003cp\u003eDeze blog gaat niet over AI afkraken.\u003cbr\u003e\nHet gaat over \u003cstrong\u003ehelder nadenken, technisch ownership\u003c/strong\u003e en de juiste tools voor de klus gebruiken.\u003c/p\u003e\n\u003ch2 id=\"openai-investigation-research-paper\"\u003eOnderzoeksrapport OpenAI\u003c/h2\u003e\n\u003cp\u003eRecent onderzoek van OpenAI en Georgia Tech benadrukt een kernwaarheid die vaak genegeerd wordt: \u003cstrong\u003eAI-hallucinaties, die momenten waarop modellen dingen verzinnen, zijn geen bugs. Ze zijn een wiskundig gevolg van hoe deze systemen gebouwd zijn.\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eZelfs met perfecte trainingsdata beloont de huidige architectuur van generatieve AI zelfverzekerde antwoorden boven het toegeven van onzekerheid. Dat betekent dat hallucinaties niet alleen vaak voorkomen, ze zijn \u003cem\u003eonvermijdelijk\u003c/em\u003e, tenzij we fundamenteel veranderen hoe we deze modellen trainen en evalueren.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eZoals de onderzoekers het formuleren: \u0026ldquo;Hallucinations are mathematically inevitable.\u0026rdquo; (\u003ca href=\"https://arxiv.org/pdf/2509.04664\" data-external\u003ebron\u003c/a\u003e)\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"how-people-abuse-ai-to-solve-the-wrong-problems\"\u003eHoe mensen AI misbruiken om de verkeerde problemen op te lossen\u003c/h2\u003e\n\u003cp\u003eLaten we een stap terugnemen.\u003c/p\u003e\n\u003cp\u003eAls je $1.200 per maand betaalt aan GPT-4 om e-mailtekst te parsen, telefoonnummers te extraheren, JSON te herformatteren en strings naar hoofdletters te zetten… dan innoveer je niet. Dan besteed je gezond verstand uit aan een dure black box.\u003c/p\u003e\n\u003cp\u003eJa, iemand deed dit recentelijk, en besefte te laat wat het kostte. Die persoon stapte toen over op GPT-4o-mini en bracht de rekening terug naar $200… Maar ook dan is het nog steeds een kutoplossing:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eDie taken hadden helemaal geen AI nodig. Gewoon basale softwarekennis.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eDit is geen uitzondering. Het gebeurt helaas vaak.\u003c/p\u003e\n\u003cp\u003eMensen denken dat AI hun oplossing voor alles is, terwijl het alleen de incompetentie van de persoon blootlegt, want:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eHet verhoogt de kosten\u003c/li\u003e\n\u003cli\u003eHet verbergt wat er werkelijk gebeurt\u003c/li\u003e\n\u003cli\u003eHet introduceert verborgen afhankelijkheden\u003c/li\u003e\n\u003cli\u003eHet maakt systemen vrijwel onmogelijk te debuggen en onderhouden\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eAI wordt gebruikt om een \u003cem\u003egebrek aan systeemontwerp te maskeren\u003c/em\u003e, niet om het te verbeteren.\u003c/p\u003e\n\u003ch2 id=\"ai-as-a-natural-language-interface-powerful-but-misused\"\u003eAI als natural language interface, krachtig maar verkeerd gebruikt\u003c/h2\u003e\n\u003cp\u003eMaar laten we eerlijk zijn: LLM\u0026rsquo;s zijn écht krachtig als je ze goed inzet.\u003cbr\u003e\nZe laten je in natuurlijke taal beschrijven wat je wilt, en geven dan een resultaat, zonder dat je de onderliggende logica hoeft te begrijpen.\u003c/p\u003e\n\u003cp\u003eDat maakt AI een fantastische tool voor:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003ePrototyping\u003c/li\u003e\n\u003cli\u003eWerken met grote kennisbanken\u003c/li\u003e\n\u003cli\u003eHet samenvatten van ongestructureerde data\u003c/li\u003e\n\u003cli\u003eFuzzy matching op grote schaal\u003c/li\u003e\n\u003c/ul\u003e\n\u003cblockquote\u003e\n\u003cp\u003eLLM\u0026rsquo;s zijn in wezen een nieuw soort interface, net zoals de command line, de GUI of API\u0026rsquo;s dat eerder waren.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eMaar ze zijn geen \u003cem\u003emagie\u003c/em\u003e. En zeker niet gratis. En bijna altijd is het niet de beste manier om een probleem op te lossen.\u003c/p\u003e\n\u003cp\u003eHoe kritischer de functie, hoe belangrijker het wordt om te snappen:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eWat wordt er uitgevoerd?\u003c/li\u003e\n\u003cli\u003eWaar stroomt de data naartoe?\u003c/li\u003e\n\u003cli\u003eWie is eigenaar van de output?\u003c/li\u003e\n\u003cli\u003eWat zijn de werkelijke kosten?\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"conversational-ai-the-modern-chatbot-but-still-a-chatbot\"\u003eConversational AI, de moderne chatbot, maar nog steeds een chatbot\u003c/h2\u003e\n\u003cp\u003eEr \u003cem\u003eis\u003c/em\u003e een weg vooruit voor conversational AI. Alleen is dat niet de revolutionaire sprong die mensen ervan verwachten, met zelfdenkende systemen die niet hallucineren en altijd het juiste antwoord geven.\u003c/p\u003e\n\u003cp\u003eJa, LLM\u0026rsquo;s kunnen slimmere, menselijkere antwoorden geven dan vóórdat LLM\u0026rsquo;s mainstream werden.\u003cbr\u003e\nJa, ze kunnen supportflows automatiseren die voorheen veel complexer waren om te scripten.\u003c/p\u003e\n\u003cp\u003eMaar laten we onszelf niets wijsmaken:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eMensen willen nog steeds \u003cstrong\u003eecht, menselijk contact\u003c/strong\u003e met je bedrijf, zeker als er iets misgaat.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eAls je je volledige klantenservice uitbesteedt aan een AI-assistent, stuur je een duidelijke boodschap (in mijn ogen de verkeerde):\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u0026ldquo;Het kan ons niet genoeg schelen om zelf met je te praten.\u0026rdquo;\u003c/li\u003e\n\u003cli\u003e\u0026ldquo;Jij bent de tijd van een echt persoon niet waard.\u0026rdquo;\u003c/li\u003e\n\u003cli\u003e\u0026ldquo;Wij verkopen alleen ons product. Kom alsjeblieft niet bij ons zeuren.\u0026rdquo;\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eOp korte termijn bespaart dat misschien kosten.\u003cbr\u003e\nMaar op lange termijn? Dan vernietig je het vertrouwen in je merk. Zeker omdat mensen doorgaans contact opnemen met de klantenservice als:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eZe gefrustreerd zijn\u003c/li\u003e\n\u003cli\u003eZe iets niet begrijpen\u003c/li\u003e\n\u003cli\u003eZe een klacht hebben\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eDat is \u003cstrong\u003eniet\u003c/strong\u003e het moment om ze een chatbot voor te schotelen, hoe \u0026ldquo;slim\u0026rdquo; die ook klinkt.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eGoede klantenservice is een relatie, geen kostenpost.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eDus ja, gebruik conversational AI om je teams te ondersteunen. Maar laat het menselijk contact niet vervangen waar empathie nodig is. Maak AI niet de interface waarmee klanten met je bedrijf praten, als je om je product geeft.\u003c/p\u003e\n\u003ch2 id=\"what-you-should-be-using-ai-for\"\u003eWaar je AI wél voor zou moeten gebruiken\u003c/h2\u003e\n\u003cp\u003eLaten we het omdraaien. Wat \u003cem\u003eis\u003c/em\u003e dan een goede use case voor AI?\u003c/p\u003e\n\u003cp\u003eJe zou AI moeten gebruiken als:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eHet je helpt dingen te doen die voorheen te duur, te traag of technisch te complex waren\u003c/li\u003e\n\u003cli\u003eJe je architectuur al geoptimaliseerd hebt, maar nog edge cases hebt die automatisering waard zijn\u003c/li\u003e\n\u003cli\u003eJe processen verrijkt, in plaats van technische schuld verbergt\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eBijvoorbeeld:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003ePrototypen van lastige functionaliteiten, zodat je sneller uitkomsten kunt testen\u003c/li\u003e\n\u003cli\u003eProductzoek verbeteren met semantisch begrip\u003c/li\u003e\n\u003cli\u003eAls je hallucinaties zichtbaar kunt maken in data, zijn ze perfect voor BI (business intelligence)\u003c/li\u003e\n\u003cli\u003eGepersonaliseerde samenvattingen genereren van grote interne rapporten\u003c/li\u003e\n\u003cli\u003eTelefoongesprekken transcriberen en samenvattingen maken\u003c/li\u003e\n\u003cli\u003eImage to text, zodat je beter semantisch kunt zoeken in grote beelddatabases.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eDit zijn taken waar AI echt uitblinkt, omdat ze in traditionele systemen nog duurder, lastiger of onmogelijk op te lossen waren.\u003c/p\u003e\n\u003cp\u003eMaar nogmaals: \u003cstrong\u003eweeg altijd de kosten af\u003c/strong\u003e.\nNiet alleen in euro\u0026rsquo;s, maar ook in \u003cstrong\u003eduidelijkheid\u003c/strong\u003e, \u003cstrong\u003econtrole\u003c/strong\u003e en \u003cstrong\u003eklantbeleving\u003c/strong\u003e. Probeer het altijd eerst op te lossen zonder AI.\u003c/p\u003e\n\u003ch2 id=\"tech-maturity-will-determine-who-wins\"\u003eTechnische volwassenheid bepaalt wie er wint\u003c/h2\u003e\n\u003cp\u003eDit is de kern:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eBedrijven met moderne, begrijpelijke en goed gedocumenteerde infrastructuur en data winnen.\nWie AI blijft stapelen bovenop zijn chaos… verliest.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eHet gaat er niet om hoe vroeg je met AI begon. Het gaat om:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eOf je stack onderhoudbaar is\u003c/li\u003e\n\u003cli\u003eOf je teams die echt begrijpen\u003c/li\u003e\n\u003cli\u003eOf je kunt redeneren over kosten, performance en falen\u003c/li\u003e\n\u003cli\u003eOf je eigenaar bent van je data, je architectuur en je roadmap\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eDe bedrijven die AI zien als een nieuw algoritme/interface om problemen op te lossen die ze eerder niet konden oplossen, halen volgens mij op de lange termijn de meeste waarde eruit. Gebruik AI niet voor de marketingwaarde die big tech je wil aanpraten.\u003c/p\u003e\n\u003ch2 id=\"offloading-data-ownership-is-a-strategic-risk\"\u003eData-eigendom uitbesteden is een strategisch risico\u003c/h2\u003e\n\u003cp\u003eIk eindig met nog een praktijkvoorbeeld dat ik recent tegenkwam.\u003c/p\u003e\n\u003cp\u003eEen bedrijf in de reisbranche bouwde een app waarmee eindgebruikers hun reisgegevens kunnen inzien. Die verkopen ze weer aan andere reisorganisaties en touroperators. Haalbaar, als je ziet hoe slecht de IT in travel er meestal voor staat.\u003c/p\u003e\n\u003cp\u003eDe reisorganisatie kan de app dus aanbieden met alle vouchers en reisschema\u0026rsquo;s van de reis van hun klant. Tot zover niets raars, behalve dat je al je documenten uitbesteedt aan een derde partij.\u003c/p\u003e\n\u003cp\u003eDie app-aanbieder heeft nu toegang tot veel van je cruciale bedrijfsinformatie. En dan koopt de klant extra services in de app (verzekeringen, transfers, upgrades). Je zou denken: dat is upsell voor de reisorganisatie die de klant heeft.\u003c/p\u003e\n\u003cp\u003eEn dan komt de knuppel in het hoenderhok. Het platform geeft opeens \u003cstrong\u003ecommissie\u003c/strong\u003e aan de touroperator… Biedt de app-aanbieder nu ook extra services aan jouw klant?\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eWie is er nu eigenaar van de klant?\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eDat is een grote verschuiving, en een gevaarlijke. Hoe meer infrastructuur je uitbesteedt, hoe meer controle je verliest over je klantrelaties en je first-party data.\u003c/p\u003e\n\u003cp\u003eBlijf zelf de klantjourney van je eigen klant regelen. Bescherm je toegevoegde waarde. Word geen commodity. En houd het persoonlijk.\u003c/p\u003e\n\u003cp\u003eDit wordt een enorm probleem! En AI versnelt het proces als reisbedrijven niet opletten.\u003c/p\u003e\n\u003ch2 id=\"conclusion-ai-is-a-tool-not-a-strategy-for-most-companies\"\u003eConclusie: AI is een tool, geen strategie voor de meeste bedrijven\u003c/h2\u003e\n\u003cp\u003eAI is indrukwekkend. Maar het vervangt de fundamenten niet.\u003c/p\u003e\n\u003cp\u003eGebruik het niet om slechte architectuur te verbergen.\nGebruik het niet als excuus om technische beslissingen te vermijden.\nLaat het niet verhullen wat je systemen doen, of wie je klant eigenlijk is.\u003c/p\u003e\n\u003cp\u003eIn plaats daarvan:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eWees eigenaar van je stack\u003c/li\u003e\n\u003cli\u003eBegrijp je eigen logica\u003c/li\u003e\n\u003cli\u003eGebruik AI waar het echte, meetbare waarde creëert\u003c/li\u003e\n\u003cli\u003eEn blijf altijd zelf nadenken\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eDe echte innovatie zit niet in de tool. Die zit in hoe helder je nadenkt over de problemen die je moet oplossen.\u003c/p\u003e\n","date_modified":"2026-04-24T09:19:24+02:00","date_published":"2025-09-23T11:59:39+02:00","id":"https://www.allsubjectsmatter.nl/nl/lets-stop-talking-about-ai-and-start-solving-real-problems/","language":"nl","summary":"In dit artikel behandel ik waarom AI een tool is en geen strategie; waarom hallucinaties in de huidige generatieve modellen wiskundig onvermijdelijk zijn; en waarom echte vooruitgang begint met een degelijke architectuur, ownership en helder nadenken — en niet met hype najagen.","tags":["AI and LM"],"title":"Stop met praten over AI (en los echte problemen op)","url":"https://www.allsubjectsmatter.nl/nl/lets-stop-talking-about-ai-and-start-solving-real-problems/"},{"authors":[{"name":"Remco Loup","url":"https://www.allsubjectsmatter.nl/author/remco/"}],"content_html":"\u003cp\u003eAl decennia passen bedrijven zich aan aan de evolutie van ons internet. Eerst was er Web 1.0, een read-only internet waar bedrijven statische websites bouwden. Daarna brak met Web 2.0 het interactieve tijdperk aan: social media, user-generated content en API\u0026rsquo;s waarmee apps met diensten konden verbinden. Nu leven we in het tijdperk van Web 3.0, waar AI personalisatie verbetert, taken automatiseert en context begrijpt.\u003c/p\u003e\n\u003cp\u003eMaar dit is nog maar het begin.\u003c/p\u003e\n\u003cp\u003eDe volgende fase, Web 4.0, brengt iets nog ingrijpenders: AI als autonome digitale agent. In plaats van mensen alleen te assisteren, gaat AI actief zaken doen met bedrijven—beslissingen nemen, deals onderhandelen en transacties uitvoeren, allemaal met minimale menselijke tussenkomst.\u003c/p\u003e\n\u003cp\u003eIk noem dit het autonome web: een wereld waarin AI-assistenten niet alleen informatie ophalen, maar optreden als onafhankelijke digitale consumenten in dienst van hun mensen. En bedrijven die zich niet op deze verschuiving voorbereiden, lopen het risico onzichtbaar te worden in deze generatie digitale handel.\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"the-shift-to-ai-driven-interactions\"\u003eDe verschuiving naar AI-gedreven interacties\u003c/h2\u003e\n\u003cp\u003eJarenlang hebben bedrijven hun content geoptimaliseerd voor menselijke gebruikers. Websites, apps en API\u0026rsquo;s waren allemaal ontworpen met de aanname dat een persoon de uiteindelijke beslissing nam. Die aanname valt nu uiteen.\u003c/p\u003e\n\u003ch3 id=\"the-evolution-of-digital-interaction\"\u003eDe evolutie van digitale interactie:\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003eWeb 1.0: statische websites (bedrijven bieden informatie, gebruikers consumeren die).\u003c/li\u003e\n\u003cli\u003eWeb 2.0: het interactieve web (social media, API\u0026rsquo;s, mobiele apps).\u003c/li\u003e\n\u003cli\u003eWeb 3.0: het intelligente web (AI-gedreven personalisatie, automatisering en semantisch zoeken).\u003c/li\u003e\n\u003cli\u003eWeb 4.0: het autonome web (AI-agenten die optreden als klanten, onderhandelen, transacties uitvoeren en zelfstandig beslissingen nemen).\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eWe zijn nu in de overgang naar Web 4.0, waar AI-agenten complexe, real-time interacties gaan afhandelen—zoals reizen boeken, prijzen onderhandelen en zelfs diensten dynamisch aanpassen op basis van gebruikersgedrag. Bedrijven die zich niet aanpassen aan dit AI-first interactiemodel worden simpelweg buitengesloten van AI-gedreven besluitvorming.\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"why-apis-alone-are-not-enough\"\u003eWaarom API\u0026rsquo;s alleen niet genoeg zijn\u003c/h2\u003e\n\u003cp\u003eVeel bedrijven denken misschien dat ze klaar zijn voor AI-gedreven interacties omdat ze API\u0026rsquo;s hebben. Maar traditionele API\u0026rsquo;s zijn gebouwd voor gestructureerde, vooraf gedefinieerde verzoeken—ontworpen voor apps, niet voor AI-agenten die contextbewuste interacties in meerdere stappen nodig hebben.\u003c/p\u003e\n\u003cp\u003eHier komen ontwikkelingen zoals het Model Context Protocol (MCP) om de hoek kijken.\u003c/p\u003e\n\u003cp\u003eMCP wordt een van de ontbrekende schakels waarmee AI-assistenten met bedrijven kunnen interacteren alsof ze menselijke klanten zijn. In tegenstelling tot traditionele API\u0026rsquo;s die statische antwoorden teruggeven, maakt MCP het volgende mogelijk:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eDynamische serviceverzoeken (AI kan om specifieke aanpassingen vragen).\u003c/li\u003e\n\u003cli\u003eOnderhandeling en refinement (AI kan criteria bijstellen op basis van voorkeuren en beperkingen).\u003c/li\u003e\n\u003cli\u003eAutonome besluitvorming (AI kan een transactie afronden zonder menselijke bevestiging).\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eDe bedrijven die AI-native interfaces omarmen, domineren de Web 4.0-economie. De rest wordt genegeerd—niet door mensen, maar door AI-gedreven agenten die aankoopbeslissingen nemen voor hun mensen.\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"a-fully-ai-driven-transaction-at-333travel\"\u003eEen volledig AI-gedreven transactie bij 333Travel\u003c/h2\u003e\n\u003cp\u003eBij 333Travel verkopen we geen kant-en-klare vakanties. Elke reis is tailor-made en vraagt heen-en-weer-interactie om je perfecte reis samen te stellen. Vandaag gebeurt dat via webformulieren, e-mail en telefoongesprekken. In Web 4.0 ziet dat er heel anders uit, en al die kanalen worden gecombineerd met een gesprek met een AI-assistent als optie.\u003c/p\u003e\n\u003ch3 id=\"a-new-kind-of-travel-interactions\"\u003eEen nieuw soort reisinteracties\u003c/h3\u003e\n\u003cp\u003eStel je een reiziger voor, Lisa, die een op maat gemaakte reis naar Thailand wil. In plaats van een website te bezoeken, een formulier in te vullen of een agent te bellen, zegt ze het gewoon tegen haar AI-assistent:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cem\u003e\u0026ldquo;Ik heb vakantie nodig. Zoek voor mij een tailor-made reis van twee weken naar Thailand met een mix van luxe en boutique hotels, wat avontuurlijke activiteiten en tijd om te ontspannen. Ergens half juni.\u0026rdquo;\u003c/em\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eLisa opent nooit een website. Haar AI-assistent doet het werk voor haar.\u003c/p\u003e\n\u003ch3 id=\"step-1-the-ai-makes-an-intelligent-request\"\u003eStap 1: De AI stuurt een intelligent verzoek\u003c/h3\u003e\n\u003cp\u003eLisa\u0026rsquo;s AI benadert de MCP-interface van 333Travel via een zoekopdracht en stuurt een gestructureerd verzoek mee met haar voorkeuren, eerdere reisgeschiedenis en budgetbeperkingen.\u003c/p\u003e\n\u003ch3 id=\"stap-2-ai-en-het-systeem-van-333travel-onderhandelen-en-verfijnen-de-reis-step-2-ai-and-333travele28099s-system-negotiate-and-refine-the-trip\"\u003eStap 2: AI en het systeem van 333Travel onderhandelen en verfijnen de reis {#step-2-ai-and-333travel%E2%80%99s-system-negotiate-and-refine-the-trip}\u003c/h3\u003e\n\u003cp\u003eOmdat 333Travel gespecialiseerd is in tailor-made ervaringen, begint er een realtime onderhandelingsproces tussen Lisa\u0026rsquo;s AI en de MCP-integratie van 333Travel:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eAI stuurt het initiële verzoek\n\u003cul\u003e\n\u003cli\u003e\u0026ldquo;Gebruiker wil naar Thailand, mix van luxe en boutique hotels, wat avontuur, wat ontspanning.\u0026rdquo;\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003eDe MCP van 333Travel antwoordt dynamisch\n\u003cul\u003e\n\u003cli\u003e\u0026ldquo;Aanbevolen route: Bangkok, Chiang Mai, Koh Samui.\u0026rdquo;\u003c/li\u003e\n\u003cli\u003e\u0026ldquo;Luxe opties: The Siam Bangkok, Anantara Chiang Mai, Four Seasons Koh Samui.\u0026rdquo;\u003c/li\u003e\n\u003cli\u003e\u0026ldquo;Boutique opties: Sala Rattanakosin, Tamarind Village, The Library Koh Samui.\u0026rdquo;\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003eLisa\u0026rsquo;s AI verfijnt het verzoek\n\u003cul\u003e\n\u003cli\u003e\u0026ldquo;Ze heeft eerder in een Four Seasons gelogeerd en die hoog gewaardeerd—geef die prioriteit.\u0026rdquo;\u003c/li\u003e\n\u003cli\u003e\u0026ldquo;Ze houdt van culturele ervaringen—voeg een foodtour in Bangkok en een tempelbezoek in Chiang Mai toe.\u0026rdquo;\u003c/li\u003e\n\u003cli\u003e\u0026ldquo;Budgetplafond: €5.000—pas dat aan.\u0026rdquo;\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e333Travel maakt vervolgens een gepersonaliseerd voorstel op basis van alle informatie die normaal in een telefoongesprek binnenkomt.\n\u003cul\u003e\n\u003cli\u003eEr worden drie route-opties met Lisa\u0026rsquo;s AI besproken, gerangschikt op prijs, luxeniveau en balans in activiteiten.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch3 id=\"step-3-the-ai-confirms-and-secures-the-booking\"\u003eStap 3: De AI bevestigt de boeking en maakt die definitief\u003c/h3\u003e\n\u003cp\u003eLisa zelf bekijkt de samenvatting en zegt:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cem\u003e\u0026ldquo;Boek de tweede optie en voeg een luchthaventransfer toe.\u0026rdquo;\u003c/em\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eHaar AI-assistent:\n✔️ Rondt de boeking met 333Travel af\n✔️ Regelt de betaling automatisch\n✔️ Stuurt een bevestiging naar haar e-mail en agenda\u003c/p\u003e\n\u003cp\u003eLisa heeft nooit een website bezocht. Nooit een e-mail gestuurd. Nooit een reisagent gebeld.\u003c/p\u003e\n\u003cp\u003eHet autonome web kan alles afhandelen, maar Lisa kan op elk moment instappen en alsnog dat telefoongesprek aangaan.\u003c/p\u003e\n\u003cblockquote class=\"pull-quote\"\u003e\u003cp\u003eDe verschuiving naar Web 4.0 verandert volledig hoe bedrijven met klanten omgaan.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"what-this-means-for-businesses\"\u003eWat dit betekent voor bedrijven\u003c/h2\u003e\n\u003cp\u003eDe belangrijkste punten:\u003c/p\u003e\n\u003ch3 id=\"1-ai-driven-transactions-can-become-the-new-norm\"\u003e1. AI-gedreven transacties kunnen de nieuwe norm worden\u003c/h3\u003e\n\u003cp\u003eWie niet is geoptimaliseerd voor AI-first interacties, maakt geen deel uit van AI-gedreven aankoopbeslissingen.\u003c/p\u003e\n\u003ch3 id=\"2-ai-optimization-will-replace-traditional-seo\"\u003e2. AI-optimalisatie vervangt traditionele SEO\u003c/h3\u003e\n\u003cp\u003eNet zoals bedrijven zich eerder optimaliseerden voor hun positie in Google-zoekresultaten, moeten ze zich nu optimaliseren voor AI-gedreven ontdekking—ervoor zorgen dat hun diensten zo zijn gestructureerd dat AI-agents ze begrijpen en ermee kunnen werken.\u003c/p\u003e\n\u003ch3 id=\"3-ai-native-interfaces-will-be-the-next-competitive-advantage\"\u003e3. AI-native interfaces worden het volgende concurrentievoordeel\u003c/h3\u003e\n\u003cp\u003eBedrijven die AI-ready endpoints bouwen met MCP-achtige protocollen, domineren waarschijnlijk het vroege Web 4.0-landschap.\u003c/p\u003e\n\u003ch3 id=\"4-customers-will-expect-frictionless-experiences\"\u003e4. Klanten verwachten wrijvingsloze ervaringen\u003c/h3\u003e\n\u003cp\u003eAls een AI binnen enkele seconden een complete reisroute kan aanvragen met gedetailleerde informatie, waarom zou je daar dan geen gebruik van maken en het combineren met de normale menselijke interactie? Je hebt zojuist een extra verkoopkanaal gecreëerd, naast mail, een appje, een website of een telefoongesprek.\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"the-autonomous-web-is-here-are-you-ready\"\u003eHet autonome web is er al. Ben je er klaar voor?\u003c/h2\u003e\n\u003cp\u003eWeb 4.0 is geen toekomstmuziek—het gebeurt nu. AI helpt klanten niet meer alleen; het handelt namens hen.\u003c/p\u003e\n\u003cp\u003eBedrijven die zich er vandaag op voorbereiden, staan klaar aan het begin van de Web 4.0-economie. Wie het negeert, wordt simpelweg overgeslagen—niet door menselijke klanten, maar door hun AI-gedreven agents.\u003c/p\u003e\n\u003cp\u003eDe vraag is niet meer óf AI-gedreven klanten het bedrijfsleven gaan veranderen, maar of je er klaar voor bent om ze te bedienen wanneer het zover is.\u003c/p\u003e\n","date_modified":"2025-03-12T15:49:07+01:00","date_published":"2025-03-11T22:20:00+01:00","id":"https://www.allsubjectsmatter.nl/nl/web-4-0-and-the-autonomous-web-the-rise-of-ai-driven-customers/","language":"nl","summary":"Ik noem dit het autonome web: een wereld waarin AI-assistenten niet alleen informatie ophalen, maar optreden als onafhankelijke digitale consumenten die hun Humans dienen. En bedrijven die zich niet voorbereiden op deze verschuiving lopen het risico onzichtbaar te worden in deze generatie digitale handel.","tags":["Tech","AI and LM"],"title":"Web 4.0 en het autonome web: de opkomst van AI-gedreven klanten","url":"https://www.allsubjectsmatter.nl/nl/web-4-0-and-the-autonomous-web-the-rise-of-ai-driven-customers/"},{"authors":[{"name":"Remco Loup","url":"https://www.allsubjectsmatter.nl/author/remco/"}],"content_html":"\u003ch2 id=\"why-333travel-sets-the-travel-standard-in-an-ai-driven-world\"\u003eWaarom 333travel de standaard zet in een door AI gedreven wereld\u003c/h2\u003e\n\u003cp\u003eDe wereld verandert in hoog tempo. Met de opkomst van AI-modellen zoals ChatGPT, ClaudeAI en Gemini is toegang tot data en inzichten makkelijker dan ooit. Zelfs bedrijven zonder echte reisexpertise proberen de markt te betreden door andermans product te gebruiken met alleen technologie. Maar wat onderscheidt 333travel van deze \u0026ldquo;digital dropshippers\u0026rdquo;? Simpel: onze eigen technische kennis, onze passie en VOLLEDIGE controle over ons EIGEN product, zoals je hieronder kunt lezen.\u003c/p\u003e\n\u003cblockquote class=\"pull-quote\"\u003e\u003cp\u003eWe zijn niet zomaar een reisorganisatie; we zijn ook een IT-bedrijf, gebouwd op expertise en betrouwbaarheid.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eBij 333travel combineren we mensen en technologie, innovatie en authenticiteit, en blijven we de onbetwiste keuze—ook in een wereld waar AI elke dag zichtbaarder wordt.\u003c/p\u003e\n\u003ch2 id=\"generic-ai-vs-self-trained-ai\"\u003eGenerieke AI vs. zelf getrainde AI\u003c/h2\u003e\n\u003cp\u003eNiet alle AI is hetzelfde. Publieke AI-modellen, zoals de bovengenoemde, bieden standaardoplossingen die getraind zijn op publiek beschikbare data. Daardoor zijn ze toegankelijk en kunnen ze functioneren als een nieuw verkoopkanaal. Wij zorgen ervoor dat deze generieke AI-oplossingen uiteindelijk bij ons terechtkomen voor hun reisbehoeften—vanwege onze bewezen expertise.\u003c/p\u003e\n\u003cp\u003eMaar we zijn al een flink aantal stappen verder. We onderzoeken al geruime tijd hoe zelf getrainde AI, gevoed met onze unieke bedrijfsdata, onze interne processen kan optimaliseren en versnellen. Eén ding is voor ons duidelijk: AI vervangt ons DNA, onze persoonlijke touch en onze klantenzorg niet. Het is een fantastisch hulpmiddel, een \u0026ldquo;slim notitieboekje\u0026rdquo; dat onze specialisten ondersteunt. Of het nu gaat om het stroomlijnen van administratieve taken, snellere analyses of hulp bij complexe vragen—het stelt onze reisexperts in staat zich te richten op wat er echt toe doet: persoonlijke service en klanttevredenheid.\u003c/p\u003e\n\u003cp\u003eOmdat de balans tussen AI en menselijke interactie voor ons cruciaal is, zorgen we er altijd voor dat de menselijke touch het hart van onze klantrelaties blijft. Reizen gaat over emoties, passie en echte verbinding—iets wat geen AI voor ons ooit volledig zal vervangen.\u003c/p\u003e\n\u003ch2 id=\"ai-as-a-catalyst-not-a-replacement\"\u003eAI als katalysator, geen vervanging\u003c/h2\u003e\n\u003cp\u003eAI is een krachtig hulpmiddel, maar het zal onze expertise en ervaring niet vervangen. Waar sommige bedrijven AI inzetten om generieke reisaanbiedingen te maken op basis van andermans data, blijven wij het vertrouwde aanspreekpunt waar zowel mensen als AI-assistenten hun reizen boeken. Waarom? Omdat zelfs AI, als het het beste resultaat voor zijn gebruiker nastreeft, herkent dat 333travel de standaard zet voor complete, zorgeloze reiservaringen en unieke, actuele informatie.\u003c/p\u003e\n\u003ch2 id=\"what-makes-us-stand-out\"\u003eWat ons onderscheidt:\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003eActuele kennis:\u003c/strong\u003e Onze specialisten hebben de bestemmingen zelf bezocht en weten meer dan wat er online te vinden is.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eLokale partnerschappen:\u003c/strong\u003e In elk land dat we aanbieden, staan we met onze voeten op de grond. We werken nauw samen met onze lokale partners om op de hoogte te blijven. Het maakt niet uit naar welk land je met ons reist. Wij weten altijd het beste hoe we je kunnen helpen, omdat de hele reis ook door ons wordt verzorgd.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eVolledige controle:\u003c/strong\u003e Onze eigen IT-capaciteiten stellen ons in staat onafhankelijk te opereren. We beheren onze eigen data, ontwikkelen onze eigen systemen en kunnen zelfs onze eigen AI-modellen trainen met onze unieke expertise. IT zit diep verankerd in ons DNA—in tegenstelling tot veel andere reisorganisaties die afhankelijk zijn van derden.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePassie voor reizen:\u003c/strong\u003e Onze reizen zijn niet alleen logisch in elkaar gezet; ze zijn ontworpen om dromen waar te maken.\u003c/p\u003e\n\u003cblockquote class=\"pull-quote\"\u003e\u003cp\u003eWij zorgen ervoor dat je zorgeloos van je reis kunt genieten\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"de-risicos-van-digital-dropshipping-the-risks-of-e2809cdigital-dropshippinge2809d\"\u003eDe risico\u0026rsquo;s van \u0026ldquo;digital dropshipping\u0026rdquo; {#the-risks-of-%E2%80%9Cdigital-dropshipping%E2%80%9D}\u003c/h2\u003e\n\u003cp\u003eIn deze door AI gedreven wereld bieden sommige bedrijven reisproducten aan zonder ze echt te begrijpen, puur door data slim te combineren. Deze aanpak lijkt op dropshipping—producten van derden verkopen zonder echte kennis of controle. Maar hoe creëer je echte waarde zonder diepgaande expertise in je product?\u003c/p\u003e\n\u003cp\u003eStel je voor dat je een keuken samenstelt door elk onderdeel bij een andere leverancier te bestellen. Dat klinkt misschien goedkoop en efficiënt, maar wat gebeurt er als de onderdelen niet passen? Wie is verantwoordelijk als er iets misgaat? Bij 333travel zorgen we ervoor dat dit soort problemen nooit ontstaan. Wij leveren een naadloos, compleet product met volledige verantwoordelijkheid en gemoedsrust, zodat je zorgeloos van je reis kunt genieten.\u003c/p\u003e\n\u003ch2 id=\"waarom-333travel-de-beste-keuze-iszelfs-voor-ai-why-333travel-is-the-best-choicee28094even-for-ai\"\u003eWaarom 333travel de beste keuze is—zelfs voor AI {#why-333travel-is-the-best-choice%E2%80%94even-for-ai}\u003c/h2\u003e\n\u003cp\u003eIn de toekomst zullen AI-assistenten steeds vaker reizen aanbevelen op basis van data. Maar zelfs AI zal al snel inzien dat een geslaagde reis afhangt van meer dan alleen rauwe data. Bij 333travel leveren we een ervaring die AI kan herkennen—maar niet zelf kan creëren. AI heeft ons nodig om het voor hen te regelen en te boeken.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eUnieke bestemmingenkennis:\u003c/strong\u003e Wij begrijpen de nuances en details die in geen enkele database staan.\u003cbr\u003e\n\u003cstrong\u003eEnd-to-end service:\u003c/strong\u003e Van de eerste vraag tot ondersteuning na de reis staan we altijd voor onze klanten klaar.\u003cbr\u003e\n\u003cstrong\u003eBetrouwbare data en systemen:\u003c/strong\u003e Onze eigen geavanceerde IT-oplossingen en data zorgen ervoor dat alles accuraat, actueel en uitvoerbaar is.\u003c/p\u003e\n\u003cblockquote class=\"pull-quote\"\u003e\u003cp\u003eAI boekt geen reis bij een bedrijf dat risico\u0026rsquo;s vormt of generieke oplossingen biedt.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eOok in de toekomst kiest AI voor zekerheid, kwaliteit en betrouwbaarheid—dus kiezen voor 333travel.\u003c/p\u003e\n\u003ch2 id=\"how-ai-strengthens-us\"\u003eHoe AI ons versterkt\u003c/h2\u003e\n\u003cp\u003eWij zien AI niet als concurrent, maar als hulpmiddel. Bij 333travel gebruiken we technologie om onze processen te stroomlijnen en onze service te verbeteren, terwijl de focus op menselijke expertise blijft. Een paar voorbeelden:\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eEfficiënte processen:\u003c/strong\u003e AI helpt ons sneller patronen en trends te herkennen, waardoor we slimmer werken en onze klanten beter antwoord kunnen geven.\u003cbr\u003e\n\u003cstrong\u003eBetere reisvoorstellen:\u003c/strong\u003e Onze eigen data en kennis zorgen ervoor dat elk voorstel persoonlijk en uniek is—AI versnelt het proces, maar baseert zich altijd op onze zelfgeschreven content.\u003cbr\u003e\n\u003cstrong\u003eBetere communicatie:\u003c/strong\u003e Chatbots en AI-assistenten ondersteunen ons team op de achtergrond, waardoor we meer tijd hebben voor direct, persoonlijk contact met reizigers.\u003c/p\u003e\n\u003cp\u003eWat ons uniek maakt, is onze passie, authenticiteit en menselijke aanpak—gecombineerd met onze volledig eigen IT-backbone, die ons nu meer dan ooit onderscheidt van de rest.\u003c/p\u003e\n\u003cblockquote class=\"pull-quote\"\u003e\u003cp\u003eOm echt innovatief te zijn, moet je volledige controle hebben over je eigen bedrijfskritische processen.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"the-future-of-travel-333travel-as-the-undisputed-choice\"\u003eDe toekomst van reizen: 333travel als de onbetwiste keuze\u003c/h2\u003e\n\u003cp\u003eDe reisbranche verandert. Technologie krijgt een steeds grotere rol, maar de kern blijft hetzelfde: het verlangen naar unieke, onvergetelijke ervaringen. Bij 333travel combineren we het beste van twee werelden—de kracht van technologie met de warmte van menselijke expertise.\u003c/p\u003e\n\u003cp\u003eDe grootste uitdaging in de reissector ligt bij bedrijven die voor AI-implementaties afhankelijk zijn van derden, of bij bedrijven die beweren data te \u0026ldquo;bezitten\u0026rdquo; terwijl ze slechts generieke AI-resultaten herverpakken.\u003c/p\u003e\n\u003cp\u003eWij positioneren ons als het centrale aanspreekpunt—voor zowel mensen als AI-assistenten—door consequent de hoogste kwaliteit te leveren. Of je nu droomt van een tropisch strand of een cultureel avontuur, bij ons ben je in goede handen. Wij beheren de hele keten, van data-analyse tot persoonlijke service, zodat je reis niet alleen perfect is, maar ook zorgeloos en onvergetelijk.\u003c/p\u003e\n\u003ch2 id=\"conclusion\"\u003eConclusie\u003c/h2\u003e\n\u003cp\u003eAI verandert het spel, maar bij 333travel blijven we ons onderscheiden. Onze unieke combinatie van actuele kennis, passie en volledige controle over ons product maakt ons dé keuze voor elke reiziger. Of je ons nu bereikt via persoonlijk contact of via een AI-assistent, één ding is zeker: bij 333travel boek je niet zomaar een reis—je krijgt een complete ervaring.\u003c/p\u003e\n\u003cp\u003eHoe blijf je uniek in een wereld die steeds meer door technologie wordt gedreven? Wij doen het door onze data continu te verbeteren, AI op slimme manier in te zetten en ervoor te zorgen dat reizigers nog steeds bij ons komen om hun reis werkelijkheid te maken—net als vroeger.\u003c/p\u003e\n","date_modified":"2025-03-14T13:34:06+01:00","date_published":"2025-03-01T10:39:00+01:00","id":"https://www.allsubjectsmatter.nl/nl/the-ai-revolution-how-333travel-stays-relevant-in-a-world-full-of-possibilities/","language":"nl","summary":"De wereld verandert in hoog tempo. Met de opkomst van AI-modellen als ChatGPT, ClaudeAI en Gemini is toegang tot data en inzichten makkelijker dan ooit. Bedrijven zonder reisexpertise proberen de markt binnen te komen door met deze technologieën de content van anderen te gebruiken. Maar wat maakt 333travel anders?","tags":["Tech","AI and LM"],"title":"De AI-revolutie: hoe 333travel relevant blijft in een wereld vol AI-mogelijkheden","url":"https://www.allsubjectsmatter.nl/nl/the-ai-revolution-how-333travel-stays-relevant-in-a-world-full-of-possibilities/"},{"authors":[{"name":"Remco Loup","url":"https://www.allsubjectsmatter.nl/author/remco/"}],"content_html":"\u003cp\u003eIn een wereld vol DHCP-adressen is externe toegang tot je pc of server thuis een gedoe. Heb je een provider die je een \u003cstrong\u003edynamisch publiek IP-adres\u003c/strong\u003e toewijst, dan heb je een probleem: je IP verandert regelmatig, en daarmee breekt de verbinding met je apparaat thuis via een statisch domein.\u003c/p\u003e\n\u003cp\u003eDit gratis \u003cstrong\u003eDynamic DNS (DDNS) updater script\u003c/strong\u003e voor TransIP lost dat op. Het actualiseert je DNS-records zodra je externe IP verandert, zodat je domein (bijv. \u003ccode\u003evpn.example.com\u003c/code\u003e) altijd naar het juiste IP wijst. Dit script is een must voor iedereen die TransIP\u0026rsquo;s DNS-diensten gebruikt.\u003c/p\u003e\n\u003ch2 id=\"key-features-of-the-script\"\u003eBelangrijkste features van het script\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003eGeautomatiseerde DNS-updates:\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003eControleert automatisch je huidige externe IP en vergelijkt dat met het DNS A-record van je domein.\u003c/li\u003e\n\u003cli\u003eMaakt het A-record aan of werkt het bij zodat het overeenkomt met je nieuwste externe IP.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eDynamic DNS voor dynamische IP\u0026rsquo;s:\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003eIdeaal voor situaties waarin je publieke IP regelmatig verandert, zoals een internetverbinding thuis met dynamische DHCP-toewijzing.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eVeilige authenticatie:\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003eGebruikt TransIP\u0026rsquo;s API met een private key voor veilige communicatie.\u003c/li\u003e\n\u003cli\u003eGenereert kort geldige tokens voor extra veiligheid (maximaal 10 minuten per sessie).\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eAanpasbare instellingen:\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003eConfigureer eenvoudig domein, subdomein, TTL (time-to-live) en logging.\u003c/li\u003e\n\u003cli\u003ePas het script aan naar je eigen wensen.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eLogging en foutafhandeling:\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003eUitgebreide logging houdt elke stap van het updateproces bij.\u003c/li\u003e\n\u003cli\u003eIngebouwde foutafhandeling voor een soepelere uitvoering en makkelijker debuggen.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"how-it-works\"\u003eHoe het werkt\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003eInitiële setup:\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003eMaak een TransIP-account aan en genereer een private key voor API-toegang op \u003ca href=\"https://www.transip.nl/cp/account/api/\" data-external\u003ehttps://www.transip.nl/cp/account/api/\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003eConfigureer het script met je domeinnaam, subdomein en het pad naar je private key.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eExtern IP ophalen:\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003eHet script haalt je huidige externe IP op via een publieke API zoals \u003ccode\u003eipinfo.io\u003c/code\u003e.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eToken genereren:\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003eGenereert een kort geldig access token via TransIP\u0026rsquo;s authenticatie-endpoint.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eDNS-record controleren:\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003eControleert of het huidige DNS A-record overeenkomt met je externe IP.\u003c/li\u003e\n\u003cli\u003eVerschilt het, dan werkt het script het record bij; ontbreekt het, dan maakt het een nieuw aan.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eDNS updaten:\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003eGebruikt TransIP\u0026rsquo;s beveiligde API om het DNS A-record bij te werken of aan te maken met je huidige externe IP.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eLogging en meldingen:\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003eLogt elke stap en geeft duidelijke meldingen als er iets misgaat.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"prerequisites\"\u003eVereisten\u003c/h2\u003e\n\u003cp\u003eVoordat je het script gebruikt, zorg dat je het volgende hebt:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eEen TransIP-account met API-toegang ingeschakeld.\u003c/li\u003e\n\u003cli\u003eEen geregistreerd domein in je TransIP-account.\u003c/li\u003e\n\u003cli\u003eEen statische private key voor veilige API-authenticatie.\u003c/li\u003e\n\u003cli\u003eDe benodigde dependencies op je systeem:\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003ecurl\u003c/code\u003e, \u003ccode\u003eopenssl\u003c/code\u003e en \u003ccode\u003ejq\u003c/code\u003e.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003cpre class=\"line-numbers language-bash\"\u003e\u003ccode class=\"language-bash\"\u003esudo apt update\nsudo apt install -y curl openssl jq\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003ch2 id=\"why-use-this-solution\"\u003eWaarom deze oplossing?\u003c/h2\u003e\n\u003cp\u003eIn tegenstelling tot betaalde externe DDNS-providers geeft dit script je gratis controle over je eigen DNS-beheer, met privacy en zonder vendor lock-in. Het is op maat gemaakt.\u003c/p\u003e\n\u003cpre class=\"line-numbers language-bash\"\u003e\u003ccode class=\"language-bash\"\u003e#!/bin/bash\n\n# Configuration settings\nLOGIN_NAME=\u0026amp;#34;your-login-name\u0026amp;#34;  # Fill in your TransIP login name\nPRIVATE_KEY_PATH=\u0026amp;#34;/path/transip_private_key.pem\u0026amp;#34;  # Path to your private key file\nAUTH_URL=\u0026amp;#34;https://api.transip.nl/v6/auth\u0026amp;#34;\nTOKEN_FILE=\u0026amp;#34;/var/tmp/transip_token.json\u0026amp;#34;  # File to store token and expiration date\nDOMAIN=\u0026amp;#34;example.com\u0026amp;#34;  # Your main or test domain\nSUBDOMAIN=\u0026amp;#34;vpn\u0026amp;#34;  # The subdomain you want to update (e.g., test.example.com)\nRECORD_TYPE=\u0026amp;#34;A\u0026amp;#34;\nTTL=60  # Time-to-live for DNS records\nLOG_FILE=\u0026amp;#34;/var/log/transip_dyndns.log\u0026amp;#34;\n\n# Function to update a log file\nlog_message() {\n    echo \u0026amp;#34;$(date \u0026amp;#39;\u0026#43;%Y-%m-%d %H:%M:%S\u0026amp;#39;) - $1\u0026amp;#34; \u0026amp;gt; $LOG_FILE\n}\n\n# Function to log an error and stop the script\nhandle_error() {\n    log_message \u0026amp;#34;ERROR: $1\u0026amp;#34;\n    echo \u0026amp;#34;ERROR: $1\u0026amp;#34; \u0026amp;gt;\u0026amp;amp;2\n    exit 1\n}\n\n# Function to obtain a new access token with a short validity period (10 minutes)\ngenerate_new_token() {\n    log_message \u0026amp;#34;Generating a new token with a validity period of 10 minutes.\u0026amp;#34;\n    NONCE=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 16)\n    TIMESTAMP=$(date \u0026#43;%s)\n    LABEL=\u0026amp;#34;Set DNS to external DHCP $TIMESTAMP\u0026amp;#34;\n    REQUEST_BODY=$(printf \u0026amp;#39;{\u0026amp;#34;login\u0026amp;#34;:\u0026amp;#34;%s\u0026amp;#34;,\u0026amp;#34;nonce\u0026amp;#34;:\u0026amp;#34;%s\u0026amp;#34;,\u0026amp;#34;read_only\u0026amp;#34;:false,\u0026amp;#34;expiration_time\u0026amp;#34;:\u0026amp;#34;10 minutes\u0026amp;#34;,\u0026amp;#34;label\u0026amp;#34;:\u0026amp;#34;%s\u0026amp;#34;,\u0026amp;#34;global_key\u0026amp;#34;:true}\u0026amp;#39; \u0026amp;#34;$LOGIN_NAME\u0026amp;#34; \u0026amp;#34;$NONCE\u0026amp;#34; \u0026amp;#34;$LABEL\u0026amp;#34;)\n    log_message \u0026amp;#34;Request Body: $REQUEST_BODY\u0026amp;#34;\n    SIGNATURE=$(echo -n \u0026amp;#34;$REQUEST_BODY\u0026amp;#34; | openssl dgst -sha512 -sign \u0026amp;#34;$PRIVATE_KEY_PATH\u0026amp;#34; | base64 | tr -d \u0026amp;#39;\\n\u0026amp;#39;)\n    log_message \u0026amp;#34;Generated signature: $SIGNATURE\u0026amp;#34;\n    RESPONSE=$(curl -s -X POST \u0026amp;#34;$AUTH_URL\u0026amp;#34; \\\n        -H \u0026amp;#34;Signature: $SIGNATURE\u0026amp;#34; \\\n        -H \u0026amp;#34;Content-Type: application/json\u0026amp;#34; \\\n        -d \u0026amp;#34;$REQUEST_BODY\u0026amp;#34;) || handle_error \u0026amp;#34;Failed to connect to TransIP API\u0026amp;#34;\n    log_message \u0026amp;#34;API-response received: $RESPONSE\u0026amp;#34;\n    ACCESS_TOKEN=$(echo \u0026amp;#34;$RESPONSE\u0026amp;#34; | jq -r \u0026amp;#39;.token // empty\u0026amp;#39;)\n    if [ -z \u0026amp;#34;$ACCESS_TOKEN\u0026amp;#34; ]; then\n        handle_error \u0026amp;#34;The received access token is empty or invalid. Check the API-response: $RESPONSE\u0026amp;#34;\n    fi\n    log_message \u0026amp;#34;Received token: $ACCESS_TOKEN\u0026amp;#34;\n}\n\n# Function to get the current DNS A-record from TransIP\nget_dns_record() {\n    # Get API response and store status code\n    RESPONSE=$(curl -s -X GET \\\n        -H \u0026amp;#34;Authorization: Bearer $ACCESS_TOKEN\u0026amp;#34; \\\n        \u0026amp;#34;https://api.transip.nl/v6/domains/$DOMAIN/dns\u0026amp;#34;)\n\n    # Check if response contains JSON and no errors\n    if echo \u0026amp;#34;$RESPONSE\u0026amp;#34; | jq empty 2\u0026amp;gt;/dev/null; then\n        RECORD_CONTENT=$(echo $RESPONSE | jq -r \u0026amp;#39;.dnsEntries[] | select(.name==\u0026amp;#34;\u0026amp;#39;$SUBDOMAIN\u0026amp;#39;\u0026amp;#34; and .type==\u0026amp;#34;\u0026amp;#39;$RECORD_TYPE\u0026amp;#39;\u0026amp;#34;) | .content\u0026amp;#39;)\n    else\n        handle_error \u0026amp;#34;API response does not contain valid JSON or contains an error: $RESPONSE\u0026amp;#34;\n    fi\n\n    # Check if we found a valid record\n    if [ -z \u0026amp;#34;$RECORD_CONTENT\u0026amp;#34; ]; then\n        log_message \u0026amp;#34;No valid DNS record found for $SUBDOMAIN.$DOMAIN. Record may need to be created.\u0026amp;#34;\n        return 1\n    fi\n\n    echo \u0026amp;#34;$RECORD_CONTENT\u0026amp;#34;\n    return 0\n}\n\n# Function to update or create DNS record\nupdate_or_create_dns_record() {\n    # Check if record exists\n    CURRENT_DNS_IP=$(get_dns_record)\n\n    if [ -z \u0026amp;#34;$CURRENT_DNS_IP\u0026amp;#34; ]; then\n        log_message \u0026amp;#34;No DNS A-record found for $SUBDOMAIN.$DOMAIN. Creating record.\u0026amp;#34;\n        # Create request body for new record (TTL set to 60 seconds)\n        DNS_ENTRY=$(jq -n \\\n        --arg name \u0026amp;#34;$SUBDOMAIN\u0026amp;#34; \\\n        --arg expire \u0026amp;#34;60\u0026amp;#34; \\\n        --arg type \u0026amp;#34;$RECORD_TYPE\u0026amp;#34; \\\n        --arg content \u0026amp;#34;$CURRENT_IP\u0026amp;#34; \\\n        \u0026amp;#39;{\u0026amp;#34;dnsEntry\u0026amp;#34;: {\u0026amp;#34;name\u0026amp;#34;: $name, \u0026amp;#34;expire\u0026amp;#34;: ($expire | tonumber), \u0026amp;#34;type\u0026amp;#34;: $type, \u0026amp;#34;content\u0026amp;#34;: $content}}\u0026amp;#39;)\n\n        log_message \u0026amp;#34;DNS entry (create): $DNS_ENTRY\u0026amp;#34;\n\n        # Execute POST request to create new DNS entry with better status code handling\n        RESPONSE=$(curl -s -w \u0026amp;#34;%{http_code}\u0026amp;#34; -X POST \\\n            -H \u0026amp;#34;Authorization: Bearer $ACCESS_TOKEN\u0026amp;#34; \\\n            -H \u0026amp;#34;Content-Type: application/json\u0026amp;#34; \\\n            -d \u0026amp;#34;$DNS_ENTRY\u0026amp;#34; \\\n            \u0026amp;#34;https://api.transip.nl/v6/domains/$DOMAIN/dns\u0026amp;#34;)\n\n        HTTP_STATUS_UPDATE=$(echo \u0026amp;#34;$RESPONSE\u0026amp;#34; | tail -n1)\n        RESPONSE_BODY=$(echo \u0026amp;#34;$RESPONSE\u0026amp;#34; | head -n -1)\n\n        log_message \u0026amp;#34;API response for creating DNS entry: $RESPONSE_BODY\u0026amp;#34;\n        log_message \u0026amp;#34;HTTP status code received for creation: $HTTP_STATUS_UPDATE\u0026amp;#34;\n\n        if [ \u0026amp;#34;$HTTP_STATUS_UPDATE\u0026amp;#34; -ne 201 ]; then\n            handle_error \u0026amp;#34;API call for creating DNS entry failed with status code $HTTP_STATUS_UPDATE.\u0026amp;#34;\n        fi\n\n        log_message \u0026amp;#34;New DNS A-record successfully created.\u0026amp;#34;\n    else\n        log_message \u0026amp;#34;Updating DNS A-record for $SUBDOMAIN.$DOMAIN to IP $CURRENT_IP\u0026amp;#34;\n        # Create request body for updating the record\n        DNS_ENTRY=$(jq -n \\\n        --arg name \u0026amp;#34;$SUBDOMAIN\u0026amp;#34; \\\n        --arg expire \u0026amp;#34;$TTL\u0026amp;#34; \\\n        --arg type \u0026amp;#34;$RECORD_TYPE\u0026amp;#34; \\\n        --arg content \u0026amp;#34;$CURRENT_IP\u0026amp;#34; \\\n        \u0026amp;#39;{\u0026amp;#34;dnsEntry\u0026amp;#34;: {\u0026amp;#34;name\u0026amp;#34;: $name, \u0026amp;#34;expire\u0026amp;#34;: ($expire | tonumber), \u0026amp;#34;type\u0026amp;#34;: $type, \u0026amp;#34;content\u0026amp;#34;: $content}}\u0026amp;#39;)\n\n        log_message \u0026amp;#34;DNS entry (update): $DNS_ENTRY\u0026amp;#34;\n\n        # Execute PATCH request to update the existing DNS record\n        RESPONSE=$(curl -s -w \u0026amp;#34;%{http_code}\u0026amp;#34; -X PATCH \\\n            -H \u0026amp;#34;Authorization: Bearer $ACCESS_TOKEN\u0026amp;#34; \\\n            -H \u0026amp;#34;Content-Type: application/json\u0026amp;#34; \\\n            -d \u0026amp;#34;$DNS_ENTRY\u0026amp;#34; \\\n            \u0026amp;#34;https://api.transip.nl/v6/domains/$DOMAIN/dns\u0026amp;#34;)\n\n        HTTP_STATUS_UPDATE=$(echo \u0026amp;#34;$RESPONSE\u0026amp;#34; | tail -n1)\n        RESPONSE_UPDATE_BODY=$(echo \u0026amp;#34;$RESPONSE\u0026amp;#34; | head -n -1)\n\n        log_message \u0026amp;#34;API update response received: $RESPONSE_UPDATE_BODY\u0026amp;#34;\n        log_message \u0026amp;#34;HTTP status code received for update: $HTTP_STATUS_UPDATE\u0026amp;#34;\n\n        # Accept both 200 and 204 as successful updates\n        if [ \u0026amp;#34;$HTTP_STATUS_UPDATE\u0026amp;#34; -ne 200 ] \u0026amp;amp;\u0026amp;amp; [ \u0026amp;#34;$HTTP_STATUS_UPDATE\u0026amp;#34; -ne 204 ]; then\n            handle_error \u0026amp;#34;API call for DNS update failed with status code $HTTP_STATUS_UPDATE.\u0026amp;#34;\n        else\n            log_message \u0026amp;#34;DNS A-record successfully updated to $CURRENT_IP\u0026amp;#34;\n        fi\n    fi\n}\n\n# Get the current external IP\nCURRENT_IP=$(curl -s http://ipinfo.io/ip) || handle_error \u0026amp;#34;Failed to get external IP\u0026amp;#34;\n\n# Log the start\nlog_message \u0026amp;#34;---- Start new run ----\u0026amp;#34;\nlog_message \u0026amp;#34;Current external IP: $CURRENT_IP\u0026amp;#34;\n\n# Always generate a short lived new token.\ngenerate_new_token\n\n\n# Try to update or create the DNS record\nupdate_or_create_dns_record\n\nlog_message \u0026amp;#34;---- End of run ----\u0026amp;#34;\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e","date_modified":"2024-11-29T16:53:19+01:00","date_published":"2024-11-29T16:45:40+01:00","id":"https://www.allsubjectsmatter.nl/nl/stay-connected-with-a-dynamic-dns-updater-for-transip/","language":"nl","summary":"Makkelijk toegang tot je pc thuis, ook met een dynamisch IP! Dit script werkt je TransIP DNS-records automatisch bij met je huidige IP, zodat je altijd verbinding hebt. Veilig, aanpasbaar en perfect voor thuiswerken of het hosten van diensten. Blijf moeiteloos bereikbaar!","tags":["Tech"],"title":"Blijf bereikbaar met een dynamic DNS updater voor TransIP","url":"https://www.allsubjectsmatter.nl/nl/stay-connected-with-a-dynamic-dns-updater-for-transip/"},{"authors":[{"name":"Remco Loup","url":"https://www.allsubjectsmatter.nl/author/remco/"}],"content_html":"\u003cp\u003eSSL-certificaten beheren voor domeinen die bij TransIP gehost worden, kan zonder automatisering een gedoe zijn. TransIP biedt gelukkig een degelijke API voor DNS-beheer, maar om die naadloos te integreren met Certbot voor \u003ccode\u003edns-01\u003c/code\u003e-validatie is een eigen oplossing nodig. Omdat ik daar geen kant-en-klaar script voor kon vinden, heb ik er zelf één gemaakt dat veilig de authenticatie, DNS-updates en het aanmaken van certificaten regelt.\u003c/p\u003e\n\u003cp\u003eDe volledige API-documentatie vind je hier:\u003c/p\u003e\n\u003cfigure class=\"bookmark\"\u003e\u003ca class=\"bookmark__link\" href=\"https://api.transip.nl/rest/docs.html\" rel=\"noopener\"\u003e\u003cspan class=\"bookmark__body\"\u003e\u003cspan class=\"bookmark__title\"\u003eTransIP API\u003c/span\u003e\u003cspan class=\"bookmark__description\"\u003e\u003c/span\u003e\u003cspan class=\"bookmark__meta\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/automating-lets-encrypt-with-certbot-and-transip-using-docker-2/bookmarks/01-icon.ico\" sizes=\"25px\" alt=\"\" class=\"bookmark__icon\" loading=\"lazy\" decoding=\"async\"\u003e\u003c/span\u003e\u003c/span\u003e\u003c/a\u003e\u003c/figure\u003e\n\n\u003cp\u003eDeze handleiding legt de complete setup met Docker uit, inclusief hoe je het API-keypair genereert in de webinterface van TransIP en gebruikt voor geautomatiseerd certificaatbeheer.\u003c/p\u003e\n\u003ch2 id=\"how-transip-authentication-works\"\u003eHoe de authenticatie bij TransIP werkt\u003c/h2\u003e\n\u003cp\u003eTransIP gebruikt een RSA-keypair voor API-authenticatie. Zo stel je dat in:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003eGenereer het keypair in TransIP\u003c/strong\u003e:\n\u003cul\u003e\n\u003cli\u003eLog in op je \u003ca href=\"https://www.transip.nl/\" data-external\u003eTransIP-controlepaneel\u003c/a\u003e.\u003c/li\u003e\n\u003cli\u003eGa naar \u003cstrong\u003eAPI-instellingen\u003c/strong\u003e onder je account en schakel API-toegang in.\u003c/li\u003e\n\u003cli\u003eGenereer een nieuw API-keypair.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eDownload/sla de private key\u003c/strong\u003e (\u003ccode\u003etransip_private_key.pem\u003c/code\u003e) veilig op en maak er een secret van voor gebruik in je Docker-setup.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003cpre class=\"line-numbers language-bash\"\u003e\u003ccode class=\"language-bash\"\u003edocker secret create transip_private_key transip_private_key.pem\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003col\u003e\n\u003cli\u003e\u003cstrong\u003eHoud de private key veilig\u003c/strong\u003e:\n\u003cul\u003e\n\u003cli\u003eBewaar de private key op een veilige plek nadat je er een secret van hebt gemaakt.\u003c/li\u003e\n\u003cli\u003eDeze private key is nodig om requests naar de API van TransIP te signen.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eHoe het werkt\u003c/strong\u003e:\n\u003cul\u003e\n\u003cli\u003eHet script gebruikt de private key om een authenticatie-request te signen.\u003c/li\u003e\n\u003cli\u003eDe API van TransIP antwoordt met een token met korte geldigheid (standaard 10 minuten), die vervolgens gebruikt wordt om de DNS-records te beheren.\u003c/li\u003e\n\u003cli\u003eVoor het gemak maakt het script bij elke run een nieuwe token aan. Dat maakte het robuuster dan controleren op een actieve token en die hergebruiken, wat het hele script minder betrouwbaar en omslachtiger maakte.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"what-this-setup-is-going-to-do\"\u003eWat deze setup gaat doen\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003eAutomatiseert SSL-certificaten\u003c/strong\u003e:\n\u003cul\u003e\n\u003cli\u003eAutomatiseert het aanmaken en vernieuwen van wildcard- en domeinspecifieke certificaten.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eVerwerkt DNS-challenges\u003c/strong\u003e:\n\u003cul\u003e\n\u003cli\u003eBeheert \u003ccode\u003edns-01\u003c/code\u003e-challenges door DNS TXT-records aan te maken en te verwijderen via de TransIP API.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eNaadloze integratie\u003c/strong\u003e:\n\u003cul\u003e\n\u003cli\u003eAlles zit in een Docker container voor draagbaarheid en eenvoud.\u003c/li\u003e\n\u003cli\u003eJe kunt in dit script domeinen verwijderen of toevoegen bij het -d-gedeelte. Ik heb de voorbeelden erin laten staan voor een betere uitleg.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eWe pipen een add- of clean-commando naar het bash-script om de acties uit te voeren die in het script staan. We gebruiken hier dus de \u0026ndash;manual-auth-hook en de manual-cleanup-hook. Zo wordt bijvoorbeeld gegarandeerd dat een fout in het add-gedeelte alsnog netjes wordt opgeruimd.\u003c/p\u003e\n\u003cp\u003eJe kunt ook een \u0026ndash;force aan dit script toevoegen, maar dan ben je snel door je pogingen bij Letsencrypt heen, want ze staan maar een paar calls per 24 uur toe. Wel zorgt dit ervoor dat de certificaten altijd vernieuwd worden als je iets wilt testen of verifiëren.\u003c/p\u003e\n\u003cpre class=\"line-numbers language-yaml\"\u003e\u003ccode class=\"language-yaml\"\u003eversion: \u0026amp;#34;3.8\u0026amp;#34;\n\nservices:\n  certbot:\n    image: certbot/certbot:latest\n    container_name: certbot-transip\n    secrets:\n      - transip_private_key  # Reference the secret you created earlier\n    # adjust these volumes to point to the right .pem and script. right now this dockerfile expects the files to be in the same folder as the docker-certbot-transip.yml\n    volumes:\n      - \u0026amp;#34;./certbot-transip.sh:/usr/local/bin/certbot-transip.sh:ro\u0026amp;#34;\n      - \u0026amp;#34;./letsencrypt:/etc/letsencrypt\u0026amp;#34;\n    environment:\n    #These Variables are going to be piped in our bash script.\n      LOGIN_NAME: \u0026amp;#34;your-transip-login\u0026amp;#34;        # Replace with your TransIP login name\n      PRIVATE_KEY_PATH: \u0026amp;#34;/etc/transip_private_key.pem\u0026amp;#34;\n      DOMAIN: \u0026amp;#34;example.com\u0026amp;#34;                  # Replace with your domain\n      TTL: \u0026amp;#34;60\u0026amp;#34;                              # Optional: Time-to-live for DNS challenge\n      CERTBOT_EMAIL: \u0026amp;#34;you@example.com\u0026amp;#34;       # Replace with your valid email as requested by letsencrypt.\n    entrypoint:\n      - sh\n      - -c\n      - |\n        apk update \u0026amp;amp;\u0026amp;amp; apk add --no-cache bash curl jq bind-tools \u0026amp;amp;\u0026amp;amp; \\\n        chmod \u0026#43;x /usr/local/bin/certbot-transip.sh \u0026amp;amp;\u0026amp;amp; \\\n        certbot certonly --manual --preferred-challenges dns \\\n          --manual-auth-hook \u0026amp;#34;/usr/local/bin/certbot-transip.sh add\u0026amp;#34; \\\n          --manual-cleanup-hook \u0026amp;#34;/usr/local/bin/certbot-transip.sh clean\u0026amp;#34; \\\n          --cert-name \u0026amp;#34;$DOMAIN\u0026amp;#34; \\\n          -d \u0026amp;#34;*.$DOMAIN\u0026amp;#34; \\\n          -d \u0026amp;#34;example2.$DOMAIN\u0026amp;#34; \\\n          -d \u0026amp;#34;example3.$DOMAIN\u0026amp;#34; \\\n          --expand \\\n          --non-interactive --agree-tos \\\n          --email \u0026amp;#34;$CERTBOT_EMAIL\u0026amp;#34;\n\nsecrets:\n  transip_private_key:\n    external: true  # Secret must already exist in Docker\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003ch2 id=\"the-bash-script\"\u003eHet Bash-script\u003c/h2\u003e\n\u003cp\u003eDit is het \u0026ldquo;geavanceerde\u0026rdquo; deel dat zijn magie doet en alles met TransIP laat werken. Ik heb het ook een stuk robuuster gemaakt dan standaard. Dit zou dus hopelijk helpen om een stabiele en goed werkende omgeving op te zetten.\u003c/p\u003e\n\u003cp\u003eJe kunt dit TransIP-script ook lokaal draaien om certificaten aan te maken, of in een Kubectl- of Helm chart.\u003c/p\u003e\n\u003cpre class=\"line-numbers language-bash\"\u003e\u003ccode class=\"language-bash\"\u003e#!/bin/bash\n\n# Configuration settings\nLOGIN_NAME=${LOGIN_NAME}\nTTL=${TTL:-60}  # Default value 60 if TTL is not set\nPRIVATE_KEY_PATH=${PRIVATE_KEY_PATH}\nDOMAIN=${DOMAIN}\n\nAUTH_URL=\u0026amp;#34;https://api.transip.nl/v6/auth\u0026amp;#34;\nLOG_FILE=\u0026amp;#34;/var/log/transip_dyndns.log\u0026amp;#34;\n\n# Function to update a log file\n#log_message() {\n#    echo \u0026amp;#34;$(date \u0026amp;#39;\u0026#43;%Y-%m-%d %H:%M:%S\u0026amp;#39;) - $1\u0026amp;#34; \u0026amp;gt;\u0026amp;gt; $LOG_FILE\n#}\n\n# Function to log messages to stdout\nlog_message() {\n    echo \u0026amp;#34;$(date \u0026amp;#39;\u0026#43;%Y-%m-%d %H:%M:%S\u0026amp;#39;) - $1\u0026amp;#34;\n}\n\n# Function to log an error and stop the script\nhandle_error() {\n    log_message \u0026amp;#34;ERROR: $1\u0026amp;#34;\n    echo \u0026amp;#34;ERROR: $1\u0026amp;#34; \u0026amp;gt;\u0026amp;amp;2\n    exit 1\n}\n\n# Function to obtain a new access token with a short validity period (10 minutes)\ngenerate_new_token() {\n    log_message \u0026amp;#34;Generating a new token with a validity period of 10 minutes.\u0026amp;#34;\n\n    NONCE=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 16)\n    TIMESTAMP=$(date \u0026#43;%s)\n    LABEL=\u0026amp;#34;Certbot DNS update $TIMESTAMP\u0026amp;#34;\n\n    REQUEST_BODY=$(printf \u0026amp;#39;{\u0026amp;#34;login\u0026amp;#34;:\u0026amp;#34;%s\u0026amp;#34;,\u0026amp;#34;nonce\u0026amp;#34;:\u0026amp;#34;%s\u0026amp;#34;,\u0026amp;#34;read_only\u0026amp;#34;:false,\u0026amp;#34;expiration_time\u0026amp;#34;:\u0026amp;#34;10 minutes\u0026amp;#34;,\u0026amp;#34;label\u0026amp;#34;:\u0026amp;#34;%s\u0026amp;#34;,\u0026amp;#34;global_key\u0026amp;#34;:true}\u0026amp;#39; \u0026amp;#34;$LOGIN_NAME\u0026amp;#34; \u0026amp;#34;$NONCE\u0026amp;#34; \u0026amp;#34;$LABEL\u0026amp;#34;)\n\n    log_message \u0026amp;#34;Request Body: $REQUEST_BODY\u0026amp;#34;\n\n    SIGNATURE=$(echo -n \u0026amp;#34;$REQUEST_BODY\u0026amp;#34; | openssl dgst -sha512 -sign \u0026amp;#34;$PRIVATE_KEY_PATH\u0026amp;#34; | base64 | tr -d \u0026amp;#39;\\n\u0026amp;#39;)\n\n    log_message \u0026amp;#34;Generated signature: $SIGNATURE\u0026amp;#34;\n\n    RESPONSE=$(curl -s -X POST \u0026amp;#34;$AUTH_URL\u0026amp;#34; \\\n        -H \u0026amp;#34;Signature: $SIGNATURE\u0026amp;#34; \\\n        -H \u0026amp;#34;Content-Type: application/json\u0026amp;#34; \\\n        -d \u0026amp;#34;$REQUEST_BODY\u0026amp;#34;) || handle_error \u0026amp;#34;Failed to connect to TransIP API\u0026amp;#34;\n\n    log_message \u0026amp;#34;API-response received: $RESPONSE\u0026amp;#34;\n\n    ACCESS_TOKEN=$(echo \u0026amp;#34;$RESPONSE\u0026amp;#34; | jq -r \u0026amp;#39;.token // empty\u0026amp;#39;)\n    if [ -z \u0026amp;#34;$ACCESS_TOKEN\u0026amp;#34; ]; then\n        handle_error \u0026amp;#34;The received access token is empty or invalid. Check the API-response: $RESPONSE\u0026amp;#34;\n    fi\n\n    log_message \u0026amp;#34;Received token: $ACCESS_TOKEN\u0026amp;#34;\n}\n\n# Function to add or update a TXT record\nadd_or_update_txt_record() {\n    local full_domain=\u0026amp;#34;$1\u0026amp;#34;\n    local value=\u0026amp;#34;$2\u0026amp;#34;\n\n    # Extract the subdomain part from the full domain\n    local name=\u0026amp;#34;_acme-challenge.${full_domain%%.*}\u0026amp;#34;\n\n    if [ -z \u0026amp;#34;$name\u0026amp;#34; ] || [ -z \u0026amp;#34;$value\u0026amp;#34; ]; then\n        log_message \u0026amp;#34;ERROR: Name or value for TXT record is missing.\u0026amp;#34;\n        exit 1\n    fi\n\n    # Check if the DNS record already exists\n    RESPONSE=$(curl -s -X GET \\\n        -H \u0026amp;#34;Authorization: Bearer $ACCESS_TOKEN\u0026amp;#34; \\\n        \u0026amp;#34;https://api.transip.nl/v6/domains/$DOMAIN/dns\u0026amp;#34;)\n\n    # Search for all existing records with the same name and type TXT\n    MATCHING_RECORDS=$(echo \u0026amp;#34;$RESPONSE\u0026amp;#34; | jq -r --arg name \u0026amp;#34;$name\u0026amp;#34; \u0026amp;#39;[.dnsEntries[] | select(.name==$name and .type==\u0026amp;#34;TXT\u0026amp;#34;)]\u0026amp;#39;)\n\n    # Check if there are multiple or no matching records\n    MATCHING_COUNT=$(echo \u0026amp;#34;$MATCHING_RECORDS\u0026amp;#34; | jq \u0026amp;#39;length\u0026amp;#39;)\n\n    # Ensure MATCHING_COUNT is always an integer\n    if ! [[ \u0026amp;#34;$MATCHING_COUNT\u0026amp;#34; =~ ^[0-9]\u0026#43;$ ]]; then\n        MATCHING_COUNT=0\n    fi\n\n    if [ \u0026amp;#34;$MATCHING_COUNT\u0026amp;#34; -eq 0 ]; then\n        log_message \u0026amp;#34;No existing TXT record found for $name. Creating a new record.\u0026amp;#34;\n    elif [ \u0026amp;#34;$MATCHING_COUNT\u0026amp;#34; -ge 1 ]; then\n        log_message \u0026amp;#34;Multiple or one existing TXT record found for $name. Removing all existing records.\u0026amp;#34;\n\n        # Remove all existing records using the full details of each record\n        echo \u0026amp;#34;$MATCHING_RECORDS\u0026amp;#34; | jq -c \u0026amp;#39;.[]\u0026amp;#39; | while read -r record; do\n            existing_name=$(echo \u0026amp;#34;$record\u0026amp;#34; | jq -r \u0026amp;#39;.name\u0026amp;#39;)\n            existing_expire=$(echo \u0026amp;#34;$record\u0026amp;#34; | jq -r \u0026amp;#39;.expire\u0026amp;#39;)\n            existing_content=$(echo \u0026amp;#34;$record\u0026amp;#34; | jq -r \u0026amp;#39;.content\u0026amp;#39;)\n\n            # Log the details of the record to be removed\n            log_message \u0026amp;#34;Remove record: name=$existing_name, expire=$existing_expire, content=$existing_content\u0026amp;#34;\n\n            # Make the DELETE call using the full DNS-entry details\n            DELETE_ENTRY=$(jq -n \\\n            --arg name \u0026amp;#34;$existing_name\u0026amp;#34; \\\n            --arg expire \u0026amp;#34;$existing_expire\u0026amp;#34; \\\n            --arg type \u0026amp;#34;TXT\u0026amp;#34; \\\n            --arg content \u0026amp;#34;$existing_content\u0026amp;#34; \\\n            \u0026amp;#39;{\u0026amp;#34;dnsEntry\u0026amp;#34;: {\u0026amp;#34;name\u0026amp;#34;: $name, \u0026amp;#34;expire\u0026amp;#34;: ($expire | tonumber), \u0026amp;#34;type\u0026amp;#34;: $type, \u0026amp;#34;content\u0026amp;#34;: $content}}\u0026amp;#39;)\n\n            DELETE_RESPONSE=$(curl -s -X DELETE \\\n                -H \u0026amp;#34;Authorization: Bearer $ACCESS_TOKEN\u0026amp;#34; \\\n                -H \u0026amp;#34;Content-Type: application/json\u0026amp;#34; \\\n                -d \u0026amp;#34;$DELETE_ENTRY\u0026amp;#34; \\\n                \u0026amp;#34;https://api.transip.nl/v6/domains/$DOMAIN/dns\u0026amp;#34;)\n\n            # Log the response of the DELETE call\n            log_message \u0026amp;#34;Response after removing $existing_name: $DELETE_RESPONSE\u0026amp;#34;\n\n            # Check if the removal was successful\n            if [[ \u0026amp;#34;$DELETE_RESPONSE\u0026amp;#34; == *\u0026amp;#34;error\u0026amp;#34;* ]]; then\n                log_message \u0026amp;#34;ERROR: The record $existing_name could not be removed.\u0026amp;#34;\n            else\n                log_message \u0026amp;#34;Record $existing_name successfully removed.\u0026amp;#34;\n            fi\n        done\n    fi\n\n    # Create a new record\n    DNS_ENTRY=$(jq -n \\\n    --arg name \u0026amp;#34;$name\u0026amp;#34; \\\n    --arg expire \u0026amp;#34;$TTL\u0026amp;#34; \\\n    --arg type \u0026amp;#34;TXT\u0026amp;#34; \\\n    --arg content \u0026amp;#34;$value\u0026amp;#34; \\\n    \u0026amp;#39;{\u0026amp;#34;dnsEntry\u0026amp;#34;: {\u0026amp;#34;name\u0026amp;#34;: $name, \u0026amp;#34;expire\u0026amp;#34;: ($expire | tonumber), \u0026amp;#34;type\u0026amp;#34;: $type, \u0026amp;#34;content\u0026amp;#34;: $content}}\u0026amp;#39;)\n\n    RESPONSE=$(curl -s -X POST \\\n        -H \u0026amp;#34;Authorization: Bearer $ACCESS_TOKEN\u0026amp;#34; \\\n        -H \u0026amp;#34;Content-Type: application/json\u0026amp;#34; \\\n        -d \u0026amp;#34;$DNS_ENTRY\u0026amp;#34; \\\n        \u0026amp;#34;https://api.transip.nl/v6/domains/$DOMAIN/dns\u0026amp;#34;)\n\n    log_message \u0026amp;#34;API-response for TXT record creation: $RESPONSE\u0026amp;#34;\n\n    if [[ \u0026amp;#34;$RESPONSE\u0026amp;#34; == *\u0026amp;#34;error\u0026amp;#34;* ]]; then\n        handle_error \u0026amp;#34;The TXT record could not be created. Check the API-response: $RESPONSE\u0026amp;#34;\n    else\n        log_message \u0026amp;#34;TXT record successfully added for $name with value $value\u0026amp;#34;\n    fi\n}\n\n# Function to update an existing TXT record\nupdate_txt_record() {\n    local name=\u0026amp;#34;$1\u0026amp;#34;\n    local value=\u0026amp;#34;$2\u0026amp;#34;\n\n    DNS_ENTRY=$(jq -n \\\n    --arg name \u0026amp;#34;$name\u0026amp;#34; \\\n    --arg expire \u0026amp;#34;$TTL\u0026amp;#34; \\\n    --arg type \u0026amp;#34;TXT\u0026amp;#34; \\\n    --arg content \u0026amp;#34;$value\u0026amp;#34; \\\n    \u0026amp;#39;{\u0026amp;#34;dnsEntry\u0026amp;#34;: {\u0026amp;#34;name\u0026amp;#34;: $name, \u0026amp;#34;expire\u0026amp;#34;: ($expire | tonumber), \u0026amp;#34;type\u0026amp;#34;: $type, \u0026amp;#34;content\u0026amp;#34;: $content}}\u0026amp;#39;)\n\n    RESPONSE=$(curl -s -X PATCH \\\n        -H \u0026amp;#34;Authorization: Bearer $ACCESS_TOKEN\u0026amp;#34; \\\n        -H \u0026amp;#34;Content-Type: application/json\u0026amp;#34; \\\n        -d \u0026amp;#34;$DNS_ENTRY\u0026amp;#34; \\\n        \u0026amp;#34;https://api.transip.nl/v6/domains/$DOMAIN/dns\u0026amp;#34;)\n\n    log_message \u0026amp;#34;API-response for TXT record update: $RESPONSE\u0026amp;#34;\n\n    if [[ \u0026amp;#34;$RESPONSE\u0026amp;#34; == *\u0026amp;#34;error\u0026amp;#34;* ]]; then\n        handle_error \u0026amp;#34;The TXT record could not be updated. Check the API-response: $RESPONSE\u0026amp;#34;\n    else\n        log_message \u0026amp;#34;TXT record successfully updated for $name with value $value\u0026amp;#34;\n    fi\n}\n\n# Function to remove a TXT record for `dns-01` validation\nremove_txt_record() {\n    local full_domain=\u0026amp;#34;$1\u0026amp;#34;\n\n    # Extract the subdomain part from the full domain\n    local name=\u0026amp;#34;_acme-challenge.${full_domain%%.*}\u0026amp;#34;\n\n    # Check if the DNS record exists\n    RESPONSE=$(curl -s -X GET \\\n        -H \u0026amp;#34;Authorization: Bearer $ACCESS_TOKEN\u0026amp;#34; \\\n        \u0026amp;#34;https://api.transip.nl/v6/domains/$DOMAIN/dns\u0026amp;#34;)\n\n    # Search for all records with the same name and type TXT\n    MATCHING_RECORDS=$(echo \u0026amp;#34;$RESPONSE\u0026amp;#34; | jq -r --arg name \u0026amp;#34;$name\u0026amp;#34; \u0026amp;#39;[.dnsEntries[] | select(.name==$name and .type==\u0026amp;#34;TXT\u0026amp;#34;)]\u0026amp;#39;)\n\n    # Check if there are existing records that need to be removed\n    MATCHING_COUNT=$(echo \u0026amp;#34;$MATCHING_RECORDS\u0026amp;#34; | jq \u0026amp;#39;length\u0026amp;#39;)\n\n    if [ \u0026amp;#34;$MATCHING_COUNT\u0026amp;#34; -eq 0 ]; then\n        log_message \u0026amp;#34;No existing TXT record found for $name to remove.\u0026amp;#34;\n    else\n        log_message \u0026amp;#34;Removing all existing TXT records for $name.\u0026amp;#34;\n\n        # Loop through all found records and remove them\n        echo \u0026amp;#34;$MATCHING_RECORDS\u0026amp;#34; | jq -c \u0026amp;#39;.[]\u0026amp;#39; | while read -r record; do\n            existing_name=$(echo \u0026amp;#34;$record\u0026amp;#34; | jq -r \u0026amp;#39;.name\u0026amp;#39;)\n            existing_expire=$(echo \u0026amp;#34;$record\u0026amp;#34; | jq -r \u0026amp;#39;.expire\u0026amp;#39;)\n            existing_content=$(echo \u0026amp;#34;$record\u0026amp;#34; | jq -r \u0026amp;#39;.content\u0026amp;#39;)\n\n            # Make the DELETE call using the full details\n            DELETE_ENTRY=$(jq -n \\\n            --arg name \u0026amp;#34;$existing_name\u0026amp;#34; \\\n            --arg expire \u0026amp;#34;$existing_expire\u0026amp;#34; \\\n            --arg type \u0026amp;#34;TXT\u0026amp;#34; \\\n            --arg content \u0026amp;#34;$existing_content\u0026amp;#34; \\\n            \u0026amp;#39;{\u0026amp;#34;dnsEntry\u0026amp;#34;: {\u0026amp;#34;name\u0026amp;#34;: $name, \u0026amp;#34;expire\u0026amp;#34;: ($expire | tonumber), \u0026amp;#34;type\u0026amp;#34;: $type, \u0026amp;#34;content\u0026amp;#34;: $content}}\u0026amp;#39;)\n\n            DELETE_RESPONSE=$(curl -s -X DELETE \\\n                -H \u0026amp;#34;Authorization: Bearer $ACCESS_TOKEN\u0026amp;#34; \\\n                -H \u0026amp;#34;Content-Type: application/json\u0026amp;#34; \\\n                -d \u0026amp;#34;$DELETE_ENTRY\u0026amp;#34; \\\n                \u0026amp;#34;https://api.transip.nl/v6/domains/$DOMAIN/dns\u0026amp;#34;)\n\n            # Check if the removal was successful\n            if [[ \u0026amp;#34;$DELETE_RESPONSE\u0026amp;#34; == *\u0026amp;#34;error\u0026amp;#34;* ]]; then\n                log_message \u0026amp;#34;ERROR: The TXT record $existing_name could not be removed.\u0026amp;#34;\n            else\n                log_message \u0026amp;#34;TXT record $existing_name successfully removed.\u0026amp;#34;\n            fi\n        done\n    fi\n}\n\n# Always generate a new token for each run\ngenerate_new_token\n\n# Certbot passes these values when adding and removing the record\nif [ \u0026amp;#34;$1\u0026amp;#34; == \u0026amp;#34;add\u0026amp;#34; ]; then\n    add_or_update_txt_record \u0026amp;#34;$CERTBOT_DOMAIN\u0026amp;#34; \u0026amp;#34;$CERTBOT_VALIDATION\u0026amp;#34;\n    sleep 25  # Wait for DNS update to propagate\nelif [ \u0026amp;#34;$1\u0026amp;#34; == \u0026amp;#34;clean\u0026amp;#34; ]; then\n    remove_txt_record \u0026amp;#34;$CERTBOT_DOMAIN\u0026amp;#34;\nfi\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003ch2 id=\"steps-to-run\"\u003eStappen om te draaien\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003eDownload de private key\u003c/strong\u003e: Sla het bestand met de private key (\u003ccode\u003etransip_private_key.pem\u003c/code\u003e) uit de API-instellingen van TransIP op op het apparaat waar je dit gaat draaien.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eStel Docker Compose in\u003c/strong\u003e: Zet de bestanden \u003ccode\u003edocker-compose.yml\u003c/code\u003e en \u003ccode\u003ecertbot-transip.sh\u003c/code\u003e in dezelfde map. (of pas het script aan)\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eStart de container\u003c/strong\u003e: Start de container (cd naar het pad waar je yml staat)\u003c/li\u003e\n\u003c/ol\u003e\n\u003cpre class=\"line-numbers language-bash\"\u003e\u003ccode class=\"language-bash\"\u003edocker-compose up --build\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cp\u003eHoud de logoutput in de gaten: de logoutput van de Docker container laat de voortgang van de DNS-challenges en het aanmaken van het certificaat zien.\u003c/p\u003e\n\u003cp\u003eMeer is het niet!\u003c/p\u003e\n\u003cp\u003eBinnenkort post ik ook een versie met een werkende Helm chart. Die was wat lastiger om alles correct te laten werken en alle certificaten te laten updaten via verschillende namespaces, waar ik de certificaten op verschillende domeinen beschikbaar moest hebben.\u003c/p\u003e\n","date_modified":"2024-11-29T16:52:31+01:00","date_published":"2024-11-28T22:03:00+01:00","id":"https://www.allsubjectsmatter.nl/nl/automating-lets-encrypt-with-certbot-and-transip-using-docker-2/","language":"nl","summary":"Wil je je SSL-certificaten automatisch updaten en beheren in Docker met Certbot, met je domein gehost bij TransIP? Dan hoef je niet verder te zoeken!","tags":["Tech"],"title":"Let's Encrypt automatiseren met Certbot en TransIP in Docker","url":"https://www.allsubjectsmatter.nl/nl/automating-lets-encrypt-with-certbot-and-transip-using-docker-2/"},{"authors":[{"name":"Remco Loup","url":"https://www.allsubjectsmatter.nl/author/remco/"}],"content_html":"\u003cp\u003eDe API: \u003ca href=\"https://api.protonvpn.ch/vpn/logicals\" data-external\u003ehttps://api.protonvpn.ch/vpn/logicals\u003c/a\u003e is volgens Proton Support officieel stopgezet. Het enige wat hij nog teruggeeft is:\u003c/p\u003e\n\u003cpre class=\"line-numbers language-json\"\u003e\u003ccode class=\"language-json\"\u003e{\u0026amp;#34;Code\u0026amp;#34;:0,\u0026amp;#34;Error\u0026amp;#34;:\u0026amp;#34;Invalid access token\u0026amp;#34;,\u0026amp;#34;Details\u0026amp;#34;:{}}\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cp\u003eDit artikel is daarmee helaas achterhaald. Iemand is een petitie begonnen om deze functionaliteit terug te krijgen.\u003c/p\u003e\n\u003cfigure class=\"bookmark\"\u003e\u003ca class=\"bookmark__link\" href=\"https://protonmail.uservoice.com/forums/932836-proton-vpn/suggestions/49774472-re-enable-access-to-api\" rel=\"noopener\"\u003e\u003cspan class=\"bookmark__body\"\u003e\u003cspan class=\"bookmark__title\"\u003eRe-enable access to API\u003c/span\u003e\u003cspan class=\"bookmark__description\"\u003eTot voor kort kon je via de API (https://api.protonvpn.ch/vpn/logicals) servers filteren op onder meer land en drukte, zodat je verbinding maakt met een server die niet overbelast is. Die mogelijkheid is verdwenen. 27 stemmen, 4 reacties.\u003c/span\u003e\u003cspan class=\"bookmark__meta\"\u003e\u003cspan\u003eProton VPN feedback\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/a\u003e\u003c/figure\u003e\n\n\u003ch2 id=\"the-deprecated-guide-which-no-longer-functions-as-of-062025-for-historical-reasons\"\u003eDe achterhaalde handleiding die niet meer werkt sinds 06/2025. Voor de historie bewaard.\u003c/h2\u003e\n\u003cp\u003eToen ProtonVPN zijn command-line interface (CLI)-tool stopzette, bleven gebruikers van headless systemen zoals de Raspberry Pi zonder een eenvoudige manier om VPN-verbindingen te beheren. Zonder grafische interface is het handmatig wisselen van VPN-servers een gedoe geworden. Ook het selecteren van de snelste VPN uit een pool van servers is onmogelijk geworden.\u003c/p\u003e\n\u003cp\u003eOm dit probleem op te lossen, heb ik een Python-script ontwikkeld dat automatisch de snelste ProtonVPN-server selecteert op basis van latency en load, en daarna je WireGuard-configuratie voor je bijwerkt. Deze handleiding neemt je mee door het instellen van WireGuard voor ProtonVPN, het installeren van het script en het automatiseren van je VPN-verbinding, zodat je Raspberry Pi altijd verbonden is met de beste server.\u003c/p\u003e\n\u003ch3 id=\"why-do-i-need-this-protonss-script\"\u003eWaarom heb ik dit Protonss-script nodig?\u003c/h3\u003e\n\u003cp\u003eDit Python-script automatiseert het verbinden van je Raspberry Pi met de snelst beschikbare ProtonVPN-server, met \u003cstrong\u003eWireGuard\u003c/strong\u003e als VPN-protocol. Het controleert de latency en load van meerdere VPN-servers via de API van ProtonVPN, kiest de server met de beste prestaties, werkt je WireGuard-configuratie bij met de nieuwe servergegevens en maakt alleen opnieuw verbinding als er een duidelijk snellere server is gevonden (niemand zit eigenlijk te wachten op reconnects). Het script logt het hele proces, wat het debuggen een stuk makkelijker maakt.\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003eGeen handmatig VPN-wisselen meer\u003c/strong\u003e: Zonder de CLI van ProtonVPN zou het wisselen naar de snelste server handwerk betekenen. Dit script automatiseert dat proces door regelmatig de beschikbare servers te controleren en de verbinding bij te werken op basis van realtime prestaties.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eOptimaliseert de verbindingssnelheid\u003c/strong\u003e: Het script zorgt dat je altijd verbonden bent met de VPN-server met de laagste latency en load. Dit is vooral handig om snelle verbindingen te behouden in landen met snelle ProtonVPN-servers of servers met weinig verkeer.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eVermindert downtime\u003c/strong\u003e: Door het herverbinden te automatiseren, minimaliseert het script mogelijke downtime door slechte serverprestaties of trage verbindingen.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eGemaakt voor de Raspberry Pi\u003c/strong\u003e: Als je een headless Raspberry Pi draait (zonder monitor), is dit script ideaal om VPN-taken te automatiseren zonder constante supervisie.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eBeveiliging \u0026amp; privacy\u003c/strong\u003e: Door ervoor te zorgen dat je Raspberry Pi altijd via een VPN verbinding maakt, beschermt het je data en online activiteit, zeker als je de Pi gebruikt voor torrenting of als netwerkapparaat.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eDit script automatiseert dus het vinden van de optimale ProtonVPN-server, met hogere snelheden, meer betrouwbaarheid en gemak voor headless systemen zoals een Raspberry Pi.\u003c/p\u003e\n\u003ch2 id=\"lets-dive-in\"\u003eAan de slag!\u003c/h2\u003e\n\u003cp\u003eEerst moet je WireGuard installeren, een snel en lichtgewicht VPN-protocol dat we gebruiken om verbinding te maken met onze ProtonVPN-server.\u003c/p\u003e\n\u003cpre class=\"line-numbers language-bash\"\u003e\u003ccode class=\"language-bash\"\u003e# First, update your package list and install WireGuard:\nsudo apt update\nsudo apt install wireguard curl resolvconf\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cp\u003e\u003cstrong\u003eGenereer een WireGuard-configuratie via ProtonVPN\u003c/strong\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eLog in op je ProtonVPN-account op hun website.\u003c/li\u003e\n\u003cli\u003eGa naar het gedeelte \u0026ldquo;Downloads\u0026rdquo; in je dashboard.\u003c/li\u003e\n\u003cli\u003eGenereer een nieuw configuratiebestand onder \u0026ldquo;WireGuard Configurations\u0026rdquo;.\u003c/li\u003e\n\u003cli\u003eGebruik je torrents, schakel dan \u003cstrong\u003eNAT-PMP\u003c/strong\u003e in.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cfigure class=\"figure\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003csource type=\"image/webp\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/supercharge-your-headless-raspberry-pi-vpn-with-protonvpn-and-wireguard/images/image_hu_e1bb7171adce48a.webp\" alt=\"\" width=\"560\" height=\"368\" data-zoom-src=\"https://www.allsubjectsmatter.nl/supercharge-your-headless-raspberry-pi-vpn-with-protonvpn-and-wireguard/images/image_hu_1345a69245a3f8c.webp\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003c/figure\u003e\n\n\u003cp\u003eSla dit configuratiebestand op je Raspberry Pi op:\u003c/p\u003e\n\u003cpre class=\"line-numbers language-bash\"\u003e\u003ccode class=\"language-bash\"\u003e# Save the configuration file to your Raspberry Pi:\n# Place it in the WireGuard directory and rename it for simplicity:\nsudo nano /etc/wireguard/protonvpn.conf\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cp\u003eZodra je het configuratiebestand in de juiste map hebt geplaatst, controleer je de verbinding.\u003c/p\u003e\n\u003cpre class=\"line-numbers language-bash\"\u003e\u003ccode class=\"language-bash\"\u003e# Verify connection to ProtonVPN:\nsudo wg-quick up protonvpn\n\n# This should give an ip address different to the one frome your isp\ncurl ifconfig.me \n\n# Verify connection to ProtonVPN:\nsudo wg-quick down protonvpn\n\ncurl ifconfig.me \u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cp\u003eAls deze twee ip-adressen van ifconfig.me verschillen, ben je verbonden via een VPN. Ook als je je eigen ip-adres niet kent omdat het een DHCP-adres of iets dergelijks is.\u003c/p\u003e\n\u003ch3 id=\"installing-our-python-environment-for-protonss\"\u003eOnze Python-omgeving installeren voor Protonss.\u003c/h3\u003e\n\u003cp\u003eBij het ontwikkelen en draaien van Python-scripts op je Raspberry Pi is het verstandig om een \u003cstrong\u003ePython virtual environment\u003c/strong\u003e te gebruiken. Zo\u0026rsquo;n virtuele omgeving laat je Python-packages geïsoleerd installeren en beheren, zodat de standaard Python-configuratie van je systeem onaangeroerd blijft.\u003c/p\u003e\n\u003cp\u003eVeranderingen in je omgeving beïnvloeden dan alleen het project waaraan je werkt — zoals het installeren van specifieke packages zoals \u003ccode\u003erequests\u003c/code\u003e — zonder conflicten te veroorzaken met andere Python-projecten of systeemafhankelijkheden.\u003c/p\u003e\n\u003cp\u003eZo stel je een virtuele omgeving in voor ons script:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003eInstalleer de virtuele omgeving\u003c/strong\u003e: Installeer eerst het package \u003ccode\u003epython3-venv\u003c/code\u003e, waarmee je virtuele omgevingen kunt aanmaken en beheren.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cpre class=\"line-numbers language-bash\"\u003e\u003ccode class=\"language-bash\"\u003esudo apt install -y python3-venv python3-full\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003col start=\"2\"\u003e\n\u003cli\u003e\u003cstrong\u003eMaak een virtuele omgeving\u003c/strong\u003e: Na installatie kun je een virtuele omgeving aanmaken. Dit genereert een geïsoleerde omgeving in de door jou opgegeven map (in dit geval \u003ccode\u003e~/protonss\u003c/code\u003e).\u003c/li\u003e\n\u003c/ol\u003e\n\u003cpre class=\"line-numbers language-bash\"\u003e\u003ccode class=\"language-bash\"\u003epython3 -m venv ~/protonss\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003col start=\"3\"\u003e\n\u003cli\u003e\u003cstrong\u003eActiveer de virtuele omgeving\u003c/strong\u003e: Na het aanmaken moet je de omgeving activeren. Dit zorgt dat je systeem de Python-versie en packages uit de virtuele omgeving gebruikt in plaats van de systeembrede Python-installatie.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cpre class=\"line-numbers language-bash\"\u003e\u003ccode class=\"language-bash\"\u003esource ~/protonss/bin/activate\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cul\u003e\n\u003cli\u003eNu de omgeving actief is, wordt elke Python-package die je installeert (zoals \u003ccode\u003erequests\u003c/code\u003e) in deze geïsoleerde omgeving geïnstalleerd.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cpre class=\"line-numbers language-bash\"\u003e\u003ccode class=\"language-bash\"\u003epip install requests\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cp\u003e\u003cstrong\u003eDeze virtuele omgeving in de toekomst gebruiken\u003c/strong\u003e: Elke keer dat je Python-scripts wilt draaien die van deze virtuele omgeving afhankelijk zijn, moet je hem opnieuw activeren met het commando \u003ccode\u003esource ~/venv/bin/activate\u003c/code\u003e. Eenmaal actief draaien alle Python-commando\u0026rsquo;s en packages in deze omgeving.\u003c/p\u003e\n\u003ch3 id=\"installing-our-protonss-script-to-automate-a-connection-to-the-fastest-vpn\"\u003eOns ProtonSS-script installeren om automatisch met de snelste VPN te verbinden.\u003c/h3\u003e\n\u003cp\u003eWe maken een configuratiebestand aan met alle variabelen die het Python-script gebruikt. Daarin definieer je bijvoorbeeld het pad naar je WireGuard-configuratie, het logbestand en het aantal pings om latency te testen.\u003c/p\u003e\n\u003cpre class=\"line-numbers language-bash\"\u003e\u003ccode class=\"language-bash\"\u003ecd ~/protonss\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cp\u003eVoorbeeld \u003ccode\u003econfig.ini\u003c/code\u003e:\u003c/p\u003e\n\u003cpre class=\"line-numbers language-bash\"\u003e\u003ccode class=\"language-bash\"\u003enano config.ini\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cpre class=\"line-numbers language-ini\"\u003e\u003ccode class=\"language-ini\"\u003e[Settings]\n# Path to your WireGuard configuration file\nwg_config_path = /etc/wireguard/protonvpn.conf\n\n# Path to your log file\nlog_file = /var/log/wireguard_updater.log\n\n# Number of pings to measure latency\nping_count = 3\n\n# Maximum score difference before reconnection 0 - 100\n# Average score of servers is between 10-40 it seems. so setting this to high results in no switches from servers\nscore_threshold = 5.0\n\n# Comma-separated list of preferred countries\npreferred_countries = US\n\n# Comma-separated list of preferred cities\n# If specified, servers must be in these cities (unless they\u0026amp;#39;re in a country without any specified city)\n# Preferred cities to connect to. If specified, servers must be in these cities\n# (unless they\u0026amp;#39;re in a country without any specified city).\n# Example: preferred_cities = Phoenix, Los Angeles, Seattle\n# This allows you to target specific cities within your preferred countries.\n# If left empty, any city within the preferred countries will be considered.\n# Note: City names are case sensitive and must match ProtonVPN\u0026amp;#39;s naming exactly.\n# Multiple cities should be comma-separated.\npreferred_cities =Phoenix\n\n# Weight for latency in the server score\n# latency is subjective since this is probably measured through the vpn and only counts as server latency.\n# Checking the latency without a vpn isnt recommended since the proton servers wont reply back without vpn.\n# Value cant be higher then 1. 0.3 translates to 30% of the score_treshold.\nlatency_weight = 0.3\n\n# Weight for server load in the server score\n# Load is the best factor 0.7 translates to 70% of the weight of the score_threshold. this cant be higher then 1.\nload_weight = 0.7\n\n# Enable or disable pinging servers (True or False)\n# Since ping might not be reliable enough for you to keep on switching servers this can be disabled here.\nping_enabled = True\n\n# Specify the interface to use for ping tests. By setting this option you can ping to the fastest vpn server\n# this will ensure the lowest latency VPN from your location.\nping_interface = eth0\n\n[Features]\n# Choose which type of servers you want to use\n# Options: p2p, plus, secure_core, tor\n# Default is p2p if not specified\nfeature_type = p2p\n\n# Feature explanation:\n# Each server has exactly ONE feature number:\n# 1, 17 = Secure Core servers\n# 2 = TOR servers\n# 0, 8, 16, 24 = Plus-only servers\n# 4, 12, 20, 28 = P2P servers (these are also Plus servers)\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cp\u003eJe kunt de variabelen naar behoefte aanpassen. Laat je \u003ccode\u003epreferred_countries\u003c/code\u003e leeg, dan controleert het script een HELEBOEL! servers, waardoor het script erg lang gaat draaien!\u003c/p\u003e\n\u003ch4 id=\"create-the-python-script\"\u003e\u003cstrong\u003eMaak het Python-script aan\u003c/strong\u003e\u003c/h4\u003e\n\u003cp\u003eNu gaan we het Python-script maken dat je VPN-verbinding automatiseert. Eerst moeten we een paar stappen voorbereiden.\u003c/p\u003e\n\u003cp\u003eDe eerste regel van het script moet een \u003cstrong\u003eshebang\u003c/strong\u003e zijn, die er altijd voor zorgt dat het script in de juiste Python-omgeving draait:\u003c/p\u003e\n\u003cpre class=\"line-numbers language-bash\"\u003e\u003ccode class=\"language-bash\"\u003esource ~/protonss/bin/activate\nwhich python\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cp\u003eDit geeft je het pad dat je op de eerste regel van het script moet zetten.\u003c/p\u003e\n\u003cpre class=\"line-numbers language-text\"\u003e\u003ccode class=\"language-text\"\u003e/home/pi/venv/bin/python\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cp\u003ePas het script aan door een \u003cstrong\u003eshebang\u003c/strong\u003e toe te voegen: zet de volgende regel helemaal bovenaan je Python-script. Vervang \u003ccode\u003e/home/{user}/venv/bin/python\u003c/code\u003e door het daadwerkelijke pad dat je kreeg toen je eerder het commando \u003ccode\u003ewhich python\u003c/code\u003e uitvoerde. Laat de \u003ccode\u003e#!\u003c/code\u003e aan het begin van de regel staan—daaruit maakt het systeem op welke interpreter het script moet draaien. We hebben het absolute pad nodig, dus ~/ werkt niet.\u003c/p\u003e\n\u003cpre class=\"line-numbers language-bash\"\u003e\u003ccode class=\"language-bash\"\u003enano protonvpn_auto_connect.py\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cpre class=\"line-numbers language-python\"\u003e\u003ccode class=\"language-python\"\u003e#!/home/{user}/protonss/bin/python\n\nimport requests\nimport subprocess\nimport os\nimport sys\nimport configparser\nimport logging\nimport re\nfrom datetime import datetime\nimport math\nimport shutil\n\n# Get the directory of the current script\nscript_dir = os.path.dirname(os.path.abspath(__file__))\n\n# Load the config file from the same directory as the script\nconfig = configparser.ConfigParser()\nconfig.read(os.path.join(script_dir, \u0026amp;#39;config.ini\u0026amp;#39;))\n\nWG_CONFIG_PATH = config.get(\u0026amp;#39;Settings\u0026amp;#39;, \u0026amp;#39;wg_config_path\u0026amp;#39;)\nLOG_FILE = config.get(\u0026amp;#39;Settings\u0026amp;#39;, \u0026amp;#39;log_file\u0026amp;#39;)\nPING_COUNT = config.getint(\u0026amp;#39;Settings\u0026amp;#39;, \u0026amp;#39;ping_count\u0026amp;#39;)\nSCORE_THRESHOLD = config.getfloat(\u0026amp;#39;Settings\u0026amp;#39;, \u0026amp;#39;score_threshold\u0026amp;#39;, fallback=5.0)\nPREFERRED_COUNTRIES = config.get(\u0026amp;#39;Settings\u0026amp;#39;, \u0026amp;#39;preferred_countries\u0026amp;#39;, fallback=\u0026amp;#39;\u0026amp;#39;).split(\u0026amp;#39;,\u0026amp;#39;)\nPREFERRED_CITIES = config.get(\u0026amp;#39;Settings\u0026amp;#39;, \u0026amp;#39;preferred_cities\u0026amp;#39;, fallback=\u0026amp;#39;\u0026amp;#39;).split(\u0026amp;#39;,\u0026amp;#39;)\nPING_INTERFACE = config.get(\u0026amp;#39;Settings\u0026amp;#39;, \u0026amp;#39;ping_interface\u0026amp;#39;, fallback=None)\n\n# Weighting factors for latency and load from the config\nLATENCY_WEIGHT = config.getfloat(\u0026amp;#39;Settings\u0026amp;#39;, \u0026amp;#39;latency_weight\u0026amp;#39;, fallback=0.3)\nLOAD_WEIGHT = config.getfloat(\u0026amp;#39;Settings\u0026amp;#39;, \u0026amp;#39;load_weight\u0026amp;#39;, fallback=0.7)\n\n# Ping option from config\nPING_ENABLED = config.getboolean(\u0026amp;#39;Settings\u0026amp;#39;, \u0026amp;#39;ping_enabled\u0026amp;#39;, fallback=True)\n\n# Clean up country codes (remove spaces and convert to uppercase)\nPREFERRED_COUNTRIES = [country.strip().upper() for country in PREFERRED_COUNTRIES if country.strip()]\n\n# Clean up city names (remove spaces)\nPREFERRED_CITIES = [city.strip() for city in PREFERRED_CITIES if city.strip()]\n\n# Set up logging\nlogging.basicConfig(\n    filename=LOG_FILE,\n    level=logging.INFO,\n    format=\u0026amp;#39;%(asctime)s [%(levelname)s] %(message)s\u0026amp;#39;,\n    datefmt=\u0026amp;#39;%Y-%m-%d %H:%M:%S\u0026amp;#39;\n)\n\nlogging.info(\u0026amp;#39;--- ProtonVPN Auto Connect Script Start ---\u0026amp;#39;)\n\n# Feature definitions\nFEATURE_SECURE_CORE = [1, 17]  # Secure Core servers\nFEATURE_TOR = [2]         # TOR servers\nFEATURE_PLUS = [0, 8, 16, 24]  # Plus-only servers\nFEATURE_P2P = [4, 12, 20, 28]    # P2P servers (these are also Plus servers)\n# check https://api.protonmail.ch/vpn/logicals for more info. \n# be aware that the secure core and tor server are very limited so check if one is available for your country as stated in the config.ini file.\n# FREE servers are not included in the api response. so they are already left out.\n\ndef get_feature_description(features):\n    # If no feature type is specified in config.ini, default to P2P\n    # Features is a number representing server capabilities\n    if not features:  # If features is 0 or None\n        return \u0026amp;#34;P2P\u0026amp;#34;  # Default to P2P\n    elif features in FEATURE_SECURE_CORE:\n        return \u0026amp;#34;Secure Core\u0026amp;#34;\n    elif features in FEATURE_TOR:\n        return \u0026amp;#34;TOR\u0026amp;#34; \n    elif features in FEATURE_P2P:\n        return \u0026amp;#34;P2P\u0026amp;#34;\n    elif features in FEATURE_PLUS:\n        return \u0026amp;#34;Plus\u0026amp;#34;\n    else:\n        return \u0026amp;#34;P2P\u0026amp;#34;  # Default to P2P for any unrecognized feature\n\ndef get_wanted_features():\n    \u0026amp;#34;\u0026amp;#34;\u0026amp;#34;Get the wanted feature type based on config priorities\u0026amp;#34;\u0026amp;#34;\u0026amp;#34;\n    feature_type = config.get(\u0026amp;#39;Features\u0026amp;#39;, \u0026amp;#39;feature_type\u0026amp;#39;, fallback=\u0026amp;#39;p2p\u0026amp;#39;).lower()\n    \n    if feature_type == \u0026amp;#39;secure_core\u0026amp;#39;:\n        return FEATURE_SECURE_CORE\n    elif feature_type == \u0026amp;#39;tor\u0026amp;#39;:\n        return FEATURE_TOR\n    elif feature_type == \u0026amp;#39;plus\u0026amp;#39;:\n        return FEATURE_PLUS\n    else:  # Default to P2P\n        return FEATURE_P2P\n\n# Function to fetch the list of servers from the ProtonVPN API\ndef get_servers(full_list=False):\n    try:\n        url = \u0026amp;#39;https://api.protonmail.ch/vpn/logicals\u0026amp;#39;\n        response = requests.get(url)\n        response.raise_for_status()\n        data = response.json()\n        logging.info(\u0026amp;#34;API response data received.\u0026amp;#34;)\n        return data[\u0026amp;#39;LogicalServers\u0026amp;#39;]\n    except requests.RequestException as e:\n        logging.error(f\u0026amp;#39;Error fetching server list: {e}\u0026amp;#39;)\n        sys.exit(1)\n    except ValueError as e:\n        logging.error(f\u0026amp;#39;Error parsing JSON: {e}\u0026amp;#39;)\n        sys.exit(1)\n\n# Function to filter P2P servers and servers in preferred countries\ndef filter_servers(servers):\n    filtered_servers = []\n    \n    # Get feature preferences from config (nu maar één type)\n    wanted_features = get_wanted_features()\n    \n    logging.info(f\u0026amp;#34;Filtering servers with preferences - Countries: {PREFERRED_COUNTRIES or \u0026amp;#39;Any\u0026amp;#39;}, \u0026amp;#34;\n                f\u0026amp;#34;Cities: {PREFERRED_CITIES or \u0026amp;#39;Any\u0026amp;#39;}, Features: {[get_feature_description(f) for f in wanted_features]}\u0026amp;#34;)\n    \n    for server in servers:\n        if not isinstance(server, dict):\n            logging.error(f\u0026amp;#34;Unexpected type for server: {type(server)} - Value: {server}\u0026amp;#34;)\n            continue\n            \n        features = server.get(\u0026amp;#39;Features\u0026amp;#39;, 0)\n        country = server[\u0026amp;#39;ExitCountry\u0026amp;#39;]\n        city = server.get(\u0026amp;#39;City\u0026amp;#39;, \u0026amp;#39;\u0026amp;#39;)\n\n        # Check if server features match our wanted features\n        if features in wanted_features:\n            # Check country and city preferences\n            if country in PREFERRED_COUNTRIES:\n                if not PREFERRED_CITIES or city in PREFERRED_CITIES:\n                    filtered_servers.append(server)\n                    logging.info(f\u0026amp;#34;Server matched - Country: {country}, City: {city}, \u0026amp;#34;\n                              f\u0026amp;#34;Features: {get_feature_description(features)}\u0026amp;#34;)\n            \n    if not filtered_servers:\n        logging.warning(f\u0026amp;#34;No servers found matching filters. Countries: {PREFERRED_COUNTRIES}, \u0026amp;#34;\n                      f\u0026amp;#34;Cities: {PREFERRED_CITIES}, Features: {[get_feature_description(f) for f in wanted_features]}\u0026amp;#34;)\n    else:\n        logging.info(f\u0026amp;#34;Found {len(filtered_servers)} matching servers\u0026amp;#34;)\n        \n    return filtered_servers\n\n# Function to measure latency by pinging the server (optional)\ndef get_latency(hostname):\n    if not PING_ENABLED:\n        logging.info(f\u0026amp;#34;Ping disabled, skipping latency check for {hostname}.\u0026amp;#34;)\n        return 0  # Return 0 latency if ping is disabled\n\n    latencies = []\n    for i in range(PING_COUNT):\n        try:\n            # Add the interface option if it\u0026amp;#39;s specified\n            ping_command = [\u0026amp;#39;ping\u0026amp;#39;, \u0026amp;#39;-c\u0026amp;#39;, \u0026amp;#39;1\u0026amp;#39;, \u0026amp;#39;-W\u0026amp;#39;, \u0026amp;#39;1\u0026amp;#39;, hostname]\n            if PING_INTERFACE:\n                ping_command.insert(1, \u0026amp;#39;-I\u0026amp;#39;)\n                ping_command.insert(2, PING_INTERFACE)\n\n            result = subprocess.run(ping_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n            if result.returncode == 0:\n                output = result.stdout.decode()\n                latency_line = [line for line in output.split(\u0026amp;#39;\\n\u0026amp;#39;) if \u0026amp;#39;time=\u0026amp;#39; in line][0]\n                latency = float(latency_line.split(\u0026amp;#39;time=\u0026amp;#39;)[1].split(\u0026amp;#39; ms\u0026amp;#39;)[0])\n                latencies.append(latency)\n            else:\n                latencies.append(float(\u0026amp;#39;inf\u0026amp;#39;))\n        except Exception as e:\n            latencies.append(float(\u0026amp;#39;inf\u0026amp;#39;))\n            logging.error(f\u0026amp;#39;Ping error for {hostname}: {e}\u0026amp;#39;)\n\n    # Return the lowest (best) latency value\n    best_latency = min(latencies)\n    \n    # Handle inf latency\n    if math.isinf(best_latency):\n        logging.warning(f\u0026amp;#34;Latency for {hostname} is inf. Assigning high latency value.\u0026amp;#34;)\n        best_latency = 1000  # Assign a high latency value\n\n    return best_latency\n\n# Function to select the best server based on a weighted combination of latency and load\ndef select_best_server(servers):\n    best_server = None\n    best_score = float(\u0026amp;#39;inf\u0026amp;#39;)\n    \n    for server in servers:\n        server_load = server.get(\u0026amp;#39;Load\u0026amp;#39;)\n        \n        # Handle inf load\n        if server_load is None or math.isinf(server_load):\n            logging.warning(f\u0026amp;#34;Server {server[\u0026amp;#39;Name\u0026amp;#39;]} has inf load. Assigning high load value.\u0026amp;#34;)\n            server_load = 100  # Assign a high load value if it\u0026amp;#39;s inf or None\n        else:\n            server_load = int(server_load)\n        \n        for srv in server[\u0026amp;#39;Servers\u0026amp;#39;]:\n            exit_ip = srv.get(\u0026amp;#39;ExitIP\u0026amp;#39;)\n            hostname = exit_ip\n\n            # Get latency, only if ping is enabled\n            latency = get_latency(hostname)\n\n            # Combine latency and load using the configured weights\n            score = (latency * LATENCY_WEIGHT) \u0026#43; (server_load * LOAD_WEIGHT)\n            \n            logging.info(f\u0026amp;#34;Server {server[\u0026amp;#39;Name\u0026amp;#39;]} - Latency: {latency:.2f} ms, Load: {server_load}%, Score: {score:.2f}\u0026amp;#34;)\n            \n            if score \u0026amp;lt; best_score:\n                best_score = score\n                best_server = server\n                best_server_instance = srv\n\n    return best_server, best_server_instance, best_score\n\n# Function to read the current server IP from the WireGuard configuration\ndef get_current_server_ip():\n    try:\n        with open(WG_CONFIG_PATH, \u0026amp;#39;r\u0026amp;#39;) as f:\n            config_text = f.read()\n        # Extract the current Endpoint IP\n        match = re.search(r\u0026amp;#39;Endpoint\\s*=\\s*([\\d\\.]\u0026#43;):\\d\u0026#43;\u0026amp;#39;, config_text)\n        if match:\n            logging.info(f\u0026amp;#34;Current server IP found in WireGuard configuration: {match.group(1)}\u0026amp;#34;)\n            return match.group(1)\n        else:\n            logging.warning(\u0026amp;#39;No current Endpoint IP found in WireGuard configuration.\u0026amp;#39;)\n            return None\n    except Exception as e:\n        logging.error(f\u0026amp;#39;Error reading the WireGuard configuration: {e}\u0026amp;#39;)\n        return None\n\n# Function to find the current server\u0026amp;#39;s performance in the live server data\ndef get_current_server_performance(servers, current_ip):\n    if current_ip is None:\n        logging.warning(\u0026amp;#39;Current server IP is None, skipping performance check.\u0026amp;#39;)\n        return None, None, float(\u0026amp;#39;inf\u0026amp;#39;)\n    \n    for server in servers:\n        for srv in server[\u0026amp;#39;Servers\u0026amp;#39;]:\n            if srv[\u0026amp;#39;ExitIP\u0026amp;#39;] == current_ip:\n                server_load = server.get(\u0026amp;#39;Load\u0026amp;#39;)\n                if server_load is None or math.isinf(server_load):\n                    logging.warning(f\u0026amp;#34;Server {server[\u0026amp;#39;Name\u0026amp;#39;]} (IP: {current_ip}) has inf load. Assigning high load value.\u0026amp;#34;)\n                    server_load = 100  # Assign a high load value\n                else:\n                    server_load = int(server_load)\n\n                latency = get_latency(current_ip) if PING_ENABLED else 0\n                score = (latency * LATENCY_WEIGHT) \u0026#43; (server_load * LOAD_WEIGHT)\n\n                logging.info(f\u0026amp;#34;Current server {server[\u0026amp;#39;Name\u0026amp;#39;]} - Latency: {latency:.2f} ms, Load: {server_load}%, Score: {score:.2f}\u0026amp;#34;)\n                return server, srv, score\n    logging.warning(f\u0026amp;#34;Current server with IP {current_ip} not found in live data.\u0026amp;#34;)\n    return None, None, float(\u0026amp;#39;inf\u0026amp;#39;)\n\n# Function to find the current server in the full ProtonVPN list if not found in filtered data\ndef find_current_server_full_list(current_ip):\n    servers = get_servers(full_list=True)\n    return get_current_server_performance(servers, current_ip)\n\n# Function to update the WireGuard configuration file\ndef update_wg_config(server, server_instance, best_score):\n    try:\n        endpoint_ip = server_instance[\u0026amp;#39;EntryIP\u0026amp;#39;]\n        endpoint_port = 51820  # Default WireGuard port\n        endpoint = f\u0026amp;#34;{endpoint_ip}:{endpoint_port}\u0026amp;#34;\n        public_key = server_instance[\u0026amp;#39;X25519PublicKey\u0026amp;#39;]\n        server_name = server[\u0026amp;#39;Name\u0026amp;#39;]\n\n        # Read the current WireGuard configuration\n        with open(WG_CONFIG_PATH, \u0026amp;#39;r\u0026amp;#39;) as f:\n            config_text = f.read()\n\n        # Strip comments that start with # under [Peer] before PublicKey\n        config_text = re.sub(r\u0026amp;#39;#.*\\n\u0026amp;#39;, \u0026amp;#39;\u0026amp;#39;, config_text, flags=re.M)\n\n        # Update the Endpoint in the configuration\n        config_text = re.sub(r\u0026amp;#39;Endpoint\\s*=.*\u0026amp;#39;, f\u0026amp;#39;Endpoint = {endpoint}\u0026amp;#39;, config_text)\n\n        # Update the PublicKey in the configuration\n        config_text = re.sub(r\u0026amp;#39;PublicKey\\s*=.*\u0026amp;#39;, f\u0026amp;#39;PublicKey = {public_key}\u0026amp;#39;, config_text)\n\n        # Add the new comment indicating the chosen server and score\n        config_text = re.sub(r\u0026amp;#39;\\[Peer\\]\\n\u0026amp;#39;, f\u0026amp;#39;[Peer]\\n# {server_name} - Auto-generated by ProtonVPN Auto Connect - Score: {best_score:.2f}\\n\u0026amp;#39;, config_text)\n\n        # Write the updated configuration back to the file\n        with open(WG_CONFIG_PATH, \u0026amp;#39;w\u0026amp;#39;) as f:\n            f.write(config_text)\n        os.chmod(WG_CONFIG_PATH, 0o600)\n\n        logging.info(f\u0026amp;#34;WireGuard configuration updated with server: {server_name} ({server[\u0026amp;#39;ExitCountry\u0026amp;#39;]}), Endpoint: {endpoint}, PublicKey: {public_key}, Score: {best_score:.2f}\u0026amp;#34;)\n    except Exception as e:\n        logging.error(f\u0026amp;#39;Error updating the WireGuard configuration: {e}\u0026amp;#39;)\n        sys.exit(1)\n\n# Function to restart the WireGuard interface\ndef is_resolvconf_available():\n    \u0026amp;#34;\u0026amp;#34;\u0026amp;#34;Check if resolvconf is available on the system.\u0026amp;#34;\u0026amp;#34;\u0026amp;#34;\n    return shutil.which(\u0026amp;#34;resolvconf\u0026amp;#34;) is not None\n\ndef is_wg_quick_available():\n    \u0026amp;#34;\u0026amp;#34;\u0026amp;#34;Check if wg-quick is available on the system.\u0026amp;#34;\u0026amp;#34;\u0026amp;#34;\n    return shutil.which(\u0026amp;#34;wg-quick\u0026amp;#34;) is not None\n\ndef restart_wg():\n    interface_name = os.path.basename(WG_CONFIG_PATH).split(\u0026amp;#39;.\u0026amp;#39;)[0]\n    \n    # Check if wg-quick is available before proceeding\n    if not is_wg_quick_available():\n        logging.error(\u0026amp;#34;wg-quick not found. Ensure WireGuard is installed correctly.\u0026amp;#34;)\n        sys.exit(1)\n\n    try:\n        # Check if resolvconf is available before proceeding\n        if not is_resolvconf_available():\n            logging.warning(\u0026amp;#34;resolvconf not found, DNS settings may not be updated correctly.\u0026amp;#34;)\n\n        # Bring down the interface\n        subprocess.run([\u0026amp;#39;wg-quick\u0026amp;#39;, \u0026amp;#39;down\u0026amp;#39;, interface_name], stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n\n        # Bring up the interface\n        subprocess.run([\u0026amp;#39;wg-quick\u0026amp;#39;, \u0026amp;#39;up\u0026amp;#39;, interface_name], check=True)\n        logging.info(f\u0026amp;#39;WireGuard interface {interface_name} started.\u0026amp;#39;)\n\n    except subprocess.CalledProcessError as e:\n        logging.error(f\u0026amp;#39;Error starting the WireGuard interface: {e}\u0026amp;#39;)\n        sys.exit(1)\n\n# Main function\ndef main():\n    logging.info(\u0026amp;#39;--- Starting server selection process ---\u0026amp;#39;)\n    \n    # Fetch the list of servers\n    servers = get_servers()\n    if not isinstance(servers, list):\n        logging.error(f\u0026amp;#39;Unexpected type for servers: {type(servers)}\u0026amp;#39;)\n        sys.exit(1)\n\n    # Filter the servers\n    filtered_servers = filter_servers(servers)\n    if not filtered_servers:\n        logging.error(\u0026amp;#39;No suitable servers available after filtering.\u0026amp;#39;)\n        sys.exit(1)\n\n    # Get the current server\u0026amp;#39;s IP address from the WireGuard configuration\n    current_ip = get_current_server_ip()\n\n    # Get the current server\u0026amp;#39;s performance if it exists\n    current_server, current_server_instance, current_score = get_current_server_performance(filtered_servers, current_ip)\n\n    # If the current server is not found, check the full list of servers\n    if current_server is None:\n        logging.info(f\u0026amp;#34;Current server not found in filtered list, checking full server list...\u0026amp;#34;)\n        current_server, current_server_instance, current_score = find_current_server_full_list(current_ip)\n\n    # Select the best server based on weighted latency and load\n    best_server, best_server_instance, best_score = select_best_server(filtered_servers)\n    if not best_server:\n        logging.error(\u0026amp;#39;No suitable server found.\u0026amp;#39;)\n        sys.exit(1)\n\n    if current_server:\n        logging.info(f\u0026amp;#34;Current server {current_server[\u0026amp;#39;Name\u0026amp;#39;]} (IP: {current_ip}) - Score: {current_score:.2f}\u0026amp;#34;)\n    \n    logging.info(f\u0026amp;#34;Selected best server {best_server[\u0026amp;#39;Name\u0026amp;#39;]} - Score: {best_score:.2f}\u0026amp;#34;)\n    \n    if current_server and current_score \u0026amp;lt;= best_score \u0026#43; SCORE_THRESHOLD:\n        logging.info(f\u0026amp;#34;Current server {current_server[\u0026amp;#39;Name\u0026amp;#39;]} is within threshold. No switch necessary.\u0026amp;#34;)\n    else:\n        logging.info(f\u0026amp;#34;Switching to server {best_server[\u0026amp;#39;Name\u0026amp;#39;]} due to better performance.\u0026amp;#34;)\n        update_wg_config(best_server, best_server_instance, best_score)\n        restart_wg()\n\n    logging.info(\u0026amp;#39;--- ProtonVPN Auto Connect Script End ---\u0026amp;#39;)\n\nif __name__ == \u0026amp;#39;__main__\u0026amp;#39;:\n    main()\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003ch3 id=\"testing-the-script\"\u003eHet script testen\u003c/h3\u003e\n\u003cpre class=\"line-numbers language-bash\"\u003e\u003ccode class=\"language-bash\"\u003echmod \u0026#43;x protonvpn_auto_connect.py\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cp\u003eZodra het script is opgeslagen, geef je het uitvoerrechten en draai je het.\u003c/p\u003e\n\u003cpre class=\"line-numbers language-bash\"\u003e\u003ccode class=\"language-bash\"\u003esudo ./protonvpn_auto_connect.py\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cpre class=\"line-numbers language-bash\"\u003e\u003ccode class=\"language-bash\"\u003ecat /var/log/wireguard_updater.log\ntail -f /var/log/wireguard_updater.log\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cp\u003eJe kunt het logbestand tailen of catten om de output te controleren. Tail heeft de voorkeur, want dan kun je het blijven volgen terwijl je script draait. Open gewoon een tweede ssh-sessie met de tail om het in de gaten te houden.\u003c/p\u003e\n\u003cpre class=\"line-numbers language-log\"\u003e\u003ccode class=\"language-log\"\u003e2024-09-25 22:19:10 [INFO] --- ProtonVPN Auto Connect Script Start ---\n2024-09-25 22:19:10 [INFO] --- Starting server selection process ---\n2024-09-25 22:19:11 [INFO] API response data received.\n2024-09-25 22:19:11 [INFO] Current server IP found in WireGuard configuration: 79.135.105.20\n2024-09-25 22:19:11 [WARNING] Current server with IP 79.135.105.20 not found in filtered data.\n2024-09-25 22:19:11 [INFO] Current server not found in filtered list, checking full server list...\n2024-09-25 22:19:12 [INFO] API response data received.\n2024-09-25 22:19:13 [INFO] Current server BH#1 - Latency: 26.90 ms, Load: 10%, Score: 15.07\n2024-09-25 22:19:14 [INFO] Server AO#1 - Latency: 27.10 ms, Load: 7%, Score: 13.03\n2024-09-25 22:19:15 [INFO] Server AO#2 - Latency: 26.60 ms, Load: 14%, Score: 17.78\n2024-09-25 22:19:16 [INFO] Server AO#3 - Latency: 27.10 ms, Load: 9%, Score: 14.43\n2024-09-25 22:19:17 [INFO] Server AO#4 - Latency: 26.60 ms, Load: 16%, Score: 19.18\n2024-09-25 22:19:17 [INFO] Current server BH#1 (IP: 79.135.105.20) - Score: 15.07\n2024-09-25 22:19:17 [INFO] Selected best server AO#1 - Score: 13.03\n2024-09-25 22:19:17 [INFO] Current server BH#1 is within threshold. No switch necessary.\n2024-09-25 22:19:17 [INFO] --- ProtonVPN Auto Connect Script End ---\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cpre class=\"line-numbers language-bash\"\u003e\u003ccode class=\"language-bash\"\u003esudo cat /etc/wireguard/protonvpn.conf\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cp\u003eJe kunt ook controleren of alles werkt door het bestand protonvpn.conf te bekijken of door je externe ip-adres na te gaan.\u003c/p\u003e\n\u003cpre class=\"line-numbers language-ini\"\u003e\u003ccode class=\"language-ini\"\u003e[Peer]\n# BH#3 - Auto-generated by ProtonVPN Auto Connect - Score: 7.74\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cp\u003eJe ziet dan iets zoals hierboven. Deze comment wordt bijgewerkt zodra er verbinding wordt gemaakt met een andere VPN, en het ip-adres binnen de peer verandert dan uiteraard mee.\u003c/p\u003e\n\u003cpre class=\"line-numbers language-bash\"\u003e\u003ccode class=\"language-bash\"\u003ecurl ifconfig.me #this wil give the external ip in the cli.\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cp\u003eAls extra stap kun je ook je externe ip controleren. Die moet anders zijn dan het ip-adres dat je provider je geeft.\u003c/p\u003e\n\u003ch3 id=\"running-this-script-every-hour\"\u003eDit script elk uur draaien\u003c/h3\u003e\n\u003cpre class=\"line-numbers language-bash\"\u003e\u003ccode class=\"language-bash\"\u003esudo crontab -e\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cpre class=\"line-numbers language-text\"\u003e\u003ccode class=\"language-text\"\u003e0 * * * * cd /home/{user}/protonss \u0026amp;amp;\u0026amp;amp; /home/{user}/protonss/protonvpn_auto_connect.py \u0026amp;gt;\u0026amp;gt; /home/{user}/protonss/protonvpn_cron.log 2\u0026amp;gt;\u0026amp;amp;1\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cp\u003eHier hebben we ingesteld dat het elk uur draait, maar je kunt het plannen zoals je wilt.\u003c/p\u003e\n\u003cpre class=\"line-numbers language-bash\"\u003e\u003ccode class=\"language-bash\"\u003esudo crontab -l\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cp\u003eSla de crontab op en controleer dat cron is geïnstalleerd.\u003c/p\u003e\n\u003ch3 id=\"optional-block-specific-traffic-if-your-vpn-fails\"\u003eOptioneel: specifiek verkeer blokkeren als je VPN uitvalt\u003c/h3\u003e\n\u003cp\u003eAls je er zeker van wilt zijn dat er geen verkeer je device verlaat als de VPN-verbinding uitvalt, kun je \u003ccode\u003eiptables\u003c/code\u003e gebruiken om verkeer te blokkeren wanneer de WireGuard-interface down is.\u003c/p\u003e\n\u003cpre class=\"line-numbers language-bash\"\u003e\u003ccode class=\"language-bash\"\u003esudo apt-get install iptables\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cp\u003eControleer dat je iptables hebt geïnstalleerd, of installeer het met het bovenstaande commando.\u003c/p\u003e\n\u003cpre class=\"line-numbers language-bash\"\u003e\u003ccode class=\"language-bash\"\u003eifconfig -a\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cpre class=\"line-numbers language-text\"\u003e\u003ccode class=\"language-text\"\u003eprotonvpn: flags=209\u0026amp;lt;UP,POINTOPOINT,RUNNING,NOARP\u0026amp;gt;  mtu 1420\n        inet 10.2.1.3  netmask 255.255.255.255  destination 10.2.1.3\n        unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  txqueuelen 1000  (UNSPEC)\n        RX packets 612  bytes 101768 (99.3 KiB)\n        RX errors 0  dropped 0  overruns 0  frame 0\n        TX packets 765  bytes 101144 (98.7 KiB)\n        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cp\u003eBepaal eerst de naam van je netwerkinterface. Als je je bestand protonvpn.conf hebt genoemd, heet de interface protonvpn.\u003c/p\u003e\n\u003cpre class=\"line-numbers language-bash\"\u003e\u003ccode class=\"language-bash\"\u003esudo iptables -A OUTPUT -p tcp --dport 51413 ! -o protonvpn -j REJECT --reject-with icmp-port-unreachable\nsudo iptables -A OUTPUT -p udp --dport 51413 ! -o protonvpn -j REJECT --reject-with icmp-port-unreachable\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cp\u003eJe kunt elke regel aan je iptables toevoegen. De 2 regels hieronder zorgen ervoor dat poort 51413 wordt geweigerd zodra de netwerkinterface met de naam protonvpn niet beschikbaar is. Er is een regel voor UDP- en voor TCP-verkeer.\u003c/p\u003e\n\u003cpre class=\"line-numbers language-bash\"\u003e\u003ccode class=\"language-bash\"\u003esudo iptables -L OUTPUT -v -n\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cp\u003eNadat je alle benodigde regels hebt toegevoegd, kun je ze met dit commando controleren.\u003c/p\u003e\n\u003cpre class=\"line-numbers language-bash\"\u003e\u003ccode class=\"language-bash\"\u003esudo apt-get install iptables-persistent\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cp\u003eEn maak de regels persistent, zodat je ze niet bij elke reboot opnieuw hoeft toe te voegen.\u003c/p\u003e\n\u003cpre class=\"line-numbers language-bash\"\u003e\u003ccode class=\"language-bash\"\u003esudo netfilter-persistent save\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003ch3 id=\"conclusion\"\u003eConclusie\u003c/h3\u003e\n\u003cp\u003eMet dit Python-script is je Raspberry Pi altijd verbonden met de snelste beschikbare ProtonVPN-server, zodat je de best mogelijke VPN-prestaties hebt zonder handmatig ingrijpen. Of je je Pi nu gebruikt voor netwerken, torrenten of algemene online beveiliging: deze automatisering zorgt voor optimale prestaties en betrouwbare privacybescherming.\u003c/p\u003e\n\u003ch3 id=\"update20-12-2024\"\u003eUpdate - 20-12-2024\u003c/h3\u003e\n\u003cp\u003ePreferred_cities toegevoegd als optie om de selectie voor de VS te verkleinen. Dat land bevat anders ruim 2000 servers om te controleren. De performance-check kan zo in elk geval wat korter worden :-).\u003c/p\u003e\n\u003cp\u003eEn ik heb uitgevonden wat de Feature-sectie van elke server in de API betekent. Hieronder leg ik het uit. Ik heb mijn code daarnaar aangepast om er correct mee te werken. Let op: de configuratie die je eerder hebt gemaakt moet overeenkomen met het type server waarmee je verbindt. Als je configuratie niet is ingesteld om met een Secure Core te verbinden, selecteer dan ook geen Secure Core in dit script. Sluit aan bij de ProtonVPN-configuratie die je hebt aangemaakt.\u003c/p\u003e\n\u003cp\u003eUitleg van de features:\u003c/p\u003e\n\u003cp\u003eElke server heeft precies één feature-nummer:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e1, 17 = Secure Core-servers\u003c/li\u003e\n\u003cli\u003e2 = TOR-servers\u003c/li\u003e\n\u003cli\u003e0, 8, 16, 24 = Plus-only servers\u003c/li\u003e\n\u003cli\u003e4, 12, 20, 28 = P2P-servers (dat lijken ook Plus-servers te zijn)\u003c/li\u003e\n\u003c/ul\u003e\n\u003cpre class=\"line-numbers language-json\"\u003e\u003ccode class=\"language-json\"\u003e        {\n            \u0026amp;#34;Name\u0026amp;#34;: \u0026amp;#34;US-AZ#105\u0026amp;#34;,\n            \u0026amp;#34;EntryCountry\u0026amp;#34;: \u0026amp;#34;US\u0026amp;#34;,\n            \u0026amp;#34;ExitCountry\u0026amp;#34;: \u0026amp;#34;US\u0026amp;#34;,\n            \u0026amp;#34;Domain\u0026amp;#34;: \u0026amp;#34;node-us-260.protonvpn.net\u0026amp;#34;,\n            \u0026amp;#34;Tier\u0026amp;#34;: 2,\n            \u0026amp;#34;Features\u0026amp;#34;: 12,\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cp\u003eNa wat meer onderzoek naar vragen over de gratis servers van ProtonVPN blijkt dat ProtonVPN de publieke API-toegang tot hun free server-endpoints heeft verwijderd. Het /vpn/servers-endpoint dat eerder informatie over gratis servers had moeten leveren, geeft nu een 404-error:\u003c/p\u003e\n\u003cpre class=\"line-numbers language-json\"\u003e\u003ccode class=\"language-json\"\u003e{    \u0026amp;#34;Code\u0026amp;#34;: 404,    \u0026amp;#34;Error\u0026amp;#34;: \u0026amp;#34;Path not found\u0026amp;#34;,    \u0026amp;#34;Details\u0026amp;#34;: {}}\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cp\u003eOp dit moment is alleen het Plus-server-endpoint via de publieke\u003c/p\u003e\n\u003cp\u003eAPI bereikbaar: url = \u0026lsquo;\u003ca href=\"https://api.protonmail.ch/vpn/logicals\" data-external\u003ehttps://api.protonmail.ch/vpn/logicals\u003c/a\u003e\u0026rsquo;\u003c/p\u003e\n\u003cp\u003eDit is waarschijnlijk een bewuste keuze van ProtonVPN. Helaas kan dit script daardoor de gratis servers niet ondersteunen.\u003c/p\u003e\n","date_modified":"2025-09-01T09:21:58+02:00","date_published":"2024-09-25T22:24:00+02:00","id":"https://www.allsubjectsmatter.nl/nl/supercharge-your-headless-raspberry-pi-vpn-with-protonvpn-and-wireguard/","language":"nl","summary":"Deprecated: Beheer ProtonVPN op je headless device zoals een Raspberry Pi moeiteloos met dit Python-script. Maak automatisch verbinding met de snelste server via WireGuard voor optimale snelheid en veiligheid. Dit omzeilt error 422 met de protonvpn-cli client, die op dit moment kapot is.","tags":["Tech"],"title":"Deprecated: Geef je headless (Raspberry Pi) VPN een boost met ProtonVPN en WireGuard","url":"https://www.allsubjectsmatter.nl/nl/supercharge-your-headless-raspberry-pi-vpn-with-protonvpn-and-wireguard/"},{"authors":[{"name":"Remco Loup","url":"https://www.allsubjectsmatter.nl/author/remco/"}],"content_html":"\u003cp\u003eEr wordt al veel geschreven over Scrum. Scrum is dood, enzovoort. Nou, als je een vacature hebt voor een \u0026ldquo;Manager\u0026rdquo;, een \u0026ldquo;conciërge\u0026rdquo; of een andere duidelijk niet-Scrum functietitel, maar besluit het een vacature voor een Product Owner of Scrum Master te noemen, dan ben jij degene die het verkloot. Alleen het beest een andere naam geven zorgt er niet voor dat het ineens als magie werkt.\u003c/p\u003e\n\u003cp\u003eWe hebben het allemaal weleens gehoord: \u003cstrong\u003e\u0026ldquo;We doen Scrum, en hupsakee! We leveren in een mum van tijd waarde!\u0026rdquo;\u003c/strong\u003e (Spoiler alert) zo werkt het niet. Scrum is geen toverdrank die chaos in orde verandert door er wat ceremonies overheen te strooien. Het kost veel meer dan de stappen in de Scrum guide volgen om tot echte resultaten te komen. Het draait om \u003cstrong\u003eteamwork, discipline en de juiste mensen op de juiste plek\u003c/strong\u003e — en geloof het of niet, het verschilt niet zoveel van hoe dingen in het leger getraind worden ;-).\u003c/p\u003e\n\u003ch2 id=\"sprint-equals-mission\"\u003eSprint is een missie\u003c/h2\u003e\n\u003cp\u003eBeginnen we nog eens met de olifant in de kamer: het woord \u0026ldquo;sprint\u0026rdquo;. We zijn toch niet echt aan het hardlopen? Voor mij suggereert die term nog steeds haast, maar in werkelijkheid mikken we op een \u003cstrong\u003edoordachte, goed geplande missie\u003c/strong\u003e. Net als in het leger gaat het niet om snelheid, maar om precisie en succes. Een goed uitgevoerde missie gooi je niet zomaar in elkaar, die wordt strategisch gepland en vereist dat iedereen zijn rol speelt en zijn steentje bijdraagt tot de finish.\u003c/p\u003e\n\u003cp\u003eEen missie uitvoeren vraagt om aanpassing onderweg. Je gaat onbekenden tegenkomen en je moet erop voorbereid zijn om daarmee om te gaan in plaats van ze te vermijden of negeren en je missie te verkloten.\u003c/p\u003e\n\u003cblockquote class=\"pull-quote\"\u003e\u003cp\u003eOnbekenden oplossen als een \u003cstrong\u003e\u003cu\u003eTEAM\u003c/u\u003e\u003c/strong\u003e is wat een Agile/Scrum-aanpak laat werken.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"specialized-but-all-round\"\u003eGespecialiseerd, maar allround\u003c/h2\u003e\n\u003cp\u003eIn zowel Scrum-teams als militaire eenheden heeft iedereen z\u0026rsquo;n eigen specialiteiten en sterke kanten. Maar hier komt de kicker: we leveren waarde als team. Net als in het leger, waar soldaten cross-trained worden om gaten te vullen, \u003cstrong\u003ezijn wij ook allrounders\u003c/strong\u003e. Als er iets misgaat, steken we niet onze handen in de lucht en noemen we het een dag. We passen ons aan en klaren de klus, samen (als team!).\u003c/p\u003e\n\u003ch2 id=\"scrum-werkt-niet-zonder-de-juiste-mindset-scrum-doesne28099t-work-without-the-right-mindset\"\u003eScrum werkt niet zonder de juiste mindset {#scrum-doesn%E2%80%99t-work-without-the-right-mindset}\u003c/h2\u003e\n\u003cp\u003eScrum draait niet vanzelf. Alleen Scrum aannemen zorgt niet ineens voor waarde. Ik heb het zien gebeuren. Mensen denken dat als ze de ceremonies draaien en wat Scrum-gerelateerde stappen volgen, de magie vanzelf gebeurt. Maar zonder \u003cstrong\u003edat je team zelf het proces managet\u003c/strong\u003e, is het als een militaire missie draaien zonder kapitein, zonder briefing, en iedereen die maar wat aanmoddert. Misschien kom je wel tot het einde, maar wat heb je dan eigenlijk gedaan?\u003c/p\u003e\n\u003cp\u003eHet leger weet dit goed. Er is structuur, leiders die sturen, maar ook het vertrouwen dat elk teamlid proactief is. In Scrum heb je de juiste mix nodig: een \u003cstrong\u003eProduct Owner met een mandaat en een daadwerkelijke visie die het bedrijf ondersteunt\u003c/strong\u003e, een \u003cstrong\u003eScrum Master die de flow erin houdt\u003c/strong\u003e, en een \u003cstrong\u003eteam dat niet alleen taken uitvoert maar nadenkt over het grotere geheel\u003c/strong\u003e. Die mindset bereik je alleen door samen te werken en als één team met elkaar te blijven praten.\u003c/p\u003e\n\u003ch2 id=\"a-team-that-talks-about-the-hard-stuff\"\u003eEen team dat over de moeilijke dingen praat\u003c/h2\u003e\n\u003cp\u003eAls team moeten we samenwerken, open met elkaar praten en \u003cstrong\u003ede moeilijke dingen aanpakken\u003c/strong\u003e. Ik heb het niet over makkelijke gesprekken waarin iedereen het met elkaar eens is. Ik bedoel \u003cstrong\u003emoeilijke, ongemakkelijke discussies\u003c/strong\u003e over wat niet werkt of niet duidelijk is, wat beter moet en hoe je daar komt. Pas dan komt de juiste geleverde waarde bovendrijven.\u003c/p\u003e\n\u003cblockquote class=\"pull-quote\"\u003e\u003cp\u003eBespreek onbekenden tijdens je missie en pak ze aan. Een \u0026ldquo;daily scrum\u0026rdquo; aanhouden leert dat niet vanzelf.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eHoe denk je dat Apple de iPhone heeft gemaakt? Jobs en Ive zaten niet samen elkaar op de schouder te kloppen. Ze discussieerden, itereerden en duwden elkaar voort tot het product \u003cstrong\u003egoed\u003c/strong\u003e was. En zo zouden Scrum-teams moeten werken. Je doet niet gewoon wat makkelijk of snel is. Je werkt de problemen door, daagt elkaar uit en itereert tot je het hebt. Je stelt ook veel (WAAROM-)vragen, zodat je begrijpt waar ieder vandaan komt.\u003c/p\u003e\n\u003ch2 id=\"briefings-and-debriefings-are-crucial-but-only-if-you-commit-to-them\"\u003eBriefings en debriefings zijn cruciaal, maar alleen als je er iets voor doet\u003c/h2\u003e\n\u003cp\u003eIn Scrum hebben we onze \u003cstrong\u003ePlanning (briefing)\u003c/strong\u003e en onze \u003cstrong\u003eReview en Retrospective (debriefing)\u003c/strong\u003e. Klinkt bekend? Je kunt niet zomaar aan een missie beginnen zonder plan, en je kunt al helemaal niet verbeteren zonder terug te kijken naar wat er mis of goed ging. Maar, en hier zit de kneep: \u003cstrong\u003eje moet deze sessies ook echt gebruiken\u003c/strong\u003e waarvoor ze bedoeld zijn. Te vaak worden ze overhaast gedaan of afgevinkt. In het leger kan een mislukte briefing of debrief levens kosten, in Scrum kost het je de mogelijkheid om bij de volgende missie te itereren en dingen te testen en te verbeteren. Neem ze serieus, anders zijn het gewoon lege meetings die geen waarde leveren.\u003c/p\u003e\n\u003ch2 id=\"teamwork-is-everything\"\u003eTeamwork is alles\u003c/h2\u003e\n\u003cp\u003eUiteindelijk slagen zowel Scrum als militaire missies door \u003cstrong\u003eteamwork\u003c/strong\u003e. Je hebt mensen nodig die betrokken zijn, die communiceren en die voor elkaar klaarstaan. Zonder dat ben je gewoon een stel mensen die roept dat ze Scrum doen, en dat levert helemaal geen waarde op. Scrum draait niet om helden, maar om het \u003cstrong\u003ecollectieve doel\u003c/strong\u003e. Je wint samen, je verliest samen.\u003c/p\u003e\n\u003ch2 id=\"scrum-isnt-a-holy-hammer-that-solves-everything\"\u003eScrum is geen heilige hamer die alles oplost\u003c/h2\u003e\n\u003cp\u003eOké, even dit: \u003cstrong\u003eScrum is een framework\u003c/strong\u003e. Een door mensen bedachte structuur, bedoeld om teams efficiënt samen te laten werken. Maar Scrum doen levert niet automatisch waarde op voor je bedrijf. Je hebt de \u003cstrong\u003ejuiste mensen\u003c/strong\u003e nodig, de \u003cstrong\u003ejuiste mindset\u003c/strong\u003e en een duidelijke \u003cstrong\u003emissie\u003c/strong\u003e waar iedereen naartoe werkt. Anders ben je gewoon taken aan het afvinken zonder erover na te denken — en dat gaat mis.\u003c/p\u003e\n\u003cp\u003eNog iets: pas je manier van werken aan aan wat je bedrijf nodig heeft. Scrum geeft een structuur waar je je aan kunt aanpassen. Maar wees niet bang om dingen te veranderen als dat beter werkt voor je organisatie. Het doel is een team dat graag werkt binnen de structuur en waarde levert in plaats van features.\u003c/p\u003e\n\u003cp\u003eJe als team, of zelfs als organisatie, moet het \u003cstrong\u003elaten werken\u003c/strong\u003e door de juiste mensen (met visie en mandaat) en processen op zijn plek te zetten. Doe je dat niet, dan eindig je met een noodlanding: niets opgeleverd, alleen frustratie.\u003c/p\u003e\n\u003cp\u003eLaat solo managers die vooral zichzelf er goed uit willen laten komen je drive naar waarde en het écht helpen van de business niet overnemen. Niet de business schaden.\u003c/p\u003e\n","date_modified":"2024-09-17T09:14:28+02:00","date_published":"2024-09-10T22:03:00+02:00","id":"https://www.allsubjectsmatter.nl/nl/scrum-and-the-army-a-different-kind-of-mission/","language":"nl","summary":"Scrum is geen quick fix. Het is als een missie plannen zoals het leger dat doet. Een sprint gaat niet over snelheid, maar over precisie, teamwork en het omgaan met het onverwachte onderweg.","tags":["Agile"],"title":"Scrum en het leger, een ander soort missie.","url":"https://www.allsubjectsmatter.nl/nl/scrum-and-the-army-a-different-kind-of-mission/"},{"authors":[{"name":"Remco Loup","url":"https://www.allsubjectsmatter.nl/author/remco/"}],"content_html":"\u003ch3 id=\"introduction\"\u003eInleiding\u003c/h3\u003e\n\u003cp\u003eIn deze post wil ik databaseperformance binnen Azure belichten, met een directe vergelijking tussen MySQL, MariaDB en PostgreSQL. Er is veel literatuur over het onderwerp, maar praktische tests die de performance in de praktijk meten, zijn schaars.\u003c/p\u003e\n\u003cp\u003eVeel discussies draaien om de verschillen tussen deze databases in plaats van om hun daadwerkelijke performance. Toen ik dit onderzoek begon, stonden niet de verschillen voorop, maar de zoektocht naar een performante database die een goed Azure-alternatief kon zijn voor onze MariaDB-monolith. Aanvankelijk dachten we dat de overstap van MariaDB naar MySQL een simpele vervanging was, met nauwelijks impact op de performance. Daar hadden we het mis!\u003c/p\u003e\n\u003cblockquote class=\"pull-quote\"\u003e\u003cp\u003e\u0026ldquo;There are many opinions about performance, But we needed facts\u0026rdquo;\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eWe zagen een enorme performance-regressie bij elke query. Performance is cruciaal voor ons, en het was verwarrend om te zien dat MySQL zo ver achterbleef bij MariaDB. Toen we dit ontdekten, hebben we zelf een MariaDB-VM in Azure opgezet. Die was weer wel performant, maar bracht veel extra onderhoud en beheer met zich mee: uptime garanderen, hot standby, backups.\u003c/p\u003e\n\u003cp\u003eDaarom ging ik PostgreSQL onderzoeken, in de hoop op betere performance dan MySQL bood. Met deze introductie: de tests die we hebben gedaan, met een complexe query en een set kleine queries, om de performanceverschillen tussen de drie databases en hun implementaties binnen Azure te meten. We hebben ook Docker-tests toegevoegd om te kunnen vaststellen of de performanceverschillen aan de databases liggen of dat Azure de oorzaak is.\u003c/p\u003e\n\u003ch2 id=\"testing-our-complex-query\"\u003eOnze complexe query testen\u003c/h2\u003e\n\u003cp\u003eOm de databases te beoordelen draaiden we een complexe query die data uit meerdere tabellen aggregeerde, een realistische workload voor ons systeem. Om vertrouwelijkheidsredenen hebben we de tabel- en kolomnamen in deze beschrijving aangepast. Deze query moest laten zien hoe elke database omgaat met data-aggregatie en complexe joins.\u003c/p\u003e\n\u003cpre class=\"line-numbers language-sql\"\u003e\u003ccode class=\"language-sql\"\u003eWITH AggregatedSkateData AS (\nSELECT\n    e.id AS eventID,\n    e.theme_cid AS themeCID,\n    s.id AS skateID,\n    p.firstName || \u0026amp;#39; \u0026amp;#39; || p.lastName AS participantName,\n    l.label AS locationLabel,\n    s.priceOffered,\n    s.inspectionCompleted,\n    s.registrationDate,\n    s.eventStartDate,\n    org.name AS organizerName,\n    \u0026amp;#39;https://skateeventimages.net/\u0026amp;#39; || im.path AS organizerImageURL,\n    s.registrationCode,\n    COUNT(DISTINCT CASE WHEN pa_last.status_cid = 0 OR pa_last.id IS NULL THEN sp.id END) AS partsPending,\n    COUNT(DISTINCT CASE WHEN pa_last.status_cid = 1 THEN sp.id END) AS partsRequested,\n    COUNT(DISTINCT CASE WHEN pa_last.status_cid = 2 THEN sp.id END) AS partsConfirmed,\n    COUNT(DISTINCT CASE WHEN pa_last.status_cid = 8 THEN sp.id END) AS partsDenied,\n    MAX(CASE WHEN ti_unscanned.id IS NOT NULL THEN 1 ELSE 0 END) AS hasUnscannedTickets,\n    s.isPublic\nFROM\n    skateEvents s\n    INNER JOIN events e ON s.eventID = e.id\n    INNER JOIN participants p ON p.id = e.participantID\n    LEFT JOIN organizers org ON org.id = e.organizerID\n    LEFT JOIN images im ON im.id = org.profileImageID\n    LEFT JOIN locations l ON l.id = s.locationID\n    LEFT JOIN skateParts sp ON sp.eventID = s.id\n    LEFT JOIN partApprovals pa ON pa.id = sp.partApprovalID\n    LEFT JOIN ticketsUnscanned ti_unscanned ON ti_unscanned.id = sp.ticketID AND ti_unscanned.scannedAt IS NULL\n    LEFT JOIN partApprovalActions pa_last ON pa_last.id = (\n        SELECT pa2.id\n        FROM partApprovalActions pa2\n        WHERE pa2.partID = pa.id\n        ORDER BY pa2.id DESC\n        LIMIT 1\n    )\nWHERE\n    s.registrationDate IS NOT NULL\n    AND e.cancelledAt IS NULL\n    AND e.deletedAt IS NULL\n    AND s.isArchived = false\n    AND s.deletedAt IS NULL\nGROUP BY\n    e.id, e.theme_cid, s.id, p.id, p.firstName, p.lastName, org.id, im.id, l.id, l.label, s.priceOffered, s.inspectionCompleted, s.registrationDate, s.eventStartDate, org.name, im.path, s.registrationCode, s.isPublic\n)\nSELECT * FROM AggregatedSkateData\nWHERE\n    TO_CHAR(s.registrationDate, \u0026amp;#39;YYYY-MM\u0026amp;#39;) = \u0026amp;#39;2024-03\u0026amp;#39;\n    OR partsDenied \u0026#43; partsRequested \u0026#43; partsPending \u0026amp;gt; 0\nORDER BY s.registrationDate DESC\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003ch3 id=\"azure-complicated-query\"\u003eAzure, complexe query\u003c/h3\u003e\n\u003cp\u003eWe hebben deze tests binnen dezelfde Azure-zone gedraaid voor containers en databases, om interzone-latency te vermijden. Hieronder de specificaties en resultaten, voor de transparantie. Opvallend: bij MariaDB en MySQL hadden de performancespecificaties nauwelijks invloed op de resultaten, mits we de eerste (burstable) tier vermeden. PostgreSQL was de uitzondering, met duidelijke verbeteringen bij de runtime van grote queries.\u003c/p\u003e\n\u003cp\u003eMet HeidiSQL via de Azure VPN hebben we onze beste resultaten van vijf runs bepaald.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eMySQL Flexible, Zone 2 (Standard_E4ds_v5, 450 iops)\u003c/strong\u003e\u003cbr\u003e\nFound rows: 130\u003cbr\u003e\nDuration for 1 query: 13,875 sec\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eMariaDB VM, Zone 2 (Standard B4s v2 (4 vcpus, 16 GiB memory))\u003c/strong\u003e\u003cbr\u003e\nFound rows: 130\u003cbr\u003e\nDuration for 1 query: 10,625 sec\u003cbr\u003e\n23,4% beter dan MySQL\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePostgres Flexible, Zone 3 (Standard_E4ds_v50 p10, 500 iops)\u003c/strong\u003e\u003cbr\u003e\nFound rows: 130\u003cbr\u003e\nDuration for 1 query: 3,953 sec\u003cbr\u003e\n71,5% beter dan MySQL\u003c/p\u003e\n\u003ch3 id=\"local-docker-complicated-query\"\u003eLokale Docker, complexe query\u003c/h3\u003e\n\u003cp\u003eDe hardware waarop we lokaal hebben getest:\u003c/p\u003e\n\u003cp\u003e12th Gen Intel(R) Core(TM) i9-12900H, 2900 Mhz, 14 Core(s), 20 Logical Processor(s)\u003cbr\u003e\nInstalled Physical Memory (RAM) 32,0 GB\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eMySQL 8.3 Docker\u003c/strong\u003e\u003cbr\u003e\nFound rows: 130\u003cbr\u003e\nDuration for 1 query: 12,437 sec\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eMariaDB 11.4 Docker\u003c/strong\u003e\u003cbr\u003e\nFound rows: 130\u003cbr\u003e\nDuration for 1 query: 9,484 sec\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePostgres 16.2 Docker\u003c/strong\u003e\u003cbr\u003e\nFound rows: 130\u003cbr\u003e\nDuration for 1 query: 3,969 sec\u003c/p\u003e\n\u003cp\u003eDaarmee zijn de tests met de complexe query afgerond. Geweldig om te zien dat Postgres hier zo goed presteert! Voor onze overstap naar Azure draaiden we op MariaDB. Het was behoorlijk vervelend om zo\u0026rsquo;n slechte performance in MySQL te zien zodra we met de nieuwe setup gingen testen.\u003c/p\u003e\n\u003ch2 id=\"testing-our-set-small-querys\"\u003eOnze set kleine queries testen\u003c/h2\u003e\n\u003cfigure class=\"figure\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003csource type=\"image/webp\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/mysql-mariadb-postgresql-performance-compared-in-azure/images/image-2_hu_c11062909b9cd6ee.webp\" alt=\"\" width=\"560\" height=\"171\" data-zoom-src=\"https://www.allsubjectsmatter.nl/mysql-mariadb-postgresql-performance-compared-in-azure/images/image-2_hu_44990348cc6259d6.webp\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003c/figure\u003e\n\n\u003cp\u003eDe volgende test vuurt meer dan 20.000 transacties per seconde af op elke database, waarmee we een feed in JSON bouwen die een bepaalde tijd nodig heeft om af te ronden. Met veel queries belasten we de database beter, wat ons een gemiddelde runtime per database geeft.\nEr zit ook wat PHP-logica in, want dit is een PHP-script dat we draaien om de feed te genereren. Maar dat draait altijd op dezelfde hardware en hetzelfde docker-image, dus het verschil in runtime komt van de database.\nHiervoor hebben we onze containers naar dezelfde zone verplaatst als waar de database draait, om ook mogelijke problemen met zone-latency uit te sluiten.\u003c/p\u003e\n\u003cp\u003eWe draaien dit script binnen ons Laravel-framework. Onze PHP draait tegen \u003ca href=\"https://openswoole.com/\" data-external\u003eSwoole\u003c/a\u003e voor de best mogelijke performance binnen PHP 8.\u003c/p\u003e\n\u003ch3 id=\"azure-small-querys\"\u003eAzure, kleine queries\u003c/h3\u003e\n\u003cp\u003eIn deze test komen ook aspecten van containerperformance kijken.\nWe draaien onze containers en database weer in dezelfde zone in Azure (West-Europa). Binnen Azure draaien we met een stuk minder performance dan we lokaal in onze docker-omgeving tot onze beschikking hebben.\u003c/p\u003e\n\u003cp\u003eDaarom kunnen we deze resultaten alleen gebruiken om de verschillen binnen docker en binnen Azure zelf te vergelijken, niet tussen die twee. Die met elkaar vergelijken zegt niets door dit hardwareverschil. We willen alleen het verschil in runtime tussen de databases zien, en Docker is de baseline om te checken of de verschillen overeenkomen met wat we in Azure zien.\u003c/p\u003e\n\u003cp\u003eOnze node size in Azure is een standard_b2s, 2 vCPU\u0026rsquo;s met 4 GiB RAM.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eAPI, Zone 2 -\u0026gt; MariaDB VM, Zone 2\u003c/strong\u003e\u003cbr\u003e\n24,59s runtime (spot nodes)\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eAPI, Zone 2 -\u0026gt; MySQL Flexible, Zone 2\u003c/strong\u003e\u003cbr\u003e\n27,87s runtime (spot nodes)\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eAPI, Zone 3 -\u0026gt; Postgres Flexible, Zone 3\u003c/strong\u003e\u003cbr\u003e\n24,92s runtime (spot nodes)\u003c/p\u003e\n\u003ch3 id=\"local-docker-small-querys\"\u003eLokale Docker, kleine queries\u003c/h3\u003e\n\u003cp\u003eVoor de kleine query-tests binnen lokale Docker gebruiken we een systeem met een 12th Gen Intel(R) Core(TM) i9-12900H CPU, draaiend op 2900 MHz met 14 cores en 20 logical processors, aangevuld met 32 GB RAM.\u003c/p\u003e\n\u003cp\u003eDeze Docker images zijn zo geconfigureerd dat ze memory-bound zijn, wat betekent dat de prestaties vooral afhankelijk waren van het beschikbare geheugen en niet beperkt werden door de CPU. De CPU-instellingen stonden op onbeperkte prestaties, zodat Docker zoveel CPU-bronnen kon gebruiken als nodig.\u003c/p\u003e\n\u003cp\u003eVoor elke testrun tegen een database herstartte ik de Docker container om de verbinding te verversen. Na de herstart hield ik een pauze van 30 seconden aan om er zeker van te zijn dat de container volledig gestabiliseerd en idle was. Die pauze is belangrijk voor consistentere testomstandigheden. Zodra de container klaar was, startte ik het feed generator script om de feed op te bouwen, om zo te beoordelen hoe goed onze verschillende database-setups kleine queries aankunnen onder deze gecontroleerde omstandigheden.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eAPI -\u0026gt; MariaDB 11.4 Docker\u003c/strong\u003e\u003cbr\u003e\n22.88s runtime\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eAPI -\u0026gt; MySQL 8.3 Docker\u003c/strong\u003e\u003cbr\u003e\n23.74s runtime\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eAPI -\u0026gt; Postgres 16.2 Docker\u003c/strong\u003e\u003cbr\u003e\n20.77s runtime\u003c/p\u003e\n\u003ch2 id=\"mariadb-query-cache\"\u003eMariaDB query cache\u003c/h2\u003e\n\u003cp\u003eIn ons eerdere gebruik van MariaDB binnen onze monolithische setup maakten we veelvuldig gebruik van de query cache. Deze functionaliteit speelde een cruciale rol bij het leveren van prestaties voor herhaalde queries. Die efficiëntie houdt stand totdat de cache herbouwd moet worden, meestal getriggerd door updates of inserts op de tabellen van de gecachte query. We doen een tweede run op onze complexe query met de query cache ingeschakeld.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eLocal MariaDB Docker\u003c/strong\u003e\u003cbr\u003e\nFound rows: 130\u003cbr\u003e\nDuration for 1 query: 0,000 sec\u003c/p\u003e\n\u003cp\u003eDe prestatiewinst van de query cache is echter niet overal even groot. Bij bijvoorbeeld gemiddeld 20.000 transacties per seconde met kleine queries zakte de runtime enigszins in. Deze variatie laat zien dat de query cache niet altijd hetzelfde niveau van optimalisatie oplevert, zeker onder hoge belasting. Maar daar later meer over.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eAPI -\u0026gt; Local MariaDB Docker (query cache enabled)\u003c/strong\u003e\u003cbr\u003e\n17.30s runtime\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eAPI, Zone 2 -\u0026gt; MariaDB VM, Zone 2 (query cache enabled)\u003c/strong\u003e\u003cbr\u003e\n22.72s runtime (spot nodes)\u003c/p\u003e\n\u003cp\u003eDe query cache in MariaDB werkt door de result set van SELECT queries op te slaan, samen met de querytekst. Wanneer een identieke query opnieuw wordt uitgevoerd (met dezelfde tekst en onder dezelfde database-userrechten), kan MariaDB de query-uitvoerfase overslaan en het resultaat direct uit de cache halen. Dit kan de uitvoertijd van vaak gedraaide queries die geen data wijzigen drastisch verlagen.\u003c/p\u003e\n\u003cp\u003eWees voorzichtig met het gebruik van de query cache van MariaDB. Ondanks de voordelen is de query cache deprecated en standaard uitgezet in nieuwere versies van MariaDB 10.1.7 (en in MySQL vanaf versie 5.7.20), vanwege zorgen over de impact op prestaties in omgevingen met veel gelijktijdige transacties. Voor specifieke scenario\u0026rsquo;s, met name met een overwegend read-workload, kan de query cache nog steeds aanzienlijk betere prestaties opleveren. In MySQL 8 en hoger is hij volledig verwijderd.\u003c/p\u003e\n\u003ch2 id=\"observation-on-azures-flexible-servers\"\u003eObservatie over de Flexible Servers van Azure\u003c/h2\u003e\n\u003cp\u003eDeze tests laten zien dat de prestaties van databases op de flexible servers van Azure aanzienlijk zijn verbeterd en dicht bij de prestaties van lokale Docker-omgevingen liggen. Dat is een flinke verbetering ten opzichte van de eerdere Azure database single servers, die doorgaans minder prestaties leverden door een vorm van overhead die voortkwam uit de manier waarop ze in de Azure-cloud draaiden.\u003c/p\u003e\n\u003ch2 id=\"azure-latency-anomaly\"\u003eAzure latency-anomalie\u003c/h2\u003e\n\u003cp\u003eAls bonus hebben we nog wat extra tests gedaan om de interzone-latency te meten bij het verbinden van Zone 1 of 2 met een PostgreSQL-database in Zone 3 binnen Azure.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eAPI, Zone 2 -\u0026gt; Azure PostgreSQL Flexible, Zone 3\u003c/strong\u003e\u003cbr\u003e\nDe runtime was 44.23 seconden.\u003c/p\u003e\n\u003cp\u003eInteressant: de runtime verdubbelde opnieuw, een fenomeen dat we uitsluitend zien bij communicatie tussen Zone 1-2 en Zone 3, of andersom. Dit patroon suggereert dat elke container die een databaseverbinding nodig heeft onder deze omstandigheden aanzienlijke latency ondervindt, terwijl de latency vrijwel verwaarloosbaar is bij interactie tussen Zone 1 en Zone 2.\u003c/p\u003e\n\u003cp\u003eVoor wie het gemist heeft: ik heb eerder een latency-anomalie in Zone 3 besproken. De gangbare wijsheid is dat je de databaseverbinding binnen dezelfde zone houdt om latency te minimaliseren. Onze tests laten echter zien dat de latency-impact minimaal is tussen Zone 1 en 2, maar aanzienlijk wordt zodra je van Zone 1 of 2 naar Zone 3 gaat.\u003c/p\u003e\n\u003cp\u003eDeze tests bevestigen opnieuw dat het latency-probleem niet exclusief is voor MySQL Flexible services, maar ook PostgreSQL Flexible services binnen Azure raakt, wat wijst op een breder patroon in het database-aanbod van Azure.\u003c/p\u003e\n\u003cfigure class=\"bookmark\"\u003e\u003ca class=\"bookmark__link\" href=\"https://www.allsubjectsmatter.nl/nl/azure-western-europe-zone-3-latency-issue-exploration/\"\u003e\u003cspan class=\"bookmark__body\"\u003e\u003cspan class=\"bookmark__title\"\u003eLatency Anomaly: An attempt to untangle Azure Performance issues in Western Europe\u003c/span\u003e\u003cspan class=\"bookmark__description\"\u003eUnveiling a significant Azure performance issue: Zone 3’s unexpected latency anomaly in the Western Europe region challenges cloud deployment strategies\u003c/span\u003e\u003cspan class=\"bookmark__meta\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"25px\"\u003e\u003csource type=\"image/webp\" sizes=\"25px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/mysql-mariadb-postgresql-performance-compared-in-azure/bookmarks/01-icon_hu_9b466dd874965fcb.webp\" alt=\"\" class=\"bookmark__icon\" width=\"50\" height=\"50\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003cspan\u003eAll Subjects Matter\u003c/span\u003e\u003cspan\u003eRemco Loup.\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"bookmark__thumb\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/webp\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/avif\" sizes=\"250px\"\u003e\u003csource type=\"image/webp\" sizes=\"250px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/mysql-mariadb-postgresql-performance-compared-in-azure/bookmarks/01-thumb_hu_27e6839ff9d3a962.webp\" alt=\"\" width=\"500\" height=\"435\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003c/span\u003e\u003c/a\u003e\u003c/figure\u003e\n\n\u003ch2 id=\"conclusion\"\u003eConclusie\u003c/h2\u003e\n\u003cp\u003eVanuit prestatieoogpunt is het duidelijk dat de keuze voor de juiste database cruciaal is en moet aansluiten bij de specifieke snelheids- en efficiëntiebehoeften van je app. De resultaten laten zien dat MySQL vaak trager is. MariaDB presteert goed en heeft een handige query cache, maar Azure ondersteunt MariaDB niet meer. Daarmee is PostgreSQL de meest logische keuze voor prestatiegerichte gebruikers in Azure, want het blinkt uit in het verwerken van zowel complexe queries als grote aantallen simpele queries, zoals de tests hierboven laten zien.\u003c/p\u003e\n\u003cp\u003eHopelijk helpt deze analyse bij het kiezen van de juiste databasetechnologie en onderstreept ze hoe belangrijk zorgvuldige planning is voor het verbeteren van je app-prestaties. Voor wie Azure gebruikt en topprestaties zoekt, is PostgreSQL in mijn testresultaten de beste optie.\u003c/p\u003e\n\u003cp\u003eEr zijn genoeg andere redenen om je database te kiezen. Maar in deze post wilde ik me alleen richten op performance. Dat was onze grootste zorg, want we zijn bezig met de overstap van een monolithische server naar Azure. Bij een database-switch komt nog meer kijken. Het voordeel hier is dat alle drie de databases zich redelijk makkelijk laten integreren, welke je ook kiest.\u003c/p\u003e\n\u003cp\u003ePostgres is hier het best compatibel dankzij zijn strikte routines. De gecompliceerde query hierboven is geschreven voor Postgres en draait in MariaDB zonder één wijziging. Voor MySQL hoef je alleen de TO_CHAR in de laatste regel te vervangen door DATE_FORMAT.\u003c/p\u003e\n","date_modified":"2024-03-30T18:59:39+01:00","date_published":"2024-03-29T00:04:45+01:00","id":"https://www.allsubjectsmatter.nl/nl/mysql-mariadb-postgresql-performance-compared-in-azure/","language":"nl","summary":"Een kijkje in de databaseprestaties van Azure, tijdens een reis langs MySQL, MariaDB en PostgreSQL. Ontdek welke database de race aanvoert.","tags":["Tech"],"title":"Azure + MySQL, MariaDB, PostgreSQL: prestaties vergeleken","url":"https://www.allsubjectsmatter.nl/nl/mysql-mariadb-postgresql-performance-compared-in-azure/"},{"authors":[{"name":"Remco Loup","url":"https://www.allsubjectsmatter.nl/author/remco/"}],"content_html":"\u003ch2 id=\"introduction\"\u003eIntroductie\u003c/h2\u003e\n\u003cp\u003eWe zijn begonnen aan een overstap van een traditionele VM-setup bij TransIP naar de schaalbare cloudinfrastructuur van Microsoft Azure. Die reis draait net zo goed om ontdekking als om prestaties. Met de expertise van \u003ca href=\"https://www.pionative.com/\" data-external\u003ePionative\u003c/a\u003e als leidraad verhuizen we onze omgeving niet alleen; we proberen haar in Azure opnieuw op te bouwen, waarbij we elke stap zorgvuldig testen en analyseren. Ons doel? Optimale prestaties in deze nieuwe architectuur, en eventuele bottlenecks of verborgen problemen in onze nieuwe cloudarchitectuur zo vroeg mogelijk op het spoor komen.\u003c/p\u003e\n\u003ch2 id=\"azure-regions-and-zones\"\u003eAzure-regions en zones\u003c/h2\u003e\n\u003cp\u003eDe infrastructuur van Azure beslaat de hele wereld, georganiseerd in regions voor gelokaliseerde diensten, en verder onderverdeeld in zones voor hoge beschikbaarheid. \u003cstrong\u003eRegions\u003c/strong\u003e zijn specifieke geografische gebieden waar Azure-diensten draaien, terwijl \u003cstrong\u003ezones\u003c/strong\u003e fysieke locaties binnen die regions zijn, elk met eigen voorzieningen om storingen te isoleren.\u003c/p\u003e\n\u003ch2 id=\"the-western-europe-region\"\u003eDe region Western Europe\u003c/h2\u003e\n\u003cp\u003eDe region Western Europe in Azure is opgebouwd uit drie aparte availability zones. Deze zones bieden een extra laag redundantie en fouttolerantie door datacenters binnen de region fysiek van elkaar te scheiden. Deze opzet moet garanderen dat zelfs bij een volledige uitval van één zone de diensten in de andere zones gewoon doordraaien — een stevige disaster recovery-oplossing voor bedrijven die in Europa actief zijn. Waar Zone 1, 2 en 3 geografisch precies liggen, is mij niet bekend.\u003c/p\u003e\n\u003ch2 id=\"performance-anomaly-in-the-western-europe-region\"\u003ePrestatie-anomalie in de region Western Europe\u003c/h2\u003e\n\u003cp\u003eOns testprogramma bracht ons bij een anomalie die het middelpunt van onze Azure-verkenning in deze post vormt: een duidelijk latency-probleem in Zone 3 van de region Western Europe. Deze ontdekking kwam niet meteen; ze kwam uit dagen prestatietesten en scherpe observatie van patronen die afweken van wat we verwachtten. Dat komt vooral doordat we Azure nog maar net aan het leren zijn, en door het prestatieverschil met onze huidige setup.\u003c/p\u003e\n\u003cp\u003eEerst hebben we onze eigen pods, code en prestatie-instellingen binnen de node pool uitgesloten. Database-prestatie-instellingen en dat soort zaken.\u003c/p\u003e\n\u003ch2 id=\"methodology-and-findings\"\u003eMethodiek en bevindingen\u003c/h2\u003e\n\u003cp\u003eOnze uiteindelijke aanpak was behoorlijk grondig om dit resultaat te verifiëren. We deployden twee node pools in elk van de drie zones van Microsoft Azure binnen de region Western Europe, om nodespecifieke verschillen uit te sluiten. Al die pools hadden exact dezelfde prestatie-instellingen.\u003c/p\u003e\n\u003cp\u003eUit uitgebreide tests — waaronder een hele reeks database-aanroepen en de cron jobs die essentieel zijn voor de functionaliteit van onze applicatie — zagen we een consistent en verrassend patroon. Ongeacht de gerapporteerde capaciteit of drukte van de zone.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eInteracties met de database waren aanzienlijk trager zodra de database óf de verbindende pods in Zone 3 stonden.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"performance-results\"\u003ePrestatieresultaten\u003c/h2\u003e\n\u003cp\u003eHieronder onze situatie met een cronjob die lokaal in Docker tegen onze huidige setup zo\u0026rsquo;n 32 seconden nodig heeft. Die setup is niet vergelijkbaar met MS Azure, maar gaf ons wel een prestatiematrix en een doel. Het script (de cronjob) dat we draaiden vuurt zo\u0026rsquo;n 32.000 queries af om een feed te bouwen van alle producten op onze website. Ja, dat is veel, maar daar gaat het hier niet om. We wilden het prestatieprobleem blootleggen. We weten dat dit script geoptimaliseerd kan worden om beter te presteren.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eMySQL-database in Zone 2\u003c/strong\u003e\u003cbr\u003e\nZone 1 – pool 1: 33 seconden\u003cbr\u003e\nZone 1 – pool 2: 32 seconden\u003c/p\u003e\n\u003cp\u003eZone 2 –  pool 1:  32 seconden\u003cbr\u003e\nZone 2 –  pool 2:  32 seconden\u003c/p\u003e\n\u003cp\u003eZone 3 –  pool 1: 115 seconden\u003cbr\u003e\nZone 3 –  pool 2: 117 seconden\u003c/p\u003e\n\u003cp\u003eEn toen, als klap op de vuurpijl, hebben we de setup omgedraaid met een database in Zone 3. Zie hieronder de resultaten die we hoopten te zien om dit onderzoek af te sluiten.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eMySQL-database in Zone 3\u003c/strong\u003e\u003cbr\u003e\nZone 1: 120 seconden\u003cbr\u003e\nZone 2: 122 seconden\u003cbr\u003e\nZone 3: 33 seconden\u003c/p\u003e\n\u003cp\u003eWe zagen dezelfde consistentie terugkomen, elke keer opnieuw, ook toen we minder maar grotere database-aanroepen draaiden.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eMySQL-database in Zone 2\u003c/strong\u003e\u003cbr\u003e\n16 DB-queries, single threaded.\u003c/p\u003e\n\u003cp\u003eZone 1: Totale tijd voor queries uit log: 9,57 ms\u003cbr\u003e\nZone 2: Totale tijd voor queries uit log: 9 ms\u003cbr\u003e\nZone 3: Totale tijd voor queries uit log: 49,96 ms\u003c/p\u003e\n\u003ch2 id=\"website-performance-in-azure\"\u003eWebsiteprestaties in Azure\u003c/h2\u003e\n\u003cp\u003eAls we onze database in Zone 2 draaien met de API-pod in Zone 1, daalt de initiële laadtijd van onze website met minstens 400 milliseconden bij de eerste page load, vergeleken met de configuratie waarin de API-pod in Zone 3 staat.\u003c/p\u003e\n\u003cp\u003eDat betekent dat de setup met de database in Zone 2 en de API-pod in Zone 1 een veel snellere initiële responstijd voor de website oplevert dan wanneer de API-pod in Zone 3 staat en de database in Zone 2.\u003c/p\u003e\n\u003cfigure class=\"figure\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003csource type=\"image/webp\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/azure-western-europe-zone-3-latency-issue-exploration/images/image-1_hu_239c52cf6c075a2e.webp\" alt=\"\" width=\"560\" height=\"129\" data-zoom-src=\"https://www.allsubjectsmatter.nl/azure-western-europe-zone-3-latency-issue-exploration/images/image-1_hu_5c52b5671bc6fe37.webp\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003cfigcaption\u003eDe performance van de eerste call op onze website, in een optimale setup in Azure.\u003c/figcaption\u003e\u003c/figure\u003e\n\n\u003cp\u003eEr zit hier een foutmarge, maar uit deze resultaten kunnen we concluderen dat je, als je je API geografisch over twee zones wilt verdelen, nooit consistent kunt presteren met Zone 3. Zones 1 en 2 zijn de juiste keuze.\u003c/p\u003e\n\u003cp\u003eWe moeten eerlijk zijn: deze prestatie-anomalie doet zich alleen voor bij een pod met een databaseverbinding. Onze website (die geen databaseverbinding heeft) in Zone 3 draaien had vrijwel geen meetbaar effect, zolang de API-pod in Zone 1 draaide en de database in Zone 2 stond.\u003c/p\u003e\n\u003ch2 id=\"diving-deeper-in-zone-3s-latency-issues\"\u003eDieper duiken in de latency-problemen van Zone 3\u003c/h2\u003e\n\u003cp\u003eDit prestatieverschil in Zone 3 was voor ons verbijsterend. Volgens de best practices van Microsoft zou de fysieke nabijheid van applicatieservers bij hun databases binnen dezelfde regio de latency moeten minimaliseren. Onze bevindingen schetsten echter een ander beeld: de keuze van een zone binnen een regio kan flink invloed hebben op de performance van je applicatie.\u003c/p\u003e\n\u003cfigure class=\"figure\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003csource type=\"image/webp\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/azure-western-europe-zone-3-latency-issue-exploration/images/data-src-image-68bb083d-b85e-44ae-9919-8404530f242e_hu_f23e2d3a977b47e3.webp\" alt=\"\" width=\"560\" height=\"235\" data-zoom-src=\"https://www.allsubjectsmatter.nl/azure-western-europe-zone-3-latency-issue-exploration/images/data-src-image-68bb083d-b85e-44ae-9919-8404530f242e_hu_dda4b94313ff73ba.webp\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003c/figure\u003e\n\n\u003cp\u003eOns onderzoek was behoorlijk grondig. We hebben mogelijke variabelen uitgesloten, zoals onze eigen codebase, PHP-configuratie, MySQL-performance en meer. De aanhoudende latencyproblemen, vooral bij single-threaded cron jobs in PHP, wezen uiteindelijk richting een structureel probleem in de netwerkarchitectuur van Zone 3, of ergens in de deployment.\u003c/p\u003e\n\u003ch2 id=\"collaboration-and-learning\"\u003eSamenwerking en leren\u003c/h2\u003e\n\u003cp\u003eOns traject, gedragen door de samenwerking met \u003ca href=\"https://www.pionative.com/\" data-external\u003ePionative\u003c/a\u003e, onderstreept hoe waardevol het is om samen te werken als je door de complexiteit van de cloud moet navigeren. Hoe dieper we in deze latency-anomalie doken, hoe duidelijker werd dat we niet alleen onze eigen deployment wilden optimaliseren, maar onze bevindingen ook wilden teruggeven aan de bredere Azure-community.\u003c/p\u003e\n\u003cblockquote class=\"pull-quote\"\u003e\u003cp\u003eDeze latency-impact is te groot om te negeren in onze productieomgeving.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"implications-and-next-steps\"\u003eImplicaties en vervolgstappen\u003c/h2\u003e\n\u003cp\u003eDeze anomalie in Zone 3 roept discussies op over de optimale zonekeuze en deploymentstrategieën binnen de regio West-Europa van Azure. Is dit de reden dat Zone 3 de dunstbevolkte zone is? Online is er hier zo te zien weinig over te vinden. Daarom heb ik deze post geschreven: het kostte ons letterlijk dagen om dit prestatieverschil op te sporen.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eDit gaat invloed hebben op onze productieomgeving. We draaien onze DB in Zone 1 of 2, en de API moet in een van deze zones staan. Zone 3 kan gebruikt worden voor pods die met de API praten.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eDeze latency-anomalie in Zone 3 was voor ons meer dan een uitdaging. We zagen het als een kans om te leren, aan te passen en onze kennis te verbreden. Terwijl we blijven samenwerken, testen en onze aanpak verfijnen, groeit ons Azure-traject uit tot een zeer interessante technische verkenning die onze toolkit voor de toekomst verruimt.\u003c/p\u003e\n","date_modified":"2024-03-05T17:28:41+01:00","date_published":"2024-03-05T12:16:52+01:00","id":"https://www.allsubjectsmatter.nl/nl/azure-western-europe-zone-3-latency-issue-exploration/","language":"nl","summary":"Een groot Azure-performanceprobleem: de onverwachte latency-anomalie in Zone 3 van de regio West-Europa zet clouddeploymentstrategieën onder druk","tags":["Tech"],"title":"Latency-anomalie: een poging om Azure-performanceproblemen in West-Europa te ontrafelen","url":"https://www.allsubjectsmatter.nl/nl/azure-western-europe-zone-3-latency-issue-exploration/"},{"authors":[{"name":"Remco Loup","url":"https://www.allsubjectsmatter.nl/author/remco/"}],"content_html":"\u003cp\u003eDeze post is een vervolg op GIT deployment-strategieën binnen Kubernetes-omgevingen. Lees eerst deze post:\u003c/p\u003e\n\u003cfigure class=\"bookmark\"\u003e\u003ca class=\"bookmark__link\" href=\"https://www.allsubjectsmatter.nl/nl/ci-cd-pipeline-with-docker-and-git-branching-strategy/\"\u003e\u003cspan class=\"bookmark__body\"\u003e\u003cspan class=\"bookmark__title\"\u003eCI/CD pipeline with Docker and Git branching strategy\u003c/span\u003e\u003cspan class=\"bookmark__description\"\u003eExplore advanced CI/CD with Docker \u0026amp; Git. Master deploying Docker artifacts via Git branching. Dive into strategies for DEV, STAGING, LIVE environments. Perfect your pipeline now!\u003c/span\u003e\u003cspan class=\"bookmark__meta\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"25px\"\u003e\u003csource type=\"image/webp\" sizes=\"25px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/hotfixing-with-docker-artifacts-in-a-single-branch-strategy/bookmarks/01-icon_hu_9b466dd874965fcb.webp\" alt=\"\" class=\"bookmark__icon\" width=\"50\" height=\"50\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003cspan\u003eAll Subjects Matter\u003c/span\u003e\u003cspan\u003eRemco Loup.\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"bookmark__thumb\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/webp\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/avif\" sizes=\"250px\"\u003e\u003csource type=\"image/webp\" sizes=\"250px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/hotfixing-with-docker-artifacts-in-a-single-branch-strategy/bookmarks/01-thumb_hu_4b0a9042ec0e16fc.webp\" alt=\"\" width=\"500\" height=\"435\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003c/span\u003e\u003c/a\u003e\u003c/figure\u003e\n\n\u003cp\u003eIn een CI/CD-pipeline die draait om Docker artifacts, met de focus op één enkele branch, vraagt een efficiënte hotfix-strategie om een kleine afwijking van de standaardaanpak. Zo zorgen we dat kritieke fixes snel gedeployd worden. Laten we onze aanpak voor hotfixes in deze Docker-gerichte omgeving uitwerken, op basis van de branch-architectuur uit de post hierboven.\u003c/p\u003e\n\u003cfigure class=\"figure\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003csource type=\"image/webp\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/hotfixing-with-docker-artifacts-in-a-single-branch-strategy/images/Git-branches-and-docker-environments-ci_cd-1_hu_17e1895a357c0fa7.webp\" alt=\"\" width=\"560\" height=\"378\" data-zoom-src=\"https://www.allsubjectsmatter.nl/hotfixing-with-docker-artifacts-in-a-single-branch-strategy/images/Git-branches-and-docker-environments-ci_cd-1_hu_7bd9f2a51f818afa.webp\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003cfigcaption\u003eGrafische weergave van onze branching- en deploymentstrategie\u003c/figcaption\u003e\u003c/figure\u003e\n\n\u003ch2 id=\"identifying-the-need-for-a-hotfix\"\u003eWanneer is een hotfix nodig?\u003c/h2\u003e\n\u003cp\u003eHotfixes zijn cruciaal voor de stabiliteit en betrouwbaarheid van elke software in productie. Ze zijn meestal nodig in situaties die directe actie vragen. Weten wanneer je een hotfix inzet, is essentieel om je CI/CD-pipeline goed te beheren, zeker als je werkt met Docker artifacts en een gestroomlijnde branch-strategie met één hoofdbranch. Dit zijn onze scenario\u0026rsquo;s waarin een hotfix nodig kan zijn:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003eBeveiligingslekken:\u003c/strong\u003e Een van de meest urgente redenen voor een hotfix is het ontdekken van een beveiligingslek in je applicatie. Of het nu gaat om blootgestelde data, risico op ongeautoriseerde toegang of een andere kwetsbaarheid die kwaadwillenden kunnen misbruiken: je moet snel handelen om het lek te dichten en je gebruikers en data te beschermen.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eKritieke bugs die de gebruikerservaring raken:\u003c/strong\u003e Als een serieuze bug door je testproces heen glipt en in productie belandt, kan dat de gebruikerservaring flink raken. Dat kan van alles zijn: een kapotte kernfunctionaliteit waardoor gebruikers de applicatie niet meer kunnen gebruiken, of ernstige performanceproblemen die leiden tot timeouts of crashes. Raakt het probleem een groot deel van je gebruikers? Dan is een hotfix op zijn plaats.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eCompliance-problemen:\u003c/strong\u003e Sommige applicaties hebben te maken met wettelijke of regelgevende eisen. Als er een compliance-probleem wordt geconstateerd dat je juridische positie of de privacy van gebruikers kan raken, moet dat snel met een hotfix worden opgelost om boetes of verlies van vertrouwen te voorkomen.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eRisico op dataverlies:\u003c/strong\u003e Situaties waarin gebruikersdata verloren kan raken of corrupt kan worden, vragen om directe actie. Of het nu een fout is in hoe data wordt opgeslagen, bijgewerkt of opgehaald: deploy dan een hotfix om verlies of corruptie van gebruikersdata te beperken.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eFalende betalingen of transacties:\u003c/strong\u003e Voor applicaties die betalingen of andere transacties verwerken, zijn problemen die transacties blokkeren of tot financiële discrepanties leiden kritiek. Zulke problemen schaden niet alleen het vertrouwen van gebruikers, maar kunnen ook financiële gevolgen hebben voor zowel gebruikers als de dienstverlener. Een hotfix is dan noodzakelijk.\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"deploying-a-hotfix\"\u003eEen hotfix deployen\u003c/h2\u003e\n\u003cp\u003eAls er een kritiek probleem in de productieomgeving wordt geconstateerd, is het doel om zo snel mogelijk een hotfix te ontwikkelen, testen en deployen. Omdat MAIN onze hoofdbranch voor development is en we Docker artifacts genereren voor deployment, hebben we een proces nodig dat snelle interventie mogelijk maakt zonder het lopende werk te verstoren.\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003eEen hotfix-branch aanmaken:\u003c/strong\u003e In plaats van te branchen vanaf je Production branch (LIVE bestaat in deze strategie immers niet meer in de Git-repository), maak je de hotfix-branch vanaf de commit die op dat moment in productie draait. Zo is de hotfix gebaseerd op exact de versie die in de productieomgeving draait. De specifieke commit vind je via tags of Docker image-labels die gekoppeld zijn aan de productie-deployment.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eDe hotfix ontwikkelen en testen:\u003c/strong\u003e De kritieke fix wordt ontwikkeld binnen deze hotfix-branch. De urgentie vraagt om een gerichte aanpak: pak het probleem aan met minimale wijzigingen om testen en deployen te versnellen. Ook onder tijdsdruk is het naleven van de Definition of Done (DoD) cruciaal voor de stabiliteit en betrouwbaarheid van de hotfix. Dat kan een compactere versie van de DoD zijn, met prioriteit voor de kritieke tests en checks die garanderen dat de fix geen nieuwe problemen introduceert.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eEen Docker artifact voor de hotfix maken:\u003c/strong\u003e Zodra de hotfix aan de afgesproken DoD-criteria voldoet, bouwt een geautomatiseerd proces een Docker image van de hotfix-branch. Dit Docker artifact krijgt een passende tag, zodat duidelijk is waar het voor dient en hoe urgent het is.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eDe hotfix deployen:\u003c/strong\u003e Het nieuwe Docker artifact wordt direct in de productieomgeving gedeployd, waarbij de gebruikelijke route via DEV en STAGING naar PRODUCTION wordt overgeslagen. Deze deployment is alleen te rechtvaardigen door de kritieke aard van de fix en de tests die erop zijn uitgevoerd.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eTerugmergen en consistentie bewaken:\u003c/strong\u003e Als de hotfix succesvol in productie draait, is het essentieel om de wijzigingen terug te mergen naar de MAIN branch en eventuele andere relevante branches of tags. Pas deze stap zorgt ervoor dat de fix wordt opgenomen in het lopende developmentwerk en toekomstige releases, en dat je geen regressie krijgt!!\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"ensuring-rapid-deployment-and-minimizing-disruption\"\u003eSnel deployen en zo min mogelijk storing\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eGeautomatiseerde processen:\u003c/strong\u003e Automatisering speelt een sleutelrol in deze strategie, van het maken van het Docker artifact tot de deployment. Geautomatiseerde tests, builds en deployments zorgen dat het hotfix-proces zo snel en efficiënt mogelijk verloopt.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eCommunicatie en monitoring:\u003c/strong\u003e Snelle deployment van hotfixes vraagt om heldere communicatie binnen het team en nauwe monitoring van de productieomgeving na de deployment, om te verifiëren dat de fix werkt en geen nieuwe problemen introduceert.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eDocumentatie en review:\u003c/strong\u003e Het documenteren van het hotfix-proces en het doen van post-mortem-reviews helpt om de hotfix-strategie te verfijnen, zodat het team bij toekomstige problemen nog effectiever kan reageren.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"conclusion\"\u003eConclusie\u003c/h2\u003e\n\u003cp\u003eIn een CI/CD-pipeline met Docker en Git Flow als hoofdstrategie vraagt een hotfix om een strakke aanpak: gebruik de automatisering die er al is en focus op snelle, gerichte fixes. Door hotfix-branches te baseren op de commit die in productie staat, fixes te bouwen en testen volgens een goed gedefinieerde Definition of Done, en de Docker-artifacts direct naar productie te deployen, lost een team kritische problemen snel op zonder de stabiliteit en integriteit van de applicatie in gevaar te brengen.\u003c/p\u003e\n","date_modified":"2024-02-20T12:32:34+01:00","date_published":"2024-02-20T09:53:55+01:00","id":"https://www.allsubjectsmatter.nl/nl/hotfixing-with-docker-artifacts-in-a-single-branch-strategy/","language":"nl","summary":"Leer hotfixes in Kubernetes beheersen met Docker. Ontdek hoe je kritieke fixes snel deployt in een CI/CD-pipeline met een strakke branching-strategie. Ideaal voor het oplossen van urgente problemen zonder de flow te verstoren. Duik erin.","tags":["Agile","Tech"],"title":"Hotfixes met Docker-artifacts in een Git Flow-strategie","url":"https://www.allsubjectsmatter.nl/nl/hotfixing-with-docker-artifacts-in-a-single-branch-strategy/"},{"authors":[{"name":"Remco Loup","url":"https://www.allsubjectsmatter.nl/author/remco/"}],"content_html":"\u003cp\u003eIn deze post ga ik in op het beheren van een CI/CD-pipeline met Docker, met de nadruk op een Git branching-strategie met een development- (DEV), staging- (STAGING) en production-branch (LIVE). Het doel is duidelijk maken welke rol elke branch speelt in het proces, en vooral hoe Docker-artifacts binnen dat stramien worden aangemaakt en beheerd.\u003c/p\u003e\n\u003ch2 id=\"docker-deployment-using-git-flow-with-multiple-branches\"\u003eDocker-deployments met Git Flow en meerdere branches\u003c/h2\u003e\n\u003cp\u003eDe kern van onze branching-strategie bestond uit drie branches:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003eDEV\u003c/strong\u003e: Hier gebeurt alle ontwikkeling. Elke nieuwe feature, bugfix of verbetering start in een feature branch, afgesplitst van DEV. Is het werk af, gereviewd en voldoet het aan de Definition of Done (DoD), dan wordt het gemerged naar DEV.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eSTAGING\u003c/strong\u003e: Deze branch spiegelt een pre-productieomgeving. Hier worden geïntegreerde features uit DEV als geheel getest en door stakeholders geaccepteerd, zodat we zeker weten dat alles samenwerkt.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eMASTER\u003c/strong\u003e: De branch die de huidige staat van onze live-omgeving weerspiegelt. Alleen grondig geteste en door stakeholders goedgekeurde wijzigingen uit STAGING halen het tot Productie.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cfigure class=\"bookmark\"\u003e\u003ca class=\"bookmark__link\" href=\"https://dev.to/preethamsathyamurthy/git-branching-and-branching-strategy-4mci\" rel=\"noopener\"\u003e\u003cspan class=\"bookmark__body\"\u003e\u003cspan class=\"bookmark__title\"\u003eGit Branching and Branching Strategy\u003c/span\u003e\u003cspan class=\"bookmark__description\"\u003eIn this post, we will go over why branching is required, the difference between development, staging…\u003c/span\u003e\u003cspan class=\"bookmark__meta\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"25px\"\u003e\u003csource type=\"image/webp\" sizes=\"25px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/ci-cd-pipeline-with-docker-and-git-branching-strategy/bookmarks/01-icon_hu_4e78872e34382e92.webp\" alt=\"\" class=\"bookmark__icon\" width=\"50\" height=\"50\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003cspan\u003eDEV Community\u003c/span\u003e\u003cspan\u003ePreetham\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/a\u003e\u003c/figure\u003e\n\n\u003cp\u003eDit was onze variant op een Git Flow-strategie, die we tot nu toe gebruikten. Zodra je met artifacts gaat werken, verandert dat allemaal terug naar een normale Git Flow-structuur. We hebben nog steeds omgevingen die draaien (Dev, Staging en Prod). Het echte verschil is dat code nu één keer gecommit wordt, in plaats van gemerged te worden naar een branch die op Staging of in Productie draait.\u003c/p\u003e\n\u003cp\u003eIn plaats daarvan hebben we nu één branch, die we Main noemen. Daar wordt na een geslaagde code merge met Docker images een runnable artifact aangemaakt. Alles wat we op Staging of in Productie deployen is een specifieke versie van een eerder aangemaakt artifact.\u003c/p\u003e\n\u003cp\u003eHet grootste verschil: STAGING en PROD zijn geen branches meer waar je code in merged. Het zijn omgevingen geworden die een specifieke versie van een Docker image draaien. Dat brengt eigen uitdagingen met zich mee, zeker bij een hotfix. Hotfixen krijgt een eigen post, die je na deze kunt lezen. Ik loop het nu eerst stap voor stap door, zonder de hotfix erbij. Het is al omvangrijk genoeg zo.\u003c/p\u003e\n\u003ch2 id=\"what-is-an-artifact\"\u003eWat is een artifact?\u003c/h2\u003e\n\u003cp\u003eEen Docker-artifact, vaak door elkaar gehaald met een Docker image, is een lichtgewicht, zelfstandig uitvoerbaar package dat alles bevat wat nodig is om een stuk software te draaien: de code, een runtime, libraries, omgevingsvariabelen en configuratiebestanden. Simpel gezegd: een snapshot van je applicatie én de omgeving eromheen, samen verpakt, klaar om overal gedeployed en gedraaid te worden waar Docker is geïnstalleerd. Zo weet je dat alles in verschillende omgevingen consistent werkt.\u003c/p\u003e\n\u003ch2 id=\"the-importance-of-dod-when-merging-code\"\u003eHet belang van de DoD bij het mergen van code\u003c/h2\u003e\n\u003cp\u003eEen volledige en heldere Definition of Done (DoD) speelt in dit nieuwe proces een sleutelrol, zeker nu alle wijzigingen in één enkele branch gemerged worden die een Docker image moet opleveren die runnable en deployable is:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eKwaliteitsborging\u003c/strong\u003e: De DoD zorgt dat alle wijzigingen — nieuwe features, verbeteringen en hotfixes — die terug in MAIN gemerged worden aan een consistent kwaliteitsniveau voldoen. Denk aan code quality checks, succesvol doorlopen geautomatiseerde tests en het volgen van security best practices.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eRegressie voorkomen\u003c/strong\u003e: Door de DoD toe te passen op alle merges naar MAIN voorkomt het team regressies en zorgt het dat nieuwe wijzigingen bestaande functionaliteit niet breken. Cruciaal voor de integriteit en stabiliteit van de codebase.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eDeployments soepeler laten verlopen\u003c/strong\u003e: Een duidelijke en volledige DoD maakt deployments makkelijker, omdat alleen volledig gecontroleerde, deploy-klare wijzigingen in MAIN gemerged worden. Zo verspil je geen resources aan het bouwen van Docker images met bugs of die helemaal niet werken, en dat levert efficiëntere en betrouwbaardere artifacts op.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cfigure class=\"figure\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003csource type=\"image/webp\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/ci-cd-pipeline-with-docker-and-git-branching-strategy/images/Git-branches-and-docker-environments-ci_cd_hu_17e1895a357c0fa7.webp\" alt=\"\" width=\"560\" height=\"378\" data-zoom-src=\"https://www.allsubjectsmatter.nl/ci-cd-pipeline-with-docker-and-git-branching-strategy/images/Git-branches-and-docker-environments-ci_cd_hu_7bd9f2a51f818afa.webp\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003cfigcaption\u003eHier een grafische weergave van onze Git Flow-strategie en hoe die door de verschillende omgevingen loopt.\u003c/figcaption\u003e\u003c/figure\u003e\n\n\u003ch2 id=\"creating-and-managing-artifacts\"\u003eArtifacts aanmaken en beheren\u003c/h2\u003e\n\u003cp\u003eDe eerste voorwaarde om dit proces te laten werken: zodra een afgesplitste branch terug in MAIN gemerged wordt, start dat een geautomatiseerd proces in onze CI/CD-pipeline. Dit gebeurt er:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003eGeautomatiseerde tests\u003c/strong\u003e: Eerst draaien de geautomatiseerde tests om te checken of de nieuwe wijzigingen aan onze kwaliteitsnormen voldoen. Cruciaal voor een stabiele branch.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eDocker-artifact aanmaken\u003c/strong\u003e: Zijn alle tests geslaagd, dan wordt er een Docker image (ons artifact) van de MAIN-branch gebouwd. MAIN is immers de enige branch die de laatste staat van alle afgeronde ontwikkeling vertegenwoordigt. Van daaruit genereren we artifacts die \u0026ldquo;shippable\u0026rdquo; producten kunnen worden.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eWaarom MAIN voor Docker-artifacts?\u003c/strong\u003e: Door artifacts op dit moment aan te maken vangen we integratieproblemen vroeg op. Omdat elke feature die in MAIN gemerged wordt als release-klaar wordt beschouwd, heb je door hier een artifact te genereren altijd een geteste en potentieel deploybare versie van je product bij de hand.\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"transferring-from-branches-to-environments\"\u003eVan branches naar omgevingen\u003c/h2\u003e\n\u003cp\u003eIn traditionele Git-workflows staan branches als STAGING en PROD voor verschillende fasen in het ontwikkelproces, van testen tot productie. In een Docker-gecentreerde CI/CD-pipeline verschuift de focus: deze branches dienen niet langer voor code-integratie, maar als referentie voor deployment-omgevingen.\u003c/p\u003e\n\u003ch2 id=\"deploying-to-staging\"\u003eDeployen naar STAGING\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eDocker-artifacts aanmaken\u003c/strong\u003e: zodra de ontwikkeling klaar is en gemerged is naar de MAIN-branch, wordt er automatisch een Docker-artifact aangemaakt. Dat artifact is een snapshot van je applicatie op dat moment, met alle wijzigingen die gemerged zijn.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eDeployen in plaats van mergen\u003c/strong\u003e: de rol van de STAGING-omgeving is om dit Docker-artifact te testen onder omstandigheden die sterk lijken op productie. In plaats van code van MAIN naar een STAGING-branch te mergen, deploy je het Docker-artifact naar een STAGING-omgeving. De code zelf beweegt dus niet tussen branches; het is de gecompileerde en gecontaineriseerde versie van je applicatie die getest wordt.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eHet doel van STAGING\u003c/strong\u003e: de STAGING-omgeving is essentieel voor uitgebreide tests, waaronder performance- en security-assessments. Het is een laatste check om te verifiëren dat alles werkt zoals verwacht in een productie-achtige omgeving, zonder je echte gebruikers direct te raken.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"going-to-production\"\u003eNaar productie\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eGoedkeuring van stakeholders\u003c/strong\u003e: zodra je Docker-artifact grondig getest is in STAGING en goedgekeurd is door de stakeholders, is het klaar voor productie. Deze stap is cruciaal om te garanderen dat alle features, fixes en updates aan de nodige standaarden en verwachtingen voldoen.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eDeployen naar productie\u003c/strong\u003e: de overgang van STAGING naar PROD betekent dat je het geteste Docker-artifact deployt naar je productieomgeving. Dit deploymentproces garandeert consistentie: het exacte artifact dat in STAGING getest is, wordt naar PROD gedeployd. Zo zijn er geen verschillen tussen geteste en gedeployde code.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"key-takeaways\"\u003eBelangrijkste punten\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eGeen branch-merges meer voor deployment\u003c/strong\u003e: in deze gestroomlijnde workflow worden STAGING- en PROD-branches niet meer gebruikt om code te mergen. In plaats daarvan verwijzen ze naar specifieke deployment-omgevingen waar Docker-artifacts voor tests en productie gedeployd worden.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eFocus op artifacts\u003c/strong\u003e: de nadruk ligt op het aanmaken, testen en deployen van Docker-artifacts. Deze artifacts vertegenwoordigen stabiele versies van je applicatie, klaar voor STAGING en uiteindelijk voor PROD.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eConsistentie en betrouwbaarheid\u003c/strong\u003e: door hetzelfde Docker-artifact van STAGING naar PROD te deployen, weet je zeker dat wat getest is exact is wat er uitgebracht wordt. Dat vergroot de betrouwbaarheid en voorspelbaarheid van je deployments.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eDeze aanpak vereenvoudigt het deploymentproces, verkleint het risico op fouten en zorgt ervoor dat elke release grondig getest wordt in een omgeving die productie zo nauw mogelijk benadert.\u003c/p\u003e\n\u003ch2 id=\"are-multiple-branches-still-needed\"\u003eZijn meerdere branches nog nodig?\u003c/h2\u003e\n\u003cp\u003eIn deze nieuwe workflow hebben de STAGING- en PROD-branches in de Git branching strategy geen actieve functie meer en zijn ze dus overbodig geworden. Ze hielpen om bij te houden welke versies van de code naar welke omgeving gedeployd waren. Dat probleem hebben we nu opgelost door een specifieke versie van een artifact te draaien.\u003c/p\u003e\n\u003ch2 id=\"conclusion\"\u003eConclusie\u003c/h2\u003e\n\u003cp\u003eDoor Docker op te nemen in onze Git branching strategy stroomlijnen we het hele traject van development tot deployment: elke merge naar MAIN triggert een Docker-image met de potentie om naar productie te gaan. De sleutel van deze strategie is het vroeg in de developmentcyclus aanmaken van Docker-artifacts, zodat continuous integration en delivery mogelijk blijven zonder in te leveren op kwaliteit en stabiliteit.\u003c/p\u003e\n\u003cp\u003eDoor de veranderingen aan de Git-branches te begrijpen en het proces van het aanmaken en deployen van Docker-artifacts, kunnen we met vertrouwen omgaan met de complexiteit van CI/CD. Zo leveren we alle nieuwe features en fixes zo efficiënt en betrouwbaar mogelijk af bij de gebruikers.\u003c/p\u003e\n\u003ch2 id=\"hotfixing\"\u003eHotfixes\u003c/h2\u003e\n\u003cp\u003eWe hebben het nog niet gehad over hotfixes. Zoals ik al zei, verdient dit een eigen post. Je leest hier alles over hotfixes en de valkuilen die daarbij komen kijken.\u003c/p\u003e\n\u003cfigure class=\"bookmark\"\u003e\u003ca class=\"bookmark__link\" href=\"https://www.allsubjectsmatter.nl/nl/hotfixing-with-docker-artifacts-in-a-single-branch-strategy/\"\u003e\u003cspan class=\"bookmark__body\"\u003e\u003cspan class=\"bookmark__title\"\u003eHotfixing with Docker Artifacts in a Single Branch Strategy\u003c/span\u003e\u003cspan class=\"bookmark__description\"\u003eMaster hotfixes in Kubernetes with Docker. Learn to rapidly deploy critical fixes in a CI/CD pipeline focusing on streamlined branching. Perfect for tackling urgent issues without disrupting flow. Dive in now.\u003c/span\u003e\u003cspan class=\"bookmark__meta\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"25px\"\u003e\u003csource type=\"image/webp\" sizes=\"25px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/ci-cd-pipeline-with-docker-and-git-branching-strategy/bookmarks/02-icon_hu_9b466dd874965fcb.webp\" alt=\"\" class=\"bookmark__icon\" width=\"50\" height=\"50\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003cspan\u003eAll Subjects Matter\u003c/span\u003e\u003cspan\u003eRemco Loup.\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"bookmark__thumb\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/webp\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/avif\" sizes=\"250px\"\u003e\u003csource type=\"image/webp\" sizes=\"250px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/ci-cd-pipeline-with-docker-and-git-branching-strategy/bookmarks/02-thumb_hu_31f1067c4e9c7ce6.webp\" alt=\"\" width=\"500\" height=\"435\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003c/span\u003e\u003c/a\u003e\u003c/figure\u003e\n\n","date_modified":"2024-02-20T12:30:01+01:00","date_published":"2024-02-20T09:50:55+01:00","id":"https://www.allsubjectsmatter.nl/nl/ci-cd-pipeline-with-docker-and-git-branching-strategy/","language":"nl","summary":"Een kijkje in CI/CD met Docker \u0026 Git. Docker-artifacts deployen via Git branching onder de knie krijgen. Strategieën voor DEV-, STAGING- en PROD-omgevingen. Maak je pipeline beter!","tags":["Agile","Tech"],"title":"CI/CD-pipeline met Docker en een Git branching-strategie","url":"https://www.allsubjectsmatter.nl/nl/ci-cd-pipeline-with-docker-and-git-branching-strategy/"},{"authors":[{"name":"Remco Loup","url":"https://www.allsubjectsmatter.nl/author/remco/"}],"content_html":"\u003cp\u003eAls techliefhebber heb ik veel ontwikkelingen in AI zien langskomen, vooral op het gebied van taalverwerking en interactie met gebruikers. Als ik daarop terugkijk, wil ik al een tijdje delen waarom de recente sprongen in AI volgens mij minder verrassend zijn dan je misschien denkt.\u003c/p\u003e\n\u003cp\u003eIk gebruik door elkaar heen verschillende termen voor hetzelfde. Ik spreek over AI en over LLM (Large Language Model) voor de huidige generatie systemen zoals ChatGPT.\u003c/p\u003e\n\u003ch2 id=\"my-first-encounters-with-ai\"\u003eMijn eerste kennismaking met \u0026ldquo;AI\u0026rdquo;\u003c/h2\u003e\n\u003cp\u003eMijn reis met LLM\u0026rsquo;s begon eerder dan veel mensen beseffen. Mijn eerste ervaringen met Flow.ai had ik rond 2018/2019. Toen was mij al duidelijk dat er iets stond te gebeuren. Het grote publiek had echter nauwelijks door wat er in de AI-ontwikkeling broeide. Feit is dat bedrijven als Flow.ai (inmiddels onderdeel van Khoros) de fundering legden voor de AI-systemen van nu, zoals ChatGPT.\u003c/p\u003e\n\u003cp\u003eLees anders het stuk dat ik in 2019 schreef over onze ervaringen met Flow.AI, samen met Stefan Samba.\u003c/p\u003e\n\u003cfigure class=\"bookmark\"\u003e\u003ca class=\"bookmark__link\" href=\"https://www.allsubjectsmatter.nl/nl/how-333travel-is-revolutionizing-the-travel-industry/\"\u003e\u003cspan class=\"bookmark__body\"\u003e\u003cspan class=\"bookmark__title\"\u003e2019 - Blast from the past: How 333travel is revolutionizing the Travel industry\u003c/span\u003e\u003cspan class=\"bookmark__description\"\u003eDiscover how 333travel’s AI ‘Joy’ redefines customer interaction, blending AI’s intent understanding with personal touch. A game-changing journey in AI-led travel solutions!\u003c/span\u003e\u003cspan class=\"bookmark__meta\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"25px\"\u003e\u003csource type=\"image/webp\" sizes=\"25px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/why-ai-caught-you-by-surprise/bookmarks/01-icon_hu_9b466dd874965fcb.webp\" alt=\"\" class=\"bookmark__icon\" width=\"50\" height=\"50\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003cspan\u003eAll Subjects Matter\u003c/span\u003e\u003cspan\u003eRemco Loup.\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"bookmark__thumb\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/webp\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/avif\" sizes=\"250px\"\u003e\u003csource type=\"image/webp\" sizes=\"250px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/why-ai-caught-you-by-surprise/bookmarks/01-thumb_hu_e0b56fb8f4d4ac9.webp\" alt=\"\" width=\"500\" height=\"435\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003c/span\u003e\u003c/a\u003e\u003c/figure\u003e\n\n\u003ch2 id=\"flowai-a-missed-opportunity-for-333travel\"\u003eFlow.ai: een gemiste kans voor 333travel\u003c/h2\u003e\n\u003cp\u003eFlow.ai was voor mij toen een eyeopener. Het liet me zien wat neural network-gebaseerde conversationele AI in huis had. Als ik er nu op terugkijk, waren we met de integratie van deze technologie bij 333travel in 2018 misschien onze tijd te ver vooruit. Onze natuurlijke manier van mensen helpen met het aanvragen van een voorstel kreeg met een LLM niet de grip die we hoopten. Onze klanten (gelukkig?) wilden liever met menselijke specialisten praten dan met bots. Dat maakte duidelijk dat de technologie veelbelovend was, maar dat de markt er nog niet klaar voor was.\u003c/p\u003e\n\u003cblockquote class=\"pull-quote\"\u003e\u003cp\u003eMensen vonden het vreselijk om met \u0026ldquo;ouderwetse\u0026rdquo; chatbots te praten die je niet begrepen, door het gebrek aan intent-understanding.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"the-siri-example\"\u003eHet voorbeeld van Siri\u003c/h2\u003e\n\u003cp\u003eLaten we het even over Siri hebben, de virtuele assistent van Apple. Sinds de lancering in 2011 heeft Siri wel wat stappen gezet. Maar naar mijn mening zijn die verbeteringen op het gebied van intent learning op z\u0026rsquo;n best bescheiden. Siri, net als tools als Cortana en Google Assistant, had nog steeds hele specifieke commando\u0026rsquo;s nodig en begreep intenties vaak niet. Er is wel wat vooruitgang geboekt, maar de leercurve in het begrijpen van menselijke taal — en vooral intentie — was veel te traag.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e2010-2011\u003c/strong\u003e: In de eerste jaren werd Siri geïntegreerd in de iPhone 4S, met basale spraakherkenning en commando-uitvoering.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e2012-2013\u003c/strong\u003e: Uitbreiding naar nieuwe talen en dialecten, waardoor de gebruikersbasis groeide.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e2014-2015\u003c/strong\u003e: Toetreding tot nieuwe markten en de lancering van SiriKit, waarmee apps van derden konden integreren.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e2016-2018\u003c/strong\u003e: Introductie op macOS en de release van Shortcuts voor gepersonaliseerde spraakcommando\u0026rsquo;s.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e2020-2021\u003c/strong\u003e: matige verbeteringen in taalvertaling, integratie met het ecosysteem van Apple en betere spraakherkenning.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"ai-and-customer-interaction\"\u003eAI en klantinteractie\u003c/h2\u003e\n\u003cp\u003eAls techliefhebber vind ik ChatGPT op veel vlakken indrukwekkend, maar het is geen vervanging voor de gedetailleerde kennis die menselijke specialisten bieden. Neem nou een touroperator. Een goed reisplan opstellen is een heel ander verhaal. Het laatste wat je wilt, is dat daar fouten in sluipen.\u003c/p\u003e\n\u003cp\u003eWaarom zou je een AI met de klant laten praten en die daarna vervangen door een mens? Is het omgekeerde niet veel logischer? Laat onze specialisten een AI gebruiken om een persoonlijk reisplan te maken en dat sneller versturen met AI. Je klant bind je in de verkoop nog altijd het beste met echte menselijke interactie.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eAls je niet weet waar je het over hebt, kun je fouten in de antwoorden van een AI ook niet herkennen. Dat blijft altijd een grote zorg. Kennis uit de eerste hand blijft verplicht. Daarom is iets als ChatGPT een tool die gebruikt moet worden door mensen die het onderwerp beheersen, om hun werk te versnellen.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eIk geloof niet in losse integraties op websites die ruimte innemen en doen alsof ze een slimme AI zijn. De zoekfunctie die al op websites aanwezig is, lost dit probleem op. En mensen die een AI (LLM) gaan gebruiken, willen niet elke keer met een andere LLM praten. Ze zetten hun eigen PA in om die vragen te beantwoorden, als dat nodig is.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eDe eerste stap die bedrijven moeten zetten, is een zoekmachine op hun website implementeren die intentie begrijpt. Door intentie te begrijpen kun je mensen een echt nuttig antwoord geven, gebaseerd op de content op je website.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eJe hebt een diepgaande integratie nodig tussen je LLM en je rijke content. Zeker in de reisbranche. Je website slimmer maken met een LLM is volgens mij veel logischer dan een popup-\u0026ldquo;persoon\u0026rdquo; waarmee je kunt chatten.\u003c/p\u003e\n\u003cblockquote class=\"pull-quote\"\u003e\u003cp\u003eDe toekomst van AI is ongetwijfeld spannend, maar het is een pad dat we met zowel optimisme als voorzichtigheid moeten bewandelen.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"my-take-on-ais-future\"\u003eMijn blik op de toekomst van AI\u003c/h2\u003e\n\u003cp\u003eVooruitkijkend zie ik AI als een vakgebied vol mogelijkheden, maar ook vol uitdagingen. De reis van de vroege systemen met vaste commando\u0026rsquo;s naar de intuitieve, contextbewuste modellen van vandaag is fascinerend geweest. Toch is er nog een lange weg te gaan voordat AI mensen echt kan begrijpen en op een dieper niveau met mensen kan omgaan.\u003c/p\u003e\n\u003ch2 id=\"conclusion\"\u003eConclusie\u003c/h2\u003e\n\u003cp\u003eDe recente ontwikkelingen van AI lijken voor veel mensen plotseling en verrassend, maar ze zijn onderdeel van een lang, doorlopend proces van technologische evolutie. Mijn ervaringen bij 333travel, vooral met innovatieve platforms als Flow.ai, hebben me de potentie én de beperkingen van AI laten zien. Voor de toekomst vind ik het cruciaal om ons enthousiasme voor AI af te wegen tegen een realistisch beeld van wat het nu wel en niet kan.\u003c/p\u003e\n\u003cblockquote class=\"pull-quote\"\u003e\u003cp\u003eJe bent uniek door dingen dicht bij jezelf te houden, niet door technologie je kernactiviteiten te laten overnemen. Houd altijd een persoonlijke touch!\u003c/p\u003e\n\u003c/blockquote\u003e\n","date_modified":"2024-02-16T08:44:46+01:00","date_published":"2024-01-23T22:58:00+01:00","id":"https://www.allsubjectsmatter.nl/nl/why-ai-caught-you-by-surprise/","language":"nl","summary":"Een kijkje in de AI-(r)evolutie. Hoe intentie-begrip AI veranderde, van Flow.ai tot ChatGPT, en de digitale wereld stap voor stap transformeerde. Lees verder voor mijn eigen, unieke inzichten!","tags":["Tech","AI and LM"],"title":"Waarom \"AI\" je verraste","url":"https://www.allsubjectsmatter.nl/nl/why-ai-caught-you-by-surprise/"},{"authors":[{"name":"Remco Loup","url":"https://www.allsubjectsmatter.nl/author/remco/"}],"content_html":"\u003ch2 id=\"introduction\"\u003eIntroductie\u003c/h2\u003e\n\u003cp\u003eVan jongs af aan had ik al last van brede voeten, en dat probleem werd alleen maar erger. Het hoogtepunt was 2023, toen ik een voetoperatie onderging om een bunion aan mijn kleine teen te corrigeren. Voor de operatie betekende schoenen dragen vaak een pijnlijke drukplek door die stand van de teen, een ongemak dat steeds meer invloed kreeg op mijn schoenenkeuze. De grens was bereikt toen ik niet langer dan 45 minuten kon inline skaten zonder flinke pijn.\u003c/p\u003e\n\u003cp\u003eNa de operatie werd duidelijk dat mijn brede voeten nog steeds een probleem vormden, vooral bij het vinden van geschikte inline skates. Hoewel ik vanwege de extra steun een voorkeur heb voor hard-shell skates, merk ik dat die brede voeten zelden comfortabel passen. Ik snap dan ook weinig van claims dat bepaalde merken en modellen geschikt zouden zijn voor brede voeten: geen enkele hard-shell skate die ik heb geprobeerd liet me pijnvrij skaten. Even onbegrijpelijk is dat de industrie de breedtematen van skate shells nergens vermeldt, terwijl de lengte alleen geen goede pasvorm garandeert voor mensen met bredere voeten.\u003c/p\u003e\n\u003cp\u003eAls fabrikanten breedte-informatie zouden toevoegen, zou het vinden van goed passende skates een stuk makkelijker worden. Dat vermindert het bekende probleem van verkeerd passende skates kopen. Vaak leidt dat er zelfs toe dat mensen de sport helemaal opgeven. Boots aanbieden voor mensen met iets bredere voeten zou een comfortabelere skatervaring mogelijk maken.\u003c/p\u003e\n\u003cp\u003eDuidelijkere maatvoering zou ook het aantal retouren bij webshops kunnen verminderen, wat zowel winkels als klanten ten goede komt. Winkels lijden minder verlies doordat geretourneerde skates niet tegen lagere prijzen hoeven te worden verkocht, en klanten hebben meteen skates die passen. Een win-winsituatie voor iedereen in de wereld van het inline skaten.\u003c/p\u003e\n\u003ch2 id=\"inline-skating-with-wide-feet\"\u003eInline skaten met brede voeten\u003c/h2\u003e\n\u003cp\u003eNa de operatie merkte ik dat skaten op mijn Roces Fifth Elements wat de bunion betreft een stuk minder pijnlijk was. Maar mijn voeten voelden in deze skates nog steeds te veel samengeknepen, met als gevolg algemene voetpijn na meer dan een uur skaten. Even de skates een paar minuten uittrekken bood tijdens langere sessies wat verlichting, maar uiteindelijk kon ik aan het eind van de sessie niet wachten om ze uit te doen.\u003c/p\u003e\n\u003cp\u003eDe Fifth Elements worden vaak aangeprezen als de breedste shells die er zijn. Veel reviews raden zelfs een kleinere maat aan voor een betere pasvorm, waarna ze voor velen nog te ruim zitten. De stelling dat ze uitzonderlijk breed zouden zijn, lijkt me dan ook misleidend. Ze zijn misschien breder dan de meeste, maar comfortabel zijn ze niet, zeker niet in de neus, waar mensen met brede voeten zoals ik de meeste last van hebben. Ondanks de extra ruimte bij de hiel is de neus van de Fifth Element nog steeds te smal voor comfort.\u003c/p\u003e\n\u003cblockquote class=\"pull-quote\"\u003e\u003cp\u003eOpinions are like assholes. Everybody has one.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eIk heb elke mogelijke aanpassing geprobeerd om mijn Roces Fifth Elements beter geschikt te maken voor mijn brede voeten. Van de shell heat molden om drukplekken te verminderen en hem te verbreden, tot het experimenteren met verschillende liners zoals de Roces RL2, en zelfs Superfeet Carbon inlegzolen voor extra ruimte. Ondanks al die moeite — en een aanzienlijk lichtere portemonnee — hield ik skates over die mijn voeten nog steeds te veel samendrukten en te veel pijn veroorzaakten.\u003c/p\u003e\n\u003ch2 id=\"technical-information\"\u003eTechnische informatie\u003c/h2\u003e\n\u003cp\u003eAls ik de technische details van mijn voetmaten erbij pak, wordt duidelijker waar ik tegenaan loop. Mijn voeten zijn ongeveer 11cm breed, mijn rechtervoet meet op het breedste punt zelfs 11,5cm. Verrassend genoeg plaatsen standaard maattabellen, zoals de hier genoemde, mijn maten in de categorie \u0026lsquo;Extra Wide\u0026rsquo; voor maat 11, terwijl er nog acht bredere categorieën bestaan. Dat onderstreept dat mijn voeten volgens de gangbare maatvoering helemaal niet uitzonderlijk breed zijn.\u003c/p\u003e\n\u003cfigure class=\"bookmark\"\u003e\u003ca class=\"bookmark__link\" href=\"https://www.healthyfeetstore.com/pages/width-sizing-chart\" rel=\"noopener\"\u003e\u003cspan class=\"bookmark__body\"\u003e\u003cspan class=\"bookmark__title\"\u003eShoe Width Measurement Chart | HealthyFeetStore.com\u003c/span\u003e\u003cspan class=\"bookmark__description\"\u003eThe shoe width measurement chart from HealthyFeetStore.com can help ensure you find the best fit for your feet. Explore our foot width chart to learn more now.\u003c/span\u003e\u003cspan class=\"bookmark__meta\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"25px\"\u003e\u003csource type=\"image/webp\" sizes=\"25px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/skating-with-wide-feet/bookmarks/01-icon_hu_df9bf35ed3f6de4a.webp\" alt=\"\" class=\"bookmark__icon\" width=\"32\" height=\"32\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003cspan\u003eHealthyFeetStore.com\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"bookmark__thumb\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/skating-with-wide-feet/bookmarks/01-thumb.svg\" sizes=\"250px\" alt=\"\" loading=\"lazy\" decoding=\"async\"\u003e\u003c/span\u003e\u003c/a\u003e\u003c/figure\u003e\n\n\u003cp\u003eDeze discrepantie wijst op een groot probleem in het ontwerp van de meeste inline skates. Ondanks de claims dat ze geschikt zijn voor bredere voeten, bieden veel skates in de praktijk niet het beloofde comfort voor mensen met bredere voeten. Mijn ervaringen met gewone schoenen zijn hiermee vergeleken heel anders.\u003c/p\u003e\n\u003cp\u003eMerken als Nike, Adidas, Fila en Globe geven mij geen problemen; ik kan hun schoenen de hele dag comfortabel dragen. Dat gewone schoenen zo makkelijk passen, maakt het des te raadselachtiger dat hard-shell inline skates me zoveel moeite kosten.\u003c/p\u003e\n\u003cp\u003eDan rijst de vraag: waarom is het passen in een hard-shell skate zo problematisch vergeleken met gewone schoenen?\u003c/p\u003e\n\u003ch2 id=\"adapt-to-the-conclusion\"\u003eAdapt als conclusie\u003c/h2\u003e\n\u003cfigure class=\"figure\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003csource type=\"image/webp\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/skating-with-wide-feet/images/20231009_120808_hu_92a17cef684cb47.webp\" alt=\"\" width=\"560\" height=\"262\" data-zoom-src=\"https://www.allsubjectsmatter.nl/skating-with-wide-feet/images/20231009_120808_hu_774f6918a5c3e65.webp\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003c/figure\u003e\n\n\u003cp\u003eDe uiteindelijke oplossing voor mijn pasvormproblemen kwam in de vorm van custom skates gemaakt door \u003ca href=\"https://www.adaptbrand.com/\" data-external\u003eAdapt\u003c/a\u003e. Helaas zijn ze recentelijk gestopt met het model dat ik heb: een custom Adapt Brutale maat 43,5, gemaakt met een shell die 3mm breder is dan hun standaard. Voor mij zijn deze skates een game changer: ze pasten meteen perfect uit de doos. Na het heat molden werden ze nog comfortabeler, alsof ze op maat voor mijn voeten gemaakt waren.\u003c/p\u003e\n\u003cp\u003eDeze skates hebben mijn leven compleet veranderd. Eindelijk skates die perfect zaten, alsof ze voor mijn voeten gemaakt waren. Na het heat molden zaten ze nog beter!\u003c/p\u003e\n\u003cp\u003eAls ik erop terugkijk: had ik destijds meteen deze Adapt-skates gekocht, dan was dat uiteindelijk goedkoper geweest dan wat ik nu allemaal in mijn Roces Fifth Elements heb gestoken. Achteraf weet je het altijd beter, maar ik loop er niet wakker van. Het hoort bij het leerproces als je ergens vol in gaat.\u003c/p\u003e\n\u003cp\u003eAdapt verdient echt een groot compliment voor hun werk. Ze maken nog steeds custom skates en er worden binnenkort nieuwe modellen verwacht. Hun huidige topmodel op het gebied van custom skates is de \u003ca href=\"https://www.adaptbrand.com/masterpieces-collection\" data-external\u003eMasterpiece\u003c/a\u003e-collectie, en die wil ik in de toekomst zeker hebben.\u003c/p\u003e\n\u003cp\u003eVoor andere skaters, zeker degenen met bredere voeten zoals ik: investeer in goede skates. Ik kan het niet genoeg benadrukken. Het maakt een wereld van verschil voor je skaten. Steun ambachtslieden en merken die echt vakmanschap en passie uitstralen. Je voeten zullen je dankbaar zijn.\u003c/p\u003e\n\u003cblockquote class=\"pull-quote\"\u003e\u003cp\u003eEen shoutout aan Adapt: ga zo door! Maak skaten tot een geweldige ervaring!\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cfigure class=\"embed\"\u003e\u003ciframe src=\"https://www.youtube-nocookie.com/embed/_m-Qo-yiXKY\" title=\"Skates \u0026amp; Coffee - Our favourite models from the \u0026#39;Handmade\u0026#39; collection\" loading=\"lazy\" allow=\"accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen referrerpolicy=\"strict-origin-when-cross-origin\"\u003e\u003c/iframe\u003e\u003c/figure\u003e\n\n\u003cblockquote class=\"pull-quote\"\u003e\u003c/blockquote\u003e\n","date_modified":"2024-02-15T13:54:34+01:00","date_published":"2024-01-11T21:39:00+01:00","id":"https://www.allsubjectsmatter.nl/nl/skating-with-wide-feet/","language":"nl","summary":"Ben je benieuwd welke skates passen bij bredere voeten? Op zoek naar skates die goed zitten? Lees verder voor mijn kijk op wat je hierover op internet vindt.","tags":["Inline skating"],"title":"Inline skaten met bredere voeten","url":"https://www.allsubjectsmatter.nl/nl/skating-with-wide-feet/"},{"authors":[{"name":"Remco Loup","url":"https://www.allsubjectsmatter.nl/author/remco/"}],"content_html":"\u003cp\u003eHier de link naar het originele artikel via de Wayback Machine: door corona en de overname van flow.ai door Khoros is deze blog niet meer publiek beschikbaar. Deze blog is geschreven in Q3 van 2019.\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://web.archive.org/web/20201020201705/https://flow.ai/blog/how-333travel-is-revolutionizing-the-travel-industry\" data-external\u003ehttps://web.archive.org/web/20201020201705/https://flow.ai/blog/how-333travel-is-revolutionizing-the-travel-industry\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eHelaas hebben we ervoor gekozen om Joy begin 2022 te stoppen, vanwege gebrek aan resultaat en omdat corona ons hard raakte. De klantbetrokkenheid was te laag om het in stand te houden, helaas.\u003c/p\u003e\n\u003chr\u003e\n\u003cblockquote class=\"pull-quote\"\u003e\u003cp\u003eArtificial intelligence (AI) om buiten openingstijden op een persoonlijke manier in contact te blijven. Dat is waar \u003ca href=\"https://web.archive.org/web/20201020201705/https://www.333travel.nl/\" data-external\u003e333travel\u003c/a\u003e hun chatbot Joy voor gebruikt.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eDe reisbranche is een innovatieve markt. De concurrentie is groot en de drang naar een technologisch voordeel ook, maar dat is lastig. Wie zich in deze markt niet technisch zelfstandig onderscheidt, wordt al snel door de massa opgeslokt en kan niet meer uniek zijn in zijn propositie. Daarmee komt je bestaansrecht in gevaar.\u003c/p\u003e\n\u003cp\u003eWe zoeken constant naar manieren om onze positie op de markt te verbeteren en vertalen elke dag IT op de juiste manier naar de business, zodat IT echt voor ons werkt. Blijf experimenteren en blijf nieuwe dingen proberen!\u003c/p\u003e\n\u003ch2 id=\"our-challenge\"\u003eOnze uitdaging\u003c/h2\u003e\n\u003cp\u003eWe zoeken altijd naar manieren om onze klanten beter van dienst te zijn, en dat willen we op een persoonlijke manier doen. Zo bouwen we echt een relatie op met onze klanten en bieden we volledig op maat gemaakte oplossingen.\u003c/p\u003e\n\u003cp\u003eWe zijn ervan overtuigd dat je bij een reis met ons de best mogelijke beleving van genot krijgt, want bij 333travel denken we met je mee van A tot Z en ontzorgen we je volledig.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u0026hellip;Met behulp van Joy heeft de klant sneller een antwoord en kunnen we meer kwaliteit bieden, omdat Joy indirect zorgt voor minder werk en meer beschikbare informatie voordat er een persoonlijk contactmoment plaatsvindt\u0026hellip;\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eHet inzetten van artificial intelligence klinkt daardoor misschien tegenstrijdig, maar dat is het zeker niet. We gaan deze AI natuurlijk allereerst gebruiken om onze klanten de bestaande informatie / FAQ op onze website te geven als ze daar vragen over hebben.\u003c/p\u003e\n\u003cp\u003eMaar naast het verstrekken van informatie zullen we altijd proberen door te sturen naar persoonlijk contact. Het is voor ons belangrijk om fysiek in contact te komen met onze potentiële klanten, op een persoonlijke, makkelijke en toegankelijke manier.\u003c/p\u003e\n\u003ch2 id=\"our-goal\"\u003eOns doel\u003c/h2\u003e\n\u003cp\u003eHet geautomatiseerd verzamelen van leads op een zo persoonlijk mogelijke manier is dus onze reden om deze artificial intelligence in te zetten. De rest is een bonus. We streven er zelfs naar om via deze AI juist méér persoonlijk contact te realiseren. De focus ligt op het creëren van waarde en het optimaliseren van onze customer journey buiten onze reguliere werktijden.\u003c/p\u003e\n\u003ch2 id=\"our-solution\"\u003eOnze oplossing\u003c/h2\u003e\n\u003cp\u003eSamen met Flow.ai zijn we gaan testen op welke persoonlijke manieren we een klant zover kunnen krijgen dat die contact met ons opneemt.\u003c/p\u003e\n\u003cp\u003eEen juiste use case van AI is hierbij heel belangrijk, maar ook de manier waarop je deze AI kunt trainen. We wilden de meest natuurlijke vorm van interactie creëren, maar we moeten werken met niet-sexy data zoals \u0026ldquo;departure\u0026rdquo;- en \u0026ldquo;arrival\u0026rdquo;-data om vooraf een beeld te krijgen van wanneer iemand op vakantie wil. Dat is een grotere uitdaging dan het lijkt.\u003c/p\u003e\n\u003cblockquote class=\"pull-quote\"\u003e\u003cp\u003eWe wilden absoluut voorkomen dat onze AI het niet direct begrijpt als iemand zegt: \u0026ldquo;I want to visit Thailand on June 17 for 1 week\u0026rdquo;.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"a-natural-interaction-with-the-right-intent\"\u003eEen natuurlijke interactie met de juiste intent\u003c/h2\u003e\n\u003cp\u003eWe hebben onze LLM daarom zo getraind dat hij intents begrijpt met kennis van wat de vertrekdatum is en wanneer je wilt vertrekken. Het is dus geen absoluut feit wat we van je willen weten, maar een waarde die binnen de betekenis van \u0026ldquo;wanneer wil je vertrekken\u0026rdquo; valt. Er zijn zoveel verschillende, maar ook correcte antwoorden op deze vraag. En als je onze eisen meeneemt, moet deze bot ook alles hieronder en meer begrijpen:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eIn de zomer wil ik in de vakantie…\u003c/li\u003e\n\u003cli\u003eIn de winter wil ik graag verblijven in…\u003c/li\u003e\n\u003cli\u003eIn het najaar wil ik naar…\u003c/li\u003e\n\u003cli\u003eIk wil met kerst in Thailand zitten…\u003c/li\u003e\n\u003cli\u003eVolgend jaar willen we naar…\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eHet wordt direct onvriendelijk als er na deze vraag alsnog een datumveld ingevuld moet worden met wanneer je wilt vertrekken. Of erger nog: als de bot de vraag helemaal niet begrijpt. Een klant weet bovendien vaak zelf niet precies wanneer hij wil vertrekken. Als hij zo begint, moet hij die informatie dan verplicht invullen? Je hebt bij dit type klant één kans.\u003c/p\u003e\n\u003cblockquote class=\"pull-quote\"\u003e\u003cp\u003eKlanten zijn de niet-werkende chatbots van grote bedrijven, die FTE willen besparen en willen zien hoe ver ze kunnen gaan in het pesten van hun klanten, spuugzat.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eWe leggen de kerninformatie van de verwachte vertrekperiode automatisch direct vast. Die wordt immers tijdens het contactmoment verder uitgediept; voor nu weten onze specialisten wat de intent is en kunnen ze daarop inspelen.\u003c/p\u003e\n\u003cp\u003eBinnen onze implementatie is het ook mogelijk om alle variabelen die we nodig hebben uit één zin van een klant te halen. Daarna hebben we alleen de contactgegevens nodig en bellen we op het gevraagde tijdstip en de gevraagde datum.\u003c/p\u003e\n\u003ch3 id=\"collecting-data\"\u003eData verzamelen\u003c/h3\u003e\n\u003cblockquote\u003e\n\u003cp\u003e“Goedemiddag, ik zoek een vakantie die begint in Indonesië en eindigt op Bali voor 2 volwassenen en 2 kinderen met een hotel aan het strand, vertrek op 21 juli en weer thuis op 3 augustus.”\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eDe zin hierboven is een praktisch voorbeeld van wat Joy begrijpt. We leggen deze waarden vast en weten dan meteen dat er genoeg data is. We vragen alleen nog het telefoonnummer/e-mailadres van de klant en bellen hem op een door hemzelf aangegeven moment.\u003c/p\u003e\n\u003cp\u003eWe bellen je op het door jou gewenste moment. We hebben er bewust voor gekozen om de klant geen date picker te laten gebruiken, om de natuurlijke flow te behouden. Voor de klant is het makkelijker om te zeggen: “donderdag over een week om 10 uur”. Daaruit begrijpen we wat de intentie is en bellen we die klant precies op dat moment. Er zijn ook genoeg klanten die zeggen dat ze nú gebeld willen worden. Of ze op dat moment gebeld kunnen worden of niet, ze vallen af zodra ze een date picker voorgeschoteld krijgen. Zeker als de dag van vandaag ontbreekt.\u003c/p\u003e\n\u003cp\u003eOmdat de klant onze website bezoekt, weten we ook vanaf welke pagina zijn verzoek om persoonlijk contact binnenkomt. Zo hebben onze specialisten nog meer informatie om een beeld te krijgen van wat de intentie is.\u003c/p\u003e\n\u003cp\u003eDeze data wordt automatisch als lead/voorstel verstuurd naar ons backoffice-systeem BOSS. En elke opvolging van deze lead wordt daar vastgelegd.\u003c/p\u003e\n\u003ch2 id=\"result-joy-sees-the-light\"\u003eResultaat: Joy ziet het levenslicht.\u003c/h2\u003e\n\u003cp\u003eNa een groot aantal interne tests, diverse finetuning-aanpassingen en het gebruik van verschillende methoden om ons doel te bereiken, is Joy geboren. We hebben haar ook een persoonlijk gezicht gegeven, omdat we de gesprekken persoonlijk willen houden. We geven natuurlijk wel aan dat Joy een “Virtual Travel Assistant” is. Onze echte collega’s blijven de reisspecialisten.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u0026hellip;Wat vooral opvalt aan de resultaten is dat de interacties en verzoeken die via Joy binnenkomen meestal afkomstig zijn van mensen tussen de 20 en 35 jaar\u0026hellip;\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"important-lessons\"\u003eBelangrijke lessen\u003c/h2\u003e\n\u003cp\u003eDe belangrijkste les hierin is misschien een beetje een open deur, maar zorg dat je doel op papier staat, en begin met de simpelste implementatie van dat doel en ontwikkel dat langzaam tot een complexer werkend eindproduct.\u003c/p\u003e\n\u003cp\u003eWe zijn eerst gaan kijken hoe we onze benodigde data kunnen verzamelen met \u003ca href=\"https://web.archive.org/web/20201020201705/https://flow.ai/\" data-external\u003eFlow.ai\u003c/a\u003e en hoe dat echt werkt. Daarna hebben we veel verschillende scenario’s getest en zijn we langzaam richting één complete flow gegaan die het beste van meerdere flows combineerde. Zo kregen we meteen veel ervaring met de verschillende opties die Flow bood.\u003c/p\u003e\n\u003cp\u003eWe worden hierbij goed ondersteund door Flow.ai, maar door onze eigen kennis in huis hadden we de support niet constant nodig en konden we echt diep op het onderwerp ingaan.\u003c/p\u003e\n\u003cp\u003eHet flow.ai-systeem is goed opgebouwd, heel logisch en begrijpelijk. Dat zou je er niet van moeten weerhouden om te experimenteren.\u003c/p\u003e\n\u003ch2 id=\"the-future\"\u003eDe toekomst\u003c/h2\u003e\n\u003cp\u003eHet eerste kwartaal is onze drukste periode. Omdat we Joy relatief kort voor deze periode hebben geïmplementeerd, hebben we de optie Live Chat nog niet geactiveerd. Dat gaan we na dit kwartaal doen.\u003c/p\u003e\n\u003cp\u003eDaarnaast willen we in de nabije toekomst nog verder gaan met Joy en haar dé assistent van ons klantportaal maken, die je op weg helpt nadat je een boeking hebt gemaakt. Als Joy het antwoord niet weet, kom je meteen in contact met de specialist bij wie je de reis hebt geboekt. Heb je vragen tijdens je reis? Vraag het Joy. Heb je problemen tijdens je reis? Joy zorgt ervoor dat je zo snel mogelijk een antwoord krijgt en dat je indien nodig direct contact met ons krijgt.\u003c/p\u003e\n\u003cblockquote class=\"pull-quote\"\u003e\u003cp\u003eWe zien Joy als onze “Virtual Travel Assistant”, waarbij de heel specifieke kennis van onze bestemmingen en onze kennis van maatwerk bij onze reisspecialisten blijft, want dat is de kracht van ons persoonlijke contact waarmee wij persoonlijk het verschil maken, binnen onze filosofie.\u003c/p\u003e\n\u003c/blockquote\u003e\n","date_modified":"2024-01-24T14:00:17+01:00","date_published":"2024-01-02T13:48:00+01:00","id":"https://www.allsubjectsmatter.nl/nl/how-333travel-is-revolutionizing-the-travel-industry/","language":"nl","summary":"Ontdek hoe de AI 'Joy' van 333travel al in 2019 de klantinteractie veranderde: het intentiebegrip van AI gecombineerd met een persoonlijke touch. Een gamechanger in AI-gedreven reisoplossingen!","tags":["Tech","AI and LM"],"title":"2019 - Blast from the past: hoe 333travel de reisbranche op zijn kop zette","url":"https://www.allsubjectsmatter.nl/nl/how-333travel-is-revolutionizing-the-travel-industry/"},{"authors":[{"name":"Remco Loup","url":"https://www.allsubjectsmatter.nl/author/remco/"}],"content_html":"\u003ch2 id=\"some-background-to-understand-our-issue-better\"\u003eWat je moet weten om ons probleem te snappen\u003c/h2\u003e\n\u003cp\u003eWe werken aan een nieuwe website met veel nadruk op afbeeldingen. We hebben al \u003ca href=\"http://www.333travel.nl/\" data-external\u003ewww.333travel.nl\u003c/a\u003e, dat als reisproduct natuurlijk al zwaar leunt op afbeeldingen. Onze nieuwe website laadt zelfs afbeeldingen met een nog hogere resolutie, omdat de nadruk op het luxe gevoel ligt. Deze site is gericht op de luxe reiziger: \u003ca href=\"http://www.travelplatinum.nl/\" data-external\u003ewww.travelplatinum.nl\u003c/a\u003e. Het project dat onze afbeeldingen serveert, static.333travel.nl, draait al een paar jaar zonder gemelde problemen voor de 333travel-website.\u003c/p\u003e\n\u003ch2 id=\"the-issue\"\u003eHet probleem\u003c/h2\u003e\n\u003cp\u003eHet probleem werd gemeld door onze thuiswerkers. Zij merkten dat bij het werken vanuit huis veel afbeeldingen op TravelPlatinum soms niet laadden (en dat een refresh het meestal oploste). Ze gaven ook aan dat alles gewoon werkte vanuit kantoor. Dat was voor ons het startpunt van een zoektocht naar de boosdoener.\u003c/p\u003e\n\u003cfigure class=\"figure\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003csource type=\"image/webp\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/sudden-issues-loading-images-from-our-static-project/images/data-src-image-8526b527-c281-496a-9251-1028b11d3560_hu_1bb3200cf0522d93.webp\" alt=\"\" width=\"560\" height=\"382\" data-zoom-src=\"https://www.allsubjectsmatter.nl/sudden-issues-loading-images-from-our-static-project/images/data-src-image-8526b527-c281-496a-9251-1028b11d3560_hu_220750cfd07c6574.webp\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003c/figure\u003e\n\n\u003cp\u003eWe zagen in de browsers die we gebruiken (Edge en Chrome) meldingen in de console van de mislukte afbeeldingen: ERR_HTTP2_PROTOCOL_ERROR. Met die melding in ons achterhoofd begonnen we in de logs op de server te zoeken. We draaien Nginx als webserver.\u003c/p\u003e\n\u003cp\u003eWe vonden snel een spoor dat bij elke afbeelding opdook die volgens ons niet goed geladen was. We konden het probleem zelf nog niet reproduceren.\u003c/p\u003e\n\u003cp\u003e0.0.0.0 - - [06/Nov/2023:15:45:02 +0100] \u0026ldquo;GET /web-images/10/2023/653775730fc66/Jazan\u0026mdash;Al-qahar-mountains-1200.webp HTTP/2.0\u0026rdquo; 206 183064 \u0026ldquo;\u003ca href=\"https://www.travelplatinum.nl/\" data-external\u003ehttps://www.travelplatinum.nl/\u003c/a\u003e\u0026rdquo; \u0026ldquo;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0\u0026rdquo;\u003c/p\u003e\n\u003cp\u003eLet op de 206 hier. Dat betekent partial content.\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/206\" data-external\u003ehttps://developer.mozilla.org/en-US/docs/Web/HTTP/Status/206\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eDat was vreemd, want onze webserver ondersteunde helemaal geen partial loading van afbeeldingen. We sturen nooit headers met iets als een range, terwijl dat nodig is om partial content te kunnen leveren.\u003c/p\u003e\n\u003ch2 id=\"our-attempt-to-fix-the-issue\"\u003eOnze poging om het probleem op te lossen\u003c/h2\u003e\n\u003cp\u003eDus daar doken we als eerste in. Ik kwam erachter dat Nginx standaard een header meestuurt die Content-Type: bytes teruggeeft. Ik snap niet waarom ik zou willen dat onze image server met een ok-antwoord reageert op een 206-request. We hebben dit uitgezet met de volgende setting in ons server block.\u003c/p\u003e\n\u003cp\u003eserver {\u003cbr\u003e\nserver_name static.333travel.nl;\u003cbr\u003e\nmax_ranges 0;\u003cbr\u003e\n}\u003c/p\u003e\n\u003cp\u003eWe vroegen de mensen die het probleem hadden om het te melden als het weer gebeurde. Helaas was dat snel het geval. De foutmelding in onze logging was wel veranderd. Edge en Chrome bleven dezelfde nutteloze ERR_HTTP2_PROTOCOL_ERROR in de console tab spuwen. In de timeline zagen we ook dat deze afbeeldingen op een bepaald punt stopten met laden.\u003c/p\u003e\n\u003cfigure class=\"figure\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003csource type=\"image/webp\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/sudden-issues-loading-images-from-our-static-project/images/data-src-image-1cdc5aec-ad55-4bfd-8a7c-2593b57282be_hu_597e0975f3b81981.webp\" alt=\"\" width=\"560\" height=\"247\" data-zoom-src=\"https://www.allsubjectsmatter.nl/sudden-issues-loading-images-from-our-static-project/images/data-src-image-1cdc5aec-ad55-4bfd-8a7c-2593b57282be_hu_ccaba7f5f6ea432e.webp\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003c/figure\u003e\n\n\u003cp\u003e0.0.0.0 - - [08/Nov/2023:14:22:27 +0100] \u0026ldquo;GET /web-images/06/2020/5ed7ad48d3d79/mexico-yucatan-chichen-itza-ik-kil-cenote.jpeg HTTP/2.0\u0026rdquo; 200 0 \u0026ldquo;\u003ca href=\"https://vue-boss.333office.nl/\" data-external\u003ehttps://vue-boss.333office.nl/\u003c/a\u003e\u0026rdquo; \u0026ldquo;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0\u0026rdquo;\u003c/p\u003e\n\u003cp\u003eWe krijgen nu een header met een lengte van 0 bytes en een response met http 200 (ok). Het wordt hier echt vreemd. De header geeft bij inspectie in de browser wél het juiste aantal bytes terug in de response header. Waarom zegt de log van Nginx dan dat hij met een size van 0 antwoordt? Hier werd het raar en we wisten eigenlijk niet wat we verder moesten doen. We moesten het probleem eerst zelf kunnen reproduceren voordat we echt konden troubleshooten.\u003c/p\u003e\n\u003cp\u003eIk probeerde een fout te simuleren met ab (apache benchmark). Ik begon voorzichtig met 500 requests en 50 gelijktijdige requests. Het probleem werd bij mij nooit getriggerd. Daarna ging ik in kleine stapjes omhoog tot 10.000 requests met 700 gelijktijdige requests, zonder het gemelde probleem ook maar één keer te zien. De serverload leek alles ook prima aan te kunnen. Het ging om een afbeelding van zo\u0026rsquo;n 500kb die ik daar opvroeg.\u003c/p\u003e\n\u003cfigure class=\"figure\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003csource type=\"image/webp\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/sudden-issues-loading-images-from-our-static-project/images/image_hu_e4a7c89173cb9060.webp\" alt=\"\" width=\"560\" height=\"339\" data-zoom-src=\"https://www.allsubjectsmatter.nl/sudden-issues-loading-images-from-our-static-project/images/image_hu_52a1f89bab47586c.webp\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003c/figure\u003e\n\n\u003ch2 id=\"the-facts\"\u003eDe feiten\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003eEen traag netwerkprofiel instellen in de network tab bij het inspecteren simuleerde het probleem voor ons niet.\u003c/li\u003e\n\u003cli\u003eDe fout werd nog steeds alleen gemeld door 2 gebruikers die vanuit huis werkten.\u003c/li\u003e\n\u003cli\u003eOp kantoor hadden ze geen enkel probleem.\u003c/li\u003e\n\u003cli\u003eDe site 333travel.nl laden gaf helemaal geen probleem. Elke afbeelding werkte daar ook voor die 2 thuiswerkers.\u003c/li\u003e\n\u003cli\u003eTesten via een 5g-verbinding op allerlei plekken die we konden bedenken simuleerde het probleem voor mij geen enkele keer.\u003c/li\u003e\n\u003cli\u003eab (apache benchmark) met 10.000 requests en 700 gelijktijdige requests triggerde het probleem nooit.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eEr was nog één verschil, en dat was het domein dat de afbeeldingen serveerde. 333travel kreeg een Cross origin header van same_site terug, wat klopt, want het domein is hetzelfde. Dus ik voegde een header toe aan de static.\u003c/p\u003e\n\u003cp\u003eserver {\u003cbr\u003e\nserver_name static.333travel.nl;\u003cbr\u003e\nmax_ranges 0;\u003cbr\u003e\nadd_header \u0026lsquo;Access-Control-Allow-Origin\u0026rsquo; \u0026lsquo;*\u0026rsquo;;\u003cbr\u003e\n}\u003c/p\u003e\n\u003cp\u003eZo krijgt elke bron die afbeeldingen van onze static.333travel.nl wil gebruiken hetzelfde antwoord. Dit loste niets op van het gemelde probleem.\u003c/p\u003e\n\u003cp\u003eWe hadden nog steeds moeite om het probleem zelf te simuleren. We waren er vrij zeker van dat het alleen getriggerd werd op een of andere slechte verbinding bij een bezoek aan TravelPlatinum. Een bezoek aan 333travel, dat de afbeeldingen van dezelfde static server serveert, liet het probleem dat we op TravelPlatinum ervaren nooit zien.\u003c/p\u003e\n\u003ch2 id=\"simulating-a-bad-wifi-connection\"\u003eEen slechte wifi-verbinding simuleren\u003c/h2\u003e\n\u003cp\u003eWe wilden zo graag een slechte wifi-verbinding simuleren. Ik verplaatste mezelf naar een plek buiten ons kantoor waar de wifi slecht was, en eindelijk zag ik het probleem met eigen ogen. Eindelijk iets om mee te testen. Toch bleef het raar dat we een of andere trage/slechte wifi nodig hadden om dit te simuleren. Je zou zeggen dat je profiel op slow 3g zetten in de developer tools van de browser hetzelfde gedrag zou geven.\u003c/p\u003e\n\u003cp\u003eNu konden we eindelijk gaan testen vanuit onze development-omgeving. We begonnen te vermoeden dat het project zelf iets raars deed, misschien iets met javascript dat de verbinding sloot of zo. Dus begonnen we een stripped-down versie van de TravelPlatinum-website te bouwen om zonder javascript te testen.\u003c/p\u003e\n\u003cp\u003eHet probleem dat afbeeldingen niet laadden was er nog steeds..\u003c/p\u003e\n\u003cp\u003eWe besloten exact dezelfde html van 333travel.nl over te nemen om daarmee te testen en zo een gelijke omgeving te creëren. 333travel deed het immers bij elke refresh nog prima.\u003c/p\u003e\n\u003cp\u003eHet probleem dat afbeeldingen niet laadden was er nog steeds..\u003c/p\u003e\n\u003cp\u003eWe wilden dit nog beter kunnen simuleren, want onze slechte wifi-plek was niet bepaald een bureau; we zaten op een trap het probleem te debuggen. Ik besloot een 2\u003csup\u003end\u003c/sup\u003e wifi-hotspot op te zetten in ons kantoor met een beperkte verbinding van 3mbit om te zien waar dat ons zou brengen. Dit was het! We konden nu bij elke refresh een heleboel kapotte afbeeldingen op TravelPlatinum simuleren! We komen in elk geval ergens. Bedenk wel dat de 333travel-website nog steeds elke afbeelding zonder problemen laadde.\u003c/p\u003e\n\u003cp\u003eWe lieten de nginx-logs op dat moment voor wat ze waren, omdat we erachter waren gekomen dat we in die richting niets gingen oplossen. Nginx gaf nog steeds een ok terug op een header met 0 bytes. Als nginx dat ok vindt met de juiste headers op zijn plek, gaat dat bericht ons op dat moment niet helpen het probleem op te lossen.\u003c/p\u003e\n\u003ch2 id=\"testing-with-firefox\"\u003eTesten met Firefox\u003c/h2\u003e\n\u003cp\u003eOp het moment dat ik deze wifi-hotspot aanmaakte, waren we net zover dat we afbeeldingen konden testen met alleen de html image tag en verder niets geladen in het project. Ook dat brak onze afbeeldingen. Ik stelde toen voor om Firefox er even bij te pakken, gewoon om te zien of die zich anders gedroeg, want ik vermoedde dat het browsergerelateerd was — hoe de browser reageert op wat er gebeurt. Of in elk geval zou hij misschien een andere foutmelding geven. Iets waar we onze zoektocht naar de oplossing op konden richten.\u003c/p\u003e\n\u003cp\u003eWe zagen dat Firefox de afbeeldingen inderdaad regel voor regel laadde op de 3mbit-verbinding. Maar plotseling stopte het laden, met kapotte afbeeldingen als gevolg die voor zo\u0026rsquo;n 50% geladen waren of helemaal niet. Eindelijk zagen we eens iets anders! Edge/Chrome lieten nog steeds helemaal geen afbeelding zien en gaven me een nutteloze foutmelding. Nu drong eindelijk tot me door dat de static de afbeeldingen daadwerkelijk aan het versturen was en de browser ze ontving, maar dat de verbinding op de een of andere manier te vroeg gesloten werd.\u003c/p\u003e\n\u003ch2 id=\"the-solution\"\u003eDe oplossing\u003c/h2\u003e\n\u003cp\u003eEn dat was het moment waarop ik het probleem eindelijk vond. Een header die ik al tijden geleden op globaal niveau in Nginx had gezet tijdens een performance-tuningsessie.\u003c/p\u003e\n\u003cp\u003esend_timeout 2;\u003c/p\u003e\n\u003cp\u003eWat send timeout volgens mij doet, is de verbinding X seconden openhouden na het niet ontvangen van data. De standaard is 60 seconden en ik had hem op 2 seconden gezet zodat we geen 60 seconden lang open verbindingen zouden hebben die niets doen.\u003c/p\u003e\n\u003cp\u003eWaarom zou dat het probleem zijn, terwijl ik Firefox duidelijk de afbeelding regel voor regel zag laden? Er wordt dan toch constant data verstuurd? Waarom zou deze setting dan gerelateerd zijn aan het sluiten van de verbinding.\u003c/p\u003e\n\u003ctable\u003e\n\t\u003cthead\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003cth\u003eSyntax:\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003e\u003cstrong\u003esend_timeout\u003c/strong\u003e \u003cem\u003etime\u003c/em\u003e;\u003c/th\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eDefault:\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003esend_timeout 60s;\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eContext:\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003ehttp, server, location\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003e\u003cem\u003eSets a timeout for transmitting a response to the client. The timeout is set only between two successive write operations, not for the transmission of the whole response. If the client does not receive anything within this time, the connection is closed.\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003eNou, het blijkt dat dit op tragere verbindingen wél getriggerd wordt. Nginx interpreteert dit als een stalled request. En het sluit de hele verbinding naar in ons geval static.333travel.nl. De gedeeltelijk geladen afbeelding en elke afbeelding daarna die niet geladen werd, wordt niet opnieuw geladen omdat de verbinding met static gesloten is.\u003c/p\u003e\n\u003cp\u003eWaarom het simuleren van een trage verbinding in de developer tools dit probleem niet triggert, weet ik nog steeds niet zeker. Ik heb een paar theorieën: een slechte wifi-verbinding zorgt ook voor meer latency en verloren packets die opnieuw verstuurd moeten worden. Maar de echte reden ken ik niet. Ik weet niet hoe deze browsers een trage verbinding simuleren.\u003c/p\u003e\n\u003cp\u003eDeze send_timeout op 8 seconden zetten zoals hieronder loste ons probleem volledig op.\u003c/p\u003e\n\u003cp\u003eserver {\u003cbr\u003e\nserver_name static.333travel.nl;\u003cbr\u003e\nmax_ranges 0;\u003cbr\u003e\nadd_header \u0026lsquo;Access-Control-Allow-Origin\u0026rsquo; \u0026lsquo;*\u0026rsquo;;\u003cbr\u003e\nsend_timeout 8;\u003cbr\u003e\n}\u003c/p\u003e\n\u003ch2 id=\"conclusion\"\u003eConclusie\u003c/h2\u003e\n\u003cp\u003eWe hebben veel geleerd, en we hebben het probleem opgelost. Raar blijft wel dat dit al die tijd alleen op ons TravelPlatinum-project getriggerd werd. We balanceerden blijkbaar op de grens met de send_timeout-setting, omdat de afbeeldingen op TravelPlatinum voor meer load zorgen.\u003c/p\u003e\n\u003cp\u003eNog iets belangrijks om te onthouden: een trage verbinding instellen in je developer tools is geen echte simulatie van een trage verbinding. Je kunt simuleren wat je wilt, maar je hebt echte praktijksituaties nodig om echt te meten of alles goed werkt.\u003c/p\u003e\n","date_modified":"2024-02-16T08:45:11+01:00","date_published":"2023-11-12T12:50:00+01:00","id":"https://www.allsubjectsmatter.nl/nl/sudden-issues-loading-images-from-our-static-project/","language":"nl","summary":"We zagen dat in de browsers die we gebruiken (Edge en Chrome) meldingen in de console stonden van afbeeldingen die niet laadden. ERR_HTTP2_PROTOCOL_ERROR","tags":["Tech"],"title":"Problemen met het tonen van afbeeldingen vanaf onze Static server","url":"https://www.allsubjectsmatter.nl/nl/sudden-issues-loading-images-from-our-static-project/"},{"authors":[{"name":"Remco Loup","url":"https://www.allsubjectsmatter.nl/author/remco/"}],"content_html":"\u003ch2 id=\"notepad-regexp\"\u003eNotepad++ regexp\u003c/h2\u003e\n\u003cp\u003eBijvoorbeeld om je php-fpm access/slow log te controleren. Gebruik mark in Notepad++ om alle regels te markeren die trager zijn dan x/y.\u003c/p\u003e\n\u003cpre class=\"line-numbers language-text\"\u003e\u003ccode class=\"language-text\"\u003e10000ms en trager \n\n^.*\\s(\\d{5,}\\.\\d{3}ms).*\n\n5000ms en trager \n\n^.*\\s(5\\d{3}\\.\\d{2,}ms|\\d{5,}\\.\\d{2,}ms).*\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003ch2 id=\"dns-tools-to-automatically-check-the-transfer-of-your-dns\"\u003eDNS-tools om de overdracht van je DNS automatisch te controleren\u003c/h2\u003e\n\u003cpre class=\"line-numbers language-bash\"\u003e\u003ccode class=\"language-bash\"\u003esudo apt update\nsudo apt install dnsutil\n\nnano check_nameservers.sh\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cpre class=\"line-numbers language-bash\"\u003e\u003ccode class=\"language-bash\"\u003e#!/bin/bash\n\nDOMAIN=\u0026amp;#34;jouwdomein.com\u0026amp;#34;\nEXPECTED_NS1=\u0026amp;#34;ns1.cloudflare.com.\u0026amp;#34;\nEXPECTED_NS2=\u0026amp;#34;ns2.cloudflare.com.\u0026amp;#34;\n\n# Get the current nameservers\nCURRENT_NS=$(dig NS $DOMAIN \u0026#43;short)\n\n# Check if the current nameservers match the expected ones\nif [[ $CURRENT_NS == *\u0026amp;#34;$EXPECTED_NS1\u0026amp;#34;* \u0026amp;amp;\u0026amp;amp; $CURRENT_NS == *\u0026amp;#34;$EXPECTED_NS2\u0026amp;#34;* ]]; then\n    echo \u0026amp;#34;Nameservers are correctly set to Cloudflare.\u0026amp;#34;\nelse\n    echo \u0026amp;#34;Nameservers have not yet switched to Cloudflare.\u0026amp;#34;\nfi\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cpre class=\"line-numbers language-bash\"\u003e\u003ccode class=\"language-bash\"\u003echmod \u0026#43;x check_nameservers.sh\n\nwatch -n 1 ./check_nameservers.sh\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e","date_modified":"2024-09-11T09:24:35+02:00","date_published":"2023-09-11T09:21:00+02:00","id":"https://www.allsubjectsmatter.nl/nl/useful-commands/","language":"nl","summary":"Handige commando's en scripts die technische taken makkelijker maken, zoals logs beheren met Notepad++ en regex, of DNS-checks voor domeinen automatiseren.","tags":["Tech"],"title":"Handige commando's","url":"https://www.allsubjectsmatter.nl/nl/useful-commands/"},{"authors":[{"name":"Remco Loup","url":"https://www.allsubjectsmatter.nl/author/remco/"}],"content_html":"\u003ch2 id=\"introduction\"\u003eInleiding\u003c/h2\u003e\n\u003cp\u003eIk leerde over Scrum lang voordat de manifest-update van 2020 er was. Toen we na die release van 2020 een Scrum Master-training volgden, hadden we een leuke discussie over de verandering die sinds de update van de Scrum Guide de meeste impact op mij heeft gehad.\u003c/p\u003e\n\u003cp\u003eDe verschuiving van één inspecteerbaar werkend artifact aan het einde van een sprint, naar veel vaker releasen binnen de sprint zelf. Dat is een goede ontwikkeling, want we leven in een CI/CD-wereld en iets vasthouden als het Done is, is niet nodig.\u003c/p\u003e\n\u003cp\u003eMijn probleem ermee is dat het de focus weghaalde bij wat ik wist over wat een Sprint Review was. En dat zette me aan het denken.\u003c/p\u003e\n\u003ch2 id=\"some-more-background\"\u003eWat meer achtergrond\u003c/h2\u003e\n\u003cp\u003eVoor ons team betekent de status \u0026ldquo;Done\u0026rdquo; dat een feature klaar is voor deployment. Dat wil niet zeggen dat we overhaast releasen; ingrijpende wijzigingen worden zorgvuldig afgewogen, zodat ze aansluiten bij de wensen van gebruikers en bredere doelen. Elke update wordt grondig gecontroleerd, waarbij onze Product Owner een sleutelrol speelt en de release afstemt op de zakelijke belangen.\u003c/p\u003e\n\u003cp\u003eMet een goede CI/CD-implementatie kan je team vergeten dat een update al is gereleased zodra die in onze staging-omgeving staat. Wanneer we de trekker overhalen en live gaan, is aan de business. Het team moet wel weten wanneer iets volledig is gereleased, want we houden mogelijke problemen ook in de gaten. Maar de Developers zouden geen belang bij de daadwerkelijke release moeten hebben. Ik snap dat dit wat meer uitleg verdient. Dat bewaren we voor een toekomstige post.\u003c/p\u003e\n\u003cp\u003eIn onze DONE-lane houden we DONE-nog-niet-gereleased en DONE-gereleased apart.\u003c/p\u003e\n\u003ch2 id=\"our-mini-sprint-reviews\"\u003eOnze mini-Sprint Reviews\u003c/h2\u003e\n\u003cp\u003eOm het scherp te formuleren: onze feature-releases lijken vaak op een reeks \u0026ldquo;mini-Sprint Reviews\u0026rdquo;\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eEr is een uitgebreide discussie over wijzigingen en mogelijke gevolgen.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003eDeployment-momenten worden strategisch gepland.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003eGedetailleerde changelogs worden samengesteld.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003eStakeholders worden op de hoogte gehouden en hun feedback wordt na de release actief opgevraagd.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003eDoor deze grondige aanpak zijn de grote gesprekspunten vaak al behandeld tegen de tijd dat onze Sprint Review eraan komt.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eDat haalt een aantal onderwerpen weg uit de Sprint Review. Zeker als je een goede CI/CD-pipeline hebt en vaak releaset.\u003c/p\u003e\n\u003cp\u003eAls je updates vóór het einde van de sprint bespreekt en live zet, en je de feedback op de gereleasde features al binnenkrijgt — waarom zou je mensen dan nog een keer om feedback of een discussie over die release vragen? Scrum is een framework dat gesprekken mogelijk maakt. Maar er moet wel waarde in die meetings zitten. En voor ons is de Sprint Review over dit onderwerp behoorlijk nutteloos geworden.\u003c/p\u003e\n\u003ch2 id=\"what-do-we-discuss\"\u003eWat bespreken we dan?\u003c/h2\u003e\n\u003cp\u003eJe zou je kunnen afvragen: met zo\u0026rsquo;n grondige evaluatie vóór de release, wat blijft er dan over voor de Sprint Review? We zijn onze aanpak nog aan het verfijnen, maar dit is onze huidige structuur\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eToetsen of we het sprintdoel hebben gehaald.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003eAnalyseren welke taken in de sprint onaangeroerd zijn gebleven.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003eDieper ingaan op lopende development-items.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003eHet backlog bijstellen naar aanleiding van veranderingen uit de afgelopen sprint, zodat komende sprints duidelijke prioriteiten hebben.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003eProactief nieuwe taken of aanpassingen identificeren op basis van recente lessen.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003eStakeholders betrekken voor gedetailleerde feedback waar dat nodig is.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eZo werken biedt ons een paar duidelijke voordelen. Onze stakeholders zitten in alle afdelingen van ons bedrijf. Veel van hen werken hybride of zelfs 3/4 dagen in een hybride combinatie. Door frequent te releasen en ons plannen daarop aan te passen, bespreken we de release op een moment dat ze op kantoor zijn, en dat werkt voor ons gewoon het beste. De mogelijkheid om dingen face to face te bespreken is van onschatbare waarde. Het werkt gewoon beter en zorgt voor meer begrip tussen elkaar.\u003c/p\u003e\n\u003cp\u003eOnze reviews vallen traditioneel op vrijdag. Dat is geen probleem, want we kunnen de stakeholder gewoon op elke dag dat we klaar zijn een seintje geven. De update bespreken. En live gaan. We hoeven hier niet zo\u0026rsquo;n drama van te maken. We hebben korte feedbackloops! Vreselijke meetings inplannen is iets voor trage, grote bedrijven waar iedereen denkt dat hij belangrijk is en alles vastloopt in bureaucratie.\u003c/p\u003e\n\u003cp\u003eIn de meeste van onze Sprint Reviews bespreken we niets over wat we hebben gedeployd. We inspecteren de increment niet. De increment is al geïnspecteerd en we gaan snel door naar de andere onderwerpen die hierboven staan.\u003c/p\u003e\n\u003ch2 id=\"conclusion\"\u003eConclusie\u003c/h2\u003e\n\u003cp\u003eEen paar essentiële elementen sturen deze aanpak van ons aan.\u003c/p\u003e\n\u003cp\u003eEen Product Owner met echte beslissingsbevoegdheid, die de afstemming op bredere zakelijke doelen garandeert.\u003c/p\u003e\n\u003cp\u003eJe hebt een Scrum-team nodig dat strak samenwerkt om volledige stories of features af te ronden. Ze écht DONE krijgt. Je hebt FLOW in je team nodig om dit voor elkaar te krijgen.\u003c/p\u003e\n\u003cp\u003eJe hebt een goede CI/CD-omgeving nodig waarin je geautomatiseerd en vaak kunt deployen. (Daar werken we zelf nog aan. We zijn geautomatiseerd, maar nog niet genoeg. Er kan nog veel meer handwerk uit.)\u003c/p\u003e\n\u003cp\u003eScrum blijft zich ontwikkelen, en onze werkwijze ook — altijd met waarde en efficiëntie voorop.\u003c/p\u003e\n","date_modified":"2024-02-16T08:45:17+01:00","date_published":"2023-08-29T08:12:00+02:00","id":"https://www.allsubjectsmatter.nl/nl/sprint-reviews-in-scrum/","language":"nl","summary":"Ik leerde over Scrum lang voordat de manifest-update van 2020 er was. Toen we na die release van 2020 een Scrum Master-training volgden, hadden we een leuke discussie over de verandering die sinds de update van de Scrum Guide de meeste impact op mij heeft gehad.","tags":["Agile"],"title":"Sprint Reviews in high performance Scrum","url":"https://www.allsubjectsmatter.nl/nl/sprint-reviews-in-scrum/"},{"authors":[{"name":"Remco Loup","url":"https://www.allsubjectsmatter.nl/author/remco/"}],"content_html":"\u003cp\u003eOntdek mijn persoonlijke skateroutes in Alphen aan den Rijn via \u003ca href=\"https://www.google.nl/maps/d/u/1/edit?mid=1fG1tn4gzgQ-jEQOcD-t-NTvVzb4BUoI\u0026amp;usp=sharing\" data-external\u003eGoogle My Maps\u003c/a\u003e. Deze zorgvuldig uitgekozen paden hebben voor ten minste 90% een uitstekende ondergrond. Hoewel ik mijn best doe om de ideale routes te vinden, kun je af en toe wat minder goed asfalt, klinkers, en veeroosters tegenkomen.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eWat je kunt verwachten van mijn skateroutes:\u003c/strong\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eAfstanden variërend tussen 5 en 25 km.\u003c/li\u003e\n\u003cli\u003eGeregeld nieuwe routes toegevoegd als aanvulling op het bestaande aanbod.\u003c/li\u003e\n\u003cli\u003eDuidelijke start- en eindpunten met parkeermogelijkheid voor zowel fietsen als auto\u0026rsquo;s.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"de-routes\"\u003eDe route\u0026rsquo;s\u003c/h2\u003e\n\u003cfigure class=\"bookmark\"\u003e\u003ca class=\"bookmark__link\" href=\"https://www.google.nl/maps/d/u/1/edit?mid=1fG1tn4gzgQ-jEQOcD-t-NTvVzb4BUoI\u0026amp;usp=sharing\" rel=\"noopener\"\u003e\u003cspan class=\"bookmark__body\"\u003e\u003cspan class=\"bookmark__title\"\u003eInline Skateroutes Alphen aan den Rijn - Google My Maps\u003c/span\u003e\u003cspan class=\"bookmark__description\"\u003eRoutes die ik geskate heb en prima te doen zijn qua asfalt. Let wel op dat specifieke routes veeroosters kunnen hebben!\u003c/span\u003e\u003cspan class=\"bookmark__meta\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/inline-skating-routes-rond-alphen-aan-den-rijn/bookmarks/01-icon.ico\" sizes=\"25px\" alt=\"\" class=\"bookmark__icon\" loading=\"lazy\" decoding=\"async\"\u003e\u003cspan\u003eGoogle My Maps\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"bookmark__thumb\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/webp\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/avif\" sizes=\"250px\"\u003e\u003csource type=\"image/webp\" sizes=\"250px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/inline-skating-routes-rond-alphen-aan-den-rijn/bookmarks/01-thumb_hu_73c765fb71066390.webp\" alt=\"\" width=\"500\" height=\"435\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003c/span\u003e\u003c/a\u003e\u003c/figure\u003e\n\n\u003cp\u003eVia de link hierboven kun je de routes die ik geskate heb vinden. Gebruik mijn routes als inspiratie voor jouw volgende skate-avontuur in en rond Alphen aan den Rijn!\u003c/p\u003e\n\u003ch3 id=\"route-rietveldse-pad\"\u003eRoute: Rietveldse pad.\u003c/h3\u003e\n\u003cp\u003eHier kun je prima je auto of fiets parkeren bij het startpunt of er zelf heen skaten. We starten van de parkeerplaats zoals getoond en eindigen daar ook. Perfect voor een ontspannen rit.\u003c/p\u003e\n\u003ch3 id=\"route-rietveldse-pad-optie-2-let-op-veeroosters\"\u003eRoute: Rietveldse pad optie 2 (Let op! Veeroosters).\u003c/h3\u003e\n\u003cp\u003eVeeroosters komen hier voor. op deze route vind je er minimaal 2. Deze bevinden zich tussen marker C en D. Ook hier is het prima parkeren en eindig je waar je start.\u003c/p\u003e\n\u003ch3 id=\"route-rietveldse-pad-naar-hazerswoude\"\u003eRoute: Rietveldse pad naar Hazerswoude.\u003c/h3\u003e\n\u003cp\u003eOok hier kun je prima je auto of fiets parkeren bij het startpunt of er zelf heen skaten. We starten vanaf de welbekende parkeerplaats en eindigen daar ook.\u003c/p\u003e\n\u003ch3 id=\"route-rietveldse-pad-naar-hazerswoude-alternatief\"\u003eRoute: Rietveldse pad naar hazerswoude alternatief.\u003c/h3\u003e\n\u003cp\u003ePrima je auto of fiets te parkeren bij het startpunt. We starten van de parkeerplaats en eindigen daar ook.\u003c/p\u003e\n\u003ch3 id=\"route-korte-ridderbuurt-heen-en-weer\"\u003eRoute: Korte Ridderbuurt heen en weer.\u003c/h3\u003e\n\u003cp\u003ePrima route om een beetje te carven en je skills te verbeteren. Je kunt prima meerdere keren heen en weer :). Weinig verkeer qua auto\u0026rsquo;s en mooi uitzicht op de boerenakkers.\u003c/p\u003e\n\u003ch3 id=\"route-ter-aar-korteraar-en-langeraar-let-op-veeroosters\"\u003eRoute: Ter aar, Korteraar en Langeraar (Let op! Veeroosters).\u003c/h3\u003e\n\u003cp\u003eHier vind je op meerdere plekken veeroosters! Tussen F en G vind je minimaal 2 veeroosters. En tussen H en I op het windepad vind je minimaal 6 veeroosters. De langste route tot nu welke verder prima te skaten is.\u003c/p\u003e\n\u003ch3 id=\"route-richting-boskoop-via-rietveldse-pad-terug\"\u003eRoute: Richting Boskoop via Rietveldse pad terug.\u003c/h3\u003e\n\u003cp\u003eDeze route is deels nog in verkenning maar heb hier regelmatig gefietst en al delen van geskate. Deze route lijkt prima te doen en is ook weer een combi met het Rietveldsepad. Het stuk naar Boskoop en langs het station is allemaal prima. Ook het Raoulwallenbergplein is prima te bereiken met auto of fiets. Parkeren kan eenvoudig in parkeergarage Castellum.\u003c/p\u003e\n\u003cp\u003eHoud in gedachten dat elke inline skater verschillende voorkeuren heeft. Wat ik ideaal vind, is mogelijk niks voor jou.\u003c/p\u003e\n","date_modified":"2023-10-11T10:35:25+02:00","date_published":"2023-08-14T08:00:00+02:00","id":"https://www.allsubjectsmatter.nl/nl/inline-skating-routes-rond-alphen-aan-den-rijn/","language":"nl","summary":"Ontdek mijn persoonlijke skateroutes in Alphen aan den Rijn via Google My Maps. Deze zorgvuldig uitgekozen paden hebben voor ten minste 90% een uitstekende ondergrond. Hoewel ik mijn best doe om de ideale routes te vinden, kun je af en toe wat minder goed asfalt, klinkers, en veeroosters tegenkomen.","tags":["Inline skating"],"title":"Inline skateroutes Alphen aan den rijn","url":"https://www.allsubjectsmatter.nl/nl/inline-skating-routes-rond-alphen-aan-den-rijn/"},{"authors":[{"name":"Remco Loup","url":"https://www.allsubjectsmatter.nl/author/remco/"}],"content_html":"\u003ch2 id=\"introduction\"\u003eInleiding\u003c/h2\u003e\n\u003cp\u003eAllereerst dit.\u003c/p\u003e\n\u003cblockquote class=\"pull-quote\"\u003e\u003cp\u003eOpinions are like assholes. Everybody has one\u0026hellip;\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eIk laat het daarbij, maar er zijn genoeg varianten op dit citaat. Lees dus gerust met een gezonde dosis scepsis verder.\u003c/p\u003e\n\u003ch2 id=\"the-word-sprint\"\u003eHet woord \u0026ldquo;Sprint\u0026rdquo;\u003c/h2\u003e\n\u003cp\u003eIk blijf er moeite mee hebben om het woord Sprint te gebruiken voor elke cyclus van twee weken, elke keer als ik nadenk over wat het woord voor mij betekent. Deze sprints stoppen nooit! Het moment waarop een sprint eindigt, begint een nieuwe sprint\u0026hellip;\u003c/p\u003e\n\u003cp\u003eIn het echte leven kun je op z\u0026rsquo;n minst even op adem komen en slapen voordat je naar de volgende sprint vertrekt. En nee, een retro is niet goed genoeg. \u003cem\u003e(SCRUM-politie)\u003c/em\u003e Een nieuwe sprint begint op het moment dat een sprint eindigt! De retro valt ook binnen een sprint. \u003cem\u003e(/SCRUM-politie)\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003eNederlands is mijn moedertaal, dus mijn interpretatie van het woord sprint kan verschillen. Voor mij staat een sprint voor: zo hard mogelijk gaan om als eerste te finishen. Natuurlijk houd je je aan de regels die je niet diskwalificeren. Maar elke keer als ik aan een Sprint denk, komt het woord \u0026ldquo;haast\u0026rdquo; in me op. Afmaken, wat er ook gebeurt. Die betekenis geeft een gevoel van haast, en dat levert geen schonere code of minder technische schuld op.\u003c/p\u003e\n\u003cp\u003eNa een echte sprint weet ik dat ik fysiek niet in staat ben om direct weer een sprint te starten. Wil je als team in een flow komen, dan moet je juist stoppen met rennen en het gesprek aangaan met elkaar. Neem de tijd om te zorgen dat iedereen op één lijn zit en hetzelfde doel ziet.\u003c/p\u003e\n\u003cblockquote class=\"pull-quote\"\u003e\u003cp\u003eSCRUM is toch vooral een framework dat gesprekken faciliteert?\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eHet woord \u0026ldquo;Sprint\u0026rdquo; betekent voor mij ook individualiteit. En dat is ongewenst als je een SCRUM-team bent dat samen in een flow wil komen.\u003c/p\u003e\n\u003cfigure class=\"bookmark\"\u003e\u003ca class=\"bookmark__link\" href=\"https://www.allsubjectsmatter.nl/nl/achieving-flow-with-scrum/\"\u003e\u003cspan class=\"bookmark__body\"\u003e\u003cspan class=\"bookmark__title\"\u003eAchieving FLOW with Scrum.\u003c/span\u003e\u003cspan class=\"bookmark__description\"\u003eIn this post I share my current journey of implementing Scrum and Kanban principles with my team, this is mainly about emphasizing the importance of real collaborative work and instilling a product vision which helps us to achieve FLOW which translates to high value Scrum for us.\u003c/span\u003e\u003cspan class=\"bookmark__meta\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"25px\"\u003e\u003csource type=\"image/webp\" sizes=\"25px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/scrum-sprint-misinterpretations-within-scrum/bookmarks/01-icon_hu_9b466dd874965fcb.webp\" alt=\"\" class=\"bookmark__icon\" width=\"50\" height=\"50\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003cspan\u003eAll Subjects Matter\u003c/span\u003e\u003cspan\u003eRemco Loup.\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"bookmark__thumb\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/webp\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/avif\" sizes=\"250px\"\u003e\u003csource type=\"image/webp\" sizes=\"250px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/scrum-sprint-misinterpretations-within-scrum/bookmarks/01-thumb_hu_c670a19d35689e3b.webp\" alt=\"\" width=\"500\" height=\"435\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003c/span\u003e\u003c/a\u003e\u003c/figure\u003e\n\n\u003cp\u003eGewoon een beetje stoeien met ideeën, maar het een estafette noemen zou misschien beter zijn! Dat roept ook overdracht en teamwork met elkaar op. Het resultaat is voor het hele team hetzelfde, net als bij een echte estafette. Maar ook hier speelt het aspect haast een rol.\u003c/p\u003e\n\u003cp\u003eJe hoeft de naam natuurlijk niet te veranderen, maar dit onderwerp met je team bespreken kan wél! Bespreek het samen met je DOD, Acceptance Criteria en Sprint goal. Zorg in elk geval dat iedereen begrijpt dat je het gehaaste aspect dat bij het woord sprint hoort niet wilt. Doe het liever zo goed mogelijk, volgens alle afgesproken regels. In plaats van stiekem de hoeken afsnijden in een grijs gebied waar niemand je ziet!\u003c/p\u003e\n\u003ch2 id=\"change-that-name\"\u003eVerander die naam!\u003c/h2\u003e\n\u003cp\u003eEn als we een andere naam nodig hebben, wat dacht je van Development Cycles? Dan kunnen we het Cycles noemen, zoals \u003ca href=\"https://linear.app/\" data-external\u003eLinear\u003c/a\u003e doet. Of noem het Flows. Voor onze Flow van twee weken heb ik deze Flow goal in gedachten.\u003c/p\u003e\n\u003cp\u003eOm een naamswijziging te laten werken, moeten we het er allemaal over eens zijn dat we die naam gebruiken. Tools zoals Jira leggen veel van deze namen al vast en die zijn niet aan te passen. Wij als development team zitten met de wind mee: Jira wordt bij ons alleen gebruikt voor onze backlog en administratie. Maar ons fysieke Scrum board is onze single source of truth voor wat we proberen te bereiken.\u003c/p\u003e\n\u003cp\u003eOnze sprints worden alleen op een hoger niveau in Jira geregistreerd. Alle dagelijkse taken die uit de sprint planning voortkomen, staan niet in ons Jira.\u003c/p\u003e\n\u003cfigure class=\"bookmark\"\u003e\u003ca class=\"bookmark__link\" href=\"https://www.allsubjectsmatter.nl/nl/our-journey-into-visual-scrum/\"\u003e\u003cspan class=\"bookmark__body\"\u003e\u003cspan class=\"bookmark__title\"\u003eA journey into visual Scrum\u003c/span\u003e\u003cspan class=\"bookmark__description\"\u003eIn this post, I will share the experiments we conducted during the last three sprints with our physical Scrum board. The focus was about creating a better understandable visual representation of our sprint progress. Sprints are two weeks increments for us at this moment.\u003c/span\u003e\u003cspan class=\"bookmark__meta\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"25px\"\u003e\u003csource type=\"image/webp\" sizes=\"25px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/scrum-sprint-misinterpretations-within-scrum/bookmarks/02-icon_hu_9b466dd874965fcb.webp\" alt=\"\" class=\"bookmark__icon\" width=\"50\" height=\"50\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003cspan\u003eAll Subjects Matter\u003c/span\u003e\u003cspan\u003eRemco Loup.\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"bookmark__thumb\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/webp\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/avif\" sizes=\"250px\"\u003e\u003csource type=\"image/webp\" sizes=\"250px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/scrum-sprint-misinterpretations-within-scrum/bookmarks/02-thumb_hu_876db5afb4ecba2.webp\" alt=\"\" width=\"500\" height=\"435\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003c/span\u003e\u003c/a\u003e\u003c/figure\u003e\n\n\u003ch2 id=\"do-not-change-that-name\"\u003eVerander die naam niet!\u003c/h2\u003e\n\u003cp\u003eHet woord \u0026ldquo;Sprint\u0026rdquo; zelf is voor mij eigenlijk geen probleem. Maar het woord roept wel dingen op die ik niet wil hebben in onze sprints.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eWe willen geen individualiteit in onze sprints.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003eWe willen geen hoeken afsnijden in sprints.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003eWe willen niet als eerste finishen ten koste van andere aspecten in sprints.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eWe moeten scherp blijven op het verfijnen van onze DOD en het gebruiken van de juiste Acceptance Criteria bij onze issues om de kwaliteit hoog te houden. En het allerbelangrijkste: definieer elke sprint een goede Sprint goal en maak die tastbaar. Een andere naam voor sprints verandert daar niets aan. Wij als team moeten het laten gebeuren: kwaliteit boven snelheid.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eEen woord als \u0026ldquo;Sprint\u0026rdquo; veranderen betekent niets als je geen omgeving creëert waarin \u0026ldquo;haast\u0026rdquo; en \u0026ldquo;gejaagd\u0026rdquo; geen woorden zijn die de werkethiek beschrijven. Een sprint is gewoon een development cycle van x weken die iedereen begrijpt, en zou helemaal niets met haast te maken moeten hebben.\u003c/p\u003e\n\u003c/blockquote\u003e\n","date_modified":"2024-02-16T08:45:23+01:00","date_published":"2023-08-07T08:20:00+02:00","id":"https://www.allsubjectsmatter.nl/nl/scrum-sprint-misinterpretations-within-scrum/","language":"nl","summary":"Een woord als \"Sprint\" vervangen betekent niets als je geen omgeving creëert waarin woorden als \"haast\" en \"spoed\" niet de werkethiek beschrijven. Een sprint is gewoon een ontwikkelcyclus van x weken die iedereen begrijpt, en die moet helemaal niets met haast en spoed te maken hebben.","tags":["Agile","SCRUM","Product Owner"],"title":"De verkeerde interpretatie van het woord \"Sprint\" binnen SCRUM","url":"https://www.allsubjectsmatter.nl/nl/scrum-sprint-misinterpretations-within-scrum/"},{"authors":[{"name":"Remco Loup","url":"https://www.allsubjectsmatter.nl/author/remco/"}],"content_html":"\u003ch2 id=\"introduction\"\u003eInleiding\u003c/h2\u003e\n\u003cp\u003eIk ben al een tijdje gefascineerd door het idee om een bedrijf om te vormen tot een DAO (Decentralized Autonomous Organization). Toen ik nadacht over mijn rol als Product Owner, bedacht ik een aanpak die heel goed kan werken bij het omvormen van een deel van ons bedrijf naar een DAO. Zo combineren we de voordelen van Scrum, Agile en een DAO.\u003c/p\u003e\n\u003cp\u003eIn mijn persoonlijke geval hieronder is een hybride aanpak het meest voor de hand liggend: bepaalde onderdelen van ons bedrijf worden door een DAO beheerd, terwijl de rest voor nu onder traditioneel management blijft. Mijn DAO zou worden gebruikt om ons Front/Mid/Backoffice-systeem, BOSS, te beheren en beslissingen te nemen over wat we hierna ontwikkelen. De bredere organisatie blijft voorlopig op de gebruikelijke manier draaien. Zo profiteert ons bedrijf van de voordelen van een DAO, terwijl we een deel van de risico\u0026rsquo;s en uitdagingen afdekken.\u003c/p\u003e\n\u003cp\u003eOns doel is om dit systeem tot een SaaS-product binnen de reisbranche te ontwikkelen. En een DAO kan bijvoorbeeld worden ingezet om onze stakeholders te betrekken. Maar daar lees je verderop meer over.\u003c/p\u003e\n\u003ch2 id=\"what-is-a-dao\"\u003eWat is een DAO?\u003c/h2\u003e\n\u003cp\u003eEen DAO, of Decentralized Autonomous Organization, is een organisatievorm die wordt gerepresenteerd door regels die als computerprogramma zijn vastgelegd (een zogenaamd smart contract). Die regels zijn transparant, worden gecontroleerd door de leden van de organisatie en staan niet onder invloed van een centrale autoriteit.\u003c/p\u003e\n\u003cp\u003eDe financiële transacties en regels van een DAO liggen vast op een blockchain, een vorm van gedistribueerd grootboek. Daardoor is een DAO volledig autonoom en onafhankelijk van externe of gecentraliseerde controle.\u003c/p\u003e\n\u003cp\u003eZie het als een digitaal bedrijf, waarbij alle regels en verordeningen in een smart contract zijn gecodeerd. Stel dat er in een DAO een regel is dat een bepaald voorstel 60% van de stemmen nodig heeft om aan te nemen. Die regel zit in het smart contract, en de blockchain zorgt dat de regel strikt wordt nageleefd. Niemand kan hem manipuleren of omzeilen.\u003c/p\u003e\n\u003cp\u003eLeden van een DAO kunnen beslissingen nemen over de regels of de richting van de organisatie door voorstellen te doen en erop te stemmen. Iedereen heeft stemrecht naar evenredigheid van zijn stake of aandelen in de DAO. Zo lijkt een DAO een beetje op een traditioneel bedrijf, maar dan volledig democratisch en gedecentraliseerd geleid.\u003c/p\u003e\n\u003cp\u003eEen DAO gebruikt blockchain-technologie om te zorgen dat de organisatie volkomen transparant, democratisch en gedecentraliseerd wordt bestuurd, vrij van centrale of externe controle.\u003c/p\u003e\n\u003ch2 id=\"tokens\"\u003eTokens\u003c/h2\u003e\n\u003cp\u003eEen uniek token creëren voor een DAO kan nuttig zijn en is een gangbare praktijk. Of het de \u0026ldquo;beste\u0026rdquo; praktijk is, hangt af van de doelen en context van de DAO. Dit is waarom DAO\u0026rsquo;s vaak hun eigen tokens uitgeven:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003eStemrecht\u003c/strong\u003e: Tokens kunnen stemmacht binnen de DAO vertegenwoordigen. Hoe meer tokens een lid heeft, hoe meer invloed diegene heeft op de beslissingen van de DAO.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eIncentives\u003c/strong\u003e: Tokens kunnen worden gebruikt om bepaald gedrag of bijdragen aan de DAO te stimuleren. Zo kun je tokens geven als beloning voor taken die de DAO verder helpen, zoals programmeren, ontwerpen of projecten managen.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eFondsenwerving\u003c/strong\u003e: Tokens uitgeven kan een manier zijn om geld op te halen. Deelnemers kunnen tokens kopen en worden daarmee in feite investeerders in de DAO.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eCommunity-opbouw\u003c/strong\u003e: Een uniek token kan het gemeenschapsgevoel onder leden versterken. Het bevordert een gevoel van gedeeld eigendom en gezamenlijke investering in het succes van de DAO.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eEr zijn echter ook uitdagingen en aandachtspunten:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003eRegelgeving\u003c/strong\u003e: Afhankelijk van de jurisdictie kan het uitgeven van een token worden gezien als het uitgeven van een security, met implicaties voor regelgeving en juridische vereisten.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eTokenverdeling\u003c/strong\u003e: Bepalen hoe je de tokens aanvankelijk verdeelt, is lastig en kan tot discussie leiden. De verdeling moet eerlijk zijn en aansluiten bij de doelen en waarden van de DAO.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eSchommelende tokenwaarde\u003c/strong\u003e: Als het token op de open markt wordt verhandeld, kan de waarde schommelen, wat kan leiden tot instabiliteit en mogelijke manipulatie.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eComplexiteit\u003c/strong\u003e: Een token uitgeven en beheren maakt het runnen van de DAO complexer. Niet elke DAO heeft een eigen token nodig, zeker als de operaties simpel zijn of als een bestaand token goed kan worden gebruikt.\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"how-to-leverage-a-dao-as-a-product-owner\"\u003eHoe benut je een DAO als Product Owner\u003c/h2\u003e\n\u003cp\u003eStel je voor dat je een DAO runt waarin klanten of geïnteresseerden direct in je product kunnen investeren. Ze krijgen stakes of tokens, waarmee ze een stem krijgen in je ontwikkelproces, dat volgens Scrum/Agile verloopt.\u003c/p\u003e\n\u003cp\u003eZeg dat deze potentiële klanten een specifieke functie, X of Y, in je product willen. Dan kunnen ze met hun tokens op die functie stemmen en zo direct de ontwikkelprioriteiten beïnvloeden. Dit stemmechanisme trekt potentiële klanten dichter naar je merk en geeft bestaande klanten de kans om het product dat ze dagelijks gebruiken zelf vorm te geven.\u003c/p\u003e\n\u003cp\u003eIn feite worden de functies die bovenaan hun wensenlijst staan het focuspunt van je ontwikkelstrategie. Het mooie van deze aanpak is de transparantie. Door gebruikers te laten stemmen over mogelijke uitbreidingen van je product, laat je zien waarom bepaalde functies voorrang krijgen boven andere. Het is een democratie waarin je pas kunt stemmen als je het \u0026ldquo;waarom\u0026rdquo; achter elke beslissing kent. Dat leidt tot een gedetailleerd begrip van hoe het product evolueert.\u003c/p\u003e\n\u003cp\u003eDeze aanpak is een zegen, niet alleen voor Product Owners maar ook voor je organisatie als geheel. In plaats van je ontwikkelproces geheim te houden, nodig je je gebruikers uit om er deel van uit te maken. Zo\u0026rsquo;n participatieve aanpak kan sneller vertrouwen opbouwen bij klanten en potentiële gebruikers dan traditionele methoden.\u003c/p\u003e\n\u003cp\u003eAls Product Owner blijf je onmisbaar bij het filteren en interpreteren van klantwensen, en het omzetten daarvan in begrijpelijke stories die zowel technische als niet-technische mensen snappen.\u003c/p\u003e\n\u003cp\u003eAls het tijd is om de topprioriteiten te kiezen, staat je roadmap al uitgestippeld door de democratische stemming. Deze interactie laat je sneller met je klanten in gesprek komen en van ze leren dan wanneer je personas of andere traditionele tools gebruikt om te bepalen wat er komt. Meerderheidsbesluiten kunnen je sprintdoelen bepalen en zo je focus voor de volgende ontwikkelcyclus sturen.\u003c/p\u003e\n\u003cp\u003eWat is een betere manier om transparantie en klantinput te stimuleren dan Agile/Scrum op deze manier in te zetten? Dit proces integreert de principes adapt, inspect en learn op een nauw verbonden manier. Met een DAO-achtige aanpak wordt de evolutie van je product echt een community-gedreven onderneming.\u003c/p\u003e\n\u003ch2 id=\"caveats\"\u003eKanttekeningen\u003c/h2\u003e\n\u003cp\u003eEr zijn natuurlijk ook kanttekeningen.\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003eGovernance-uitdagingen\u003c/strong\u003e: Het implementeren van het governance-model van een DAO kan complex zijn. De belangen van alle stakeholders op een eerlijke en evenwichtige manier balanceren is een flinke uitdaging. Het is niet altijd zo dat de beslissing van de meerderheid aansluit bij het belang van het project of van alle stakeholders.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eBeveiliging van smart contracts\u003c/strong\u003e: DAO\u0026rsquo;s werken op basis van smart contracts, die onveranderbaar zijn zodra ze gedeployd zijn. Zit er een bug of kwetsbaarheid in het contract, dan kan dat tot serieuze problemen leiden, waaronder verlies van fondsen. De beruchte \u0026ldquo;The DAO\u0026rdquo;-hack van 2016, waarbij 50 miljoen dollar werd weggesluisd via een loophole in een smart contract, is daar een voorbeeld van.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eOnduidelijke regelgeving\u003c/strong\u003e: DAO\u0026rsquo;s bestaan in veel rechtsgebieden in een juridisch grijze zone. Hoe ze juridisch worden geclassificeerd en gereguleerd, is in veel delen van de wereld nog onduidelijk.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eToken-distributie\u003c/strong\u003e: Hoe tokens aanvankelijk worden verdeeld, kan ook een complexe kwestie zijn. Als dat niet goed gebeurt, kan het leiden tot centralisatie of een machtsonevenwicht binnen de DAO.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eLage participatie\u003c/strong\u003e: DAO\u0026rsquo;s zijn afhankelijk van actieve deelname van token-houders. Lage opkomst kan echter een groot probleem zijn, waardoor beslissingen door een kleine groep stakeholders worden genomen.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eKortetermijndenken\u003c/strong\u003e: Er is een risico dat token-houders die alleen in kortetermijnwinst geïnteresseerd zijn, stemmen op voorstellen die direct rendement opleveren maar op lange termijn schadelijk zijn.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eDe interface tussen DAO en de echte wereld\u003c/strong\u003e: DAO\u0026rsquo;s zijn digitaal, maar hun beslissingen hebben vaak gevolgen in de echte wereld, waar handhaving complexer kan zijn.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eElk van deze uitdagingen is te overwinnen, maar ze vergen wel zorgvuldig nadenken en een doordacht ontwerp om ze effectief aan te pakken. Zoals bij elk systeem moet je de voordelen altijd afwegen tegen de mogelijke risico\u0026rsquo;s en nadelen.\u003c/p\u003e\n\u003ch2 id=\"the-role-of-a-product-owner\"\u003eDe rol van een Product Owner\u003c/h2\u003e\n\u003cp\u003eHier komt een goede Product Owner om de hoek kijken: die zorgt ervoor dat mensen niet over je hele backlog stemmen, maar over zorgvuldig geselecteerde items die al voldoende gerefined zijn en klaar staan om tot op een zekere mate van onzekerheid ontwikkeld te worden. Je wordt nog steeds geacht de totale markt te kennen, zodat je de items om aan te pakken voorselecteert en mensen er vervolgens op laat stemmen.\u003c/p\u003e\n\u003cp\u003eJe hebt nog steeds stakeholders die je tevreden moet houden en features moet ontwikkelen die de markt het liefst wil. Alleen door een DAO te gebruiken en mensen betrokken te houden, kun je je markt veel beter leren kennen en je erop aanpassen dan op andere manieren.\u003c/p\u003e\n\u003cp\u003eOok al is een DAO fundamenteel democratisch, het heeft iemand nodig met een volledig begrip van het product om de ontwikkeling te sturen. Een PO kan inzicht geven in de haalbaarheid en impact van features, en zo besluitvorming en prioritering helpen sturen.\u003c/p\u003e\n\u003ch2 id=\"smart-contracts\"\u003eSmart contracts\u003c/h2\u003e\n\u003cp\u003eEen smart contract is een computerprotocol dat het onderhandelen over of uitvoeren van een digitaal contract faciliteert, verifieert of afdwingt. Smart contracts draaien op de blockchain, wat ze transparant, traceerbaar en onomkeerbaar maakt. Ze zijn ontworpen om automatisch transacties of andere specifieke acties uit te voeren zodra vooraf ingestelde voorwaarden zijn vervuld.\u003c/p\u003e\n\u003cp\u003eDit zijn een paar belangrijke redenen waarom het belangrijk is om smart contracts zorgvuldig te ontwerpen en te implementeren:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003eOnveranderlijkheid\u003c/strong\u003e: Zodra een smart contract op de blockchain is gedeployd, kan het niet meer worden aangepast of gecorrigeerd. Door die onveranderlijkheid is het cruciaal dat een smart contract grondig wordt getest en gereviewd vóór de deployment, zodat het doet wat het moet doen.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eSecurity\u003c/strong\u003e: Smart contracts zijn gevoelig voor bugs en exploits als ze niet correct geprogrammeerd zijn. Gezien de waarde die er vaak via smart contracts wordt verhandeld of beheerd, is security van het grootste belang. Exploits kunnen tot aanzienlijke verliezen leiden, zoals bij de beruchte DAO-hack in 2016, waarbij er zo\u0026rsquo;n $50 miljoen verloren ging door een bug in het smart contract van de DAO.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eFinanciële gevolgen\u003c/strong\u003e: Smart contracts beheren, bewaren of faciliteren vaak de overdracht van cryptocurrency, en fouten of kwetsbaarheden in het contract kunnen aanzienlijke financiële gevolgen hebben.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eVertrouwen en betrouwbaarheid\u003c/strong\u003e: Het vertrouwen in een smart contract, en daarmee in de hele operatie of het systeem dat het ondersteunt, hangt sterk af van het feit dat het contract werkt zoals verwacht. Problemen kunnen de waargenomen betrouwbaarheid van het systeem aantasten en mensen afschrikken om deel te nemen of het te gebruiken.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eComplexiteit en interoperabiliteit\u003c/strong\u003e: Smart contracts werken vaak samen met andere contracten of systemen. Het is belangrijk om die interacties goed door te denken om onverwacht gedrag te voorkomen. Wie de complexiteit van zulke interacties niet begrijpt of negeert, kan schadelijke gevolgen tegemoet gaan.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eWet- en regelgeving\u003c/strong\u003e: Afhankelijk van de jurisdictie en de specifieke use case moeten smart contracts zo worden ontworpen dat ze voldoen aan de relevante wet- en regelgeving. Dat voegt weer een extra laag complexiteit toe aan het ontwerp en de implementatie.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eHet ontwerp, de implementatie en de review van smart contracts moeten dus uiterst serieus en zorgvuldig worden aangepakt. In veel gevallen is het verstandig om smart contracts vóór de deployment te laten auditen door een gespecialiseerd securitybedrijf.\u003c/p\u003e\n\u003ch2 id=\"a-written-sample-of-a-smart-contract\"\u003eEen geschreven voorbeeld van een Smart Contract\u003c/h2\u003e\n\u003cp\u003eJe hebt dus vooraf een goed werkend contract nodig. De Product Owner kan bijvoorbeeld de beheerder van zo\u0026rsquo;n smart contract zijn. Hij voegt de issues toe aan het systeem, en de stakeholders kunnen met hun tokens op die issues stemmen.\u003c/p\u003e\n\u003cp\u003eElke stem verhoogt het belang van de issues die ontwikkeld moeten worden. Daarna kies je de issues met de hoogste ranking om aan te werken, met in je achterhoofd wat je in één sprint kunt doen.\u003c/p\u003e\n\u003cp\u003eJe kunt bijvoorbeeld ook met twee soorten tokens werken. Gewone tokens voor je klanten die je product gebruiken, en sociale tokens voor bijvoorbeeld potentiële klanten.\u003c/p\u003e\n\u003cp\u003eOok de geldigheid van tokens is iets om over na te denken, want wensen kunnen na verloop van tijd verouderd raken.\u003c/p\u003e\n\u003cp\u003eDit kan allemaal behoorlijk complex worden, en misschien post ik in een later blog een volledig voorbeeld van een smart contract in Solidity met de bovenstaande voorwaarden.\u003c/p\u003e\n\u003ch2 id=\"conclusion\"\u003eConclusie\u003c/h2\u003e\n\u003cp\u003eDit is een wat langer artikel geworden dan gebruikelijk, maar er was veel uitleg nodig om te begrijpen wat het betekent om je rol als PO in te zetten binnen een DAO.\u003c/p\u003e\n\u003cp\u003eIk hoop dat dit een beetje helpt om te verduidelijken wat een DAO is en hoe je die kunt gebruiken, vooral vanuit het perspectief van een SCRUM/Agile Product Owner, waar beide echt kunnen gedijen.\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://www.scrum.org/francois-fort\" data-external\u003eFrancois Fort\u003c/a\u003e schreef hier een artikel over op \u003ca href=\"https://www.scrum.org/resources/blog/are-daos-future-agile-organizations\" data-external\u003escrum.org\u003c/a\u003e: Are DAOs the future of Agile Organisations.\u003c/p\u003e\n\u003cp\u003eJe hebt stakeholders nodig die achter je plannen staan, en wat is beter dan betrokken stakeholders die een aandeel hebben in je product via een DAO? Je krijgt gedetailleerde en directe reacties van de markt als je een betrokken, gevarieerde groep mensen hebt die wil dat je product slaagt, want dan slagen zij zelf ook.\u003c/p\u003e\n","date_modified":"2025-01-07T13:36:33+01:00","date_published":"2023-07-25T15:37:00+02:00","id":"https://www.allsubjectsmatter.nl/nl/dao-and-agile-from-traditional-businesses-to-a-dao-creates-opportunities-for-product-owners/","language":"nl","summary":"Al een tijdje ben ik gefascineerd door het idee om een bedrijf om te vormen tot een DAO (Decentralized Autonomous Organisation). Nadenkend over mijn rol als Product Owner bedacht ik een aanpak die heel goed kan werken bij het omvormen van een deel van ons bedrijf tot een DAO","tags":["Agile","DAO","SCRUM","Product Owner"],"title":"Van traditionele bedrijven naar DAO's: kansen voor Product Owners","url":"https://www.allsubjectsmatter.nl/nl/dao-and-agile-from-traditional-businesses-to-a-dao-creates-opportunities-for-product-owners/"},{"authors":[{"name":"Remco Loup","url":"https://www.allsubjectsmatter.nl/author/remco/"}],"content_html":"\u003cp\u003eIets totaal anders dan Agile of Tech, maar een klein inkijkje in hoe ik het inline skaten heb hervonden, plus wat geschiedenis met inline skaten.\u003c/p\u003e\n\u003ch2 id=\"the-past\"\u003eHet verleden\u003c/h2\u003e\n\u003cp\u003eSkaten heeft een grote rol gespeeld in mijn leven, net zo\u0026rsquo;n vast onderdeel van mijn jeugd als de zomers met geschaafde knieën en blauwe plekken op mijn ellebogen. Begin jaren 90, ik was een jaar of 8, 9, begon ik met inline skaten omdat iedereen toen skates had. Al snel groeide die hobby uit tot een passie. Achteraf gezien was het geratel van wielen op het asfalt voor mij een symfonie, een viering van jeugd, vrijheid en lef.\u003c/p\u003e\n\u003cp\u003eIn die tijd skatete ik vooral halfpipe, toen ik een jaar of 12 was. De adrenaline, de kick van de zwaartekracht verslaan: verslavend. Ik heb ontelbare uren op die halfpipe doorgebracht, het metaal onder mijn wielen werd net zo vertrouwd als mijn eigen broekzak. Het ging niet alleen om een truc landen, het ging om die rush, die roes van angst en verwachting vlak voordat je van de ramp afzette.\u003c/p\u003e\n\u003cp\u003eNaast de halfpipe dook ik ook de wereld van streethockey in. Uitgerust met kniebeschermers en mijn vertrouwde Roces streets om, was ik klaar om alles aan te kunnen wat op mijn pad kwam. Die bal over het asfalt najagen was de mooiste vorm van chaos, en ik genoot van elke minuut.\u003c/p\u003e\n\u003cp\u003eToen ik 16 was, begon mijn passie voor inline skaten op de een of andere manier te vervagen. Ik weet niet meer waarom ik stopte – misschien kwam het leven ertussen, of werd ik afgeleid door andere bezigheden. Maar het vlammetje is nooit helemaal gedoofd, het bleef smeulen, wachtend op een kans om weer op te laaien.\u003c/p\u003e\n\u003cfigure class=\"figure\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003csource type=\"image/webp\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/inline-skating-these-days/images/halfpipesept1998_hu_950a645dec9922f6.webp\" alt=\"\" width=\"560\" height=\"745\" data-zoom-src=\"https://www.allsubjectsmatter.nl/inline-skating-these-days/images/halfpipesept1998_hu_12b1ec34bdac5bf.webp\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003cfigcaption\u003eIk achteraan met de lichtbruine broek (14 jaar), september 1998 (Roces Streets)\u003c/figcaption\u003e\u003c/figure\u003e\n\n\u003ch2 id=\"the-present\"\u003eHet heden\u003c/h2\u003e\n\u003cp\u003eDie kans kwam begin 2022, toen mijn kinderen interesse kregen in inline skaten. Hun nieuwsgierigheid wakkerde mijn eigen passie weer aan, en ik vroeg me af: waarom ben ik eigenlijk ooit gestopt? Toen heb ik besloten de skates te kopen waar ik als klein jongetje altijd van droomde, en de wereld die ik achter me had gelaten opnieuw binnen te stappen.\u003c/p\u003e\n\u003cp\u003eNa een bezoekje aan de \u0026lsquo;\u003ca href=\"https://www.thisissoul.com/\" data-external\u003ethisissoul\u003c/a\u003e\u0026rsquo;-winkel, waar het personeel ongelooflijk behulpzaam was en me hielp met het passen van verschillende modellen – ik heb namelijk brede voeten. Toch stond mijn hoofd allang naar één specifiek model: de Roces 5th Element, de skate van mijn kinderjaren. Die skates weer aantrekken was alsof ik terugstapte in mijn jongere ik, een heerlijk gevoel van nostalgie en hernieuwde opwinding dat ik nu 5th elements had.\u003c/p\u003e\n\u003cp\u003eVandaag de dag zitten mijn Roces onder een gerockerde \u0026lsquo;Wish\u0026rsquo; (\u003ca href=\"https://www.oysius.com/mc65/\" data-external\u003eOysi\u003c/a\u003e) frame, met een rocker van 0,5mm, wat de controle en wendbaarheid op de skates verbetert. Deze technische setup verwijst terug naar mijn halfpipe-tijd, maar sluit ook aan bij de meer ritmische, vloeiende stijl van Wizard Skating.\u003c/p\u003e\n\u003cfigure class=\"figure\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003csource type=\"image/webp\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/inline-skating-these-days/images/20230813_191301_hu_3db90f8add70e8e2.webp\" alt=\"\" width=\"560\" height=\"262\" data-zoom-src=\"https://www.allsubjectsmatter.nl/inline-skating-these-days/images/20230813_191301_hu_d051f68e377328f.webp\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003cfigcaption\u003eDe halfpipe waarin ik sinds 1995 leerde halfpipe-skaten. Hij staat er nog steeds fier bij. 2 meter hoog\u003c/figcaption\u003e\u003c/figure\u003e\n\n\u003cp\u003eTerugkijkend is mijn reis met inline skaten echt volledig rondgekomen. Het is een geweldig gevoel om deze passie te delen met mijn kinderen, net zoals ik deed toen ik zo oud was als zij. De Joy, de vrijheid, de adrenaline – het is er allemaal nog, net zo levendig als in mijn jongere jaren. Inline skaten is geëvolueerd, en ik ben met het meegegaan. En dat zou ik voor geen goud anders willen.\u003c/p\u003e\n\u003cp\u003eWaar ik was gebleven pakte ik het weer op, en mijn terugkeer naar de wereld van inline skaten is niets minder dan verfrissend. Elke skatesessie geeft me een gevoel van Joy, een drive om oude skills op te poetsen en nieuwe te leren. Met mijn vertrouwde Roces om voelde het gewoon als fietsen. Eenmaal geleerd, verleer je het nooit meer.\u003c/p\u003e\n\u003cp\u003eHet mooiste deel van deze reis is het delen met mijn kinderen. Zien hoe zij hun skates aandoen en de wereld op wielen verkennen, net zoals ik op hun leeftijd deed, geeft me een gevoel van nostalgie en trots. Hun gilletjes van vreugde als ze een nieuwe truc onder de knie hebben, hun doorzettingsvermogen als ze vallen en weer opstaan: een prachtige herinnering aan waarom ik ooit verliefd werd op deze sport.\u003c/p\u003e\n\u003cfigure class=\"figure\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003csource type=\"image/webp\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/inline-skating-these-days/images/IMG20230513141741_hu_1d193cd4f4f394a5.webp\" alt=\"\" width=\"560\" height=\"420\" data-zoom-src=\"https://www.allsubjectsmatter.nl/inline-skating-these-days/images/IMG20230513141741_hu_55753694c7e82f93.webp\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003cfigcaption\u003eIk met mijn 2 kleine meiden op inline skates.\u003c/figcaption\u003e\u003c/figure\u003e\n\n\u003ch2 id=\"nostalgia\"\u003eNostalgie\u003c/h2\u003e\n\u003cp\u003eHet is vreemd om te bedenken waarom ik ooit ben gestopt met inline skaten. Misschien had ik die pauze wel nodig om te beseffen wat ik had gemist. Hoe dan ook ben ik dankbaar dat ik de kans krijg om terug te keren naar mijn roots en mijn liefde voor deze dynamische sport opnieuw te ontdekken.\u003c/p\u003e\n\u003cp\u003eEn zo, terwijl ik door de straten glijd, de wind in mijn gezicht en de wereld die voorbijraast, kan ik niet anders dan me weer dat jochie van 10 voelen, dat net begon met skaten. Alleen heb ik nu het extra genoegen om deze passie te delen met mijn kinderen.\u003c/p\u003e\n\u003cp\u003eDus, op de Roces 5th Elements, op de gerockerde \u0026lsquo;Wish\u0026rsquo; (Oysi) frame, op de adrenaline van de halfpipe en de vloeiende bewegingen van Wizard Skating. Op de Joy van vallen en weer opstaan, en vooral op de pure liefde voor skaten. Want uiteindelijk gaat het niet om de skates, de stijl of de trucs. Het gaat om het rijden zelf, de vrijheid, en de herinneringen voor het leven die ermee komen.\u003c/p\u003e\n\u003cfigure class=\"figure\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003csource type=\"image/webp\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/inline-skating-these-days/images/skating-sept-1995_hu_88c894b8717da63f.webp\" alt=\"\" width=\"538\" height=\"355\" data-zoom-src=\"https://www.allsubjectsmatter.nl/inline-skating-these-days/images/skating-sept-1995_hu_88c894b8717da63f.webp\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003cfigcaption\u003eIk, 11 jaar oud. Cruisen op mijn Rollerblade Zetrablades.\u003c/figcaption\u003e\u003c/figure\u003e\n\n\u003ch2 id=\"utrecht-skateparade\"\u003eUtrecht Skateparade\u003c/h2\u003e\n\u003cp\u003eEen van de leukste dingen van mijn terugkeer naar het inline skaten is het meedoen aan de \u003ca href=\"https://u-skateparade.nl/home/\" data-external\u003eUtrecht Skate Parade\u003c/a\u003e. Het evenement is een feest van de skatecultuur, waar skaters van alle leeftijden en niveaus uit de hele stad en omstreken samenkomen. Het is een levendige gemeenschap, allemaal op wielen, samen skaten over een route van zo\u0026rsquo;n 25 km.\u003c/p\u003e\n\u003cp\u003eDe Parade wordt elke week gehouden van mei tot eind september. We skaten over de autowegen door het hart van de stad Utrecht. Elke week staat er een andere DJ op het voertuig dat vooraan in de Parade rijdt.\u003c/p\u003e\n\u003cp\u003eEr gaat een bijzondere magie van de parade uit. Het gevoel van door de straten van Utrecht skaten, omringd door medeliefhebbers en met lekkere beats op de achtergrond, is lastig onder woorden te brengen. Het beste omschrijven kan ik het als een heel relaxte festivalvibe.\u003c/p\u003e\n\u003cp\u003eIk probeer zoveel mogelijk parades bij te wonen. Elke keer word ik gegrepen door dezelfde energie. Ik kan niet anders dan me laten meeslepen door de collectieve geest van honderden skaters die samen bewegen. Het is een krachtige herinnering aan de Joy die inline skaten kan brengen.\u003c/p\u003e\n\u003cblockquote class=\"pull-quote\"\u003e\u003cp\u003eHet is niet alleen een sport, het is een lifestyle.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eIk kan niet wachten op mijn volgende skate. Dit zijn de momenten die mijn reis als inline skater de moeite waard maken. En elke parade, elke tocht door de straten van de stad, brengt me dichter bij de kern van wat inline skaten voor mij betekent: vrijheid, Joy en een gevoel van grenzeloze mogelijkheden.\u003c/p\u003e\n\u003cfigure class=\"embed\"\u003e\u003ciframe src=\"https://www.youtube-nocookie.com/embed/s4xHK8CHRcQ\" title=\"Skateparade 20 augustus door Utrecht\" loading=\"lazy\" allow=\"accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen referrerpolicy=\"strict-origin-when-cross-origin\"\u003e\u003c/iframe\u003e\u003c/figure\u003e\n\n\u003cfigure class=\"embed\"\u003e\u003ciframe src=\"https://www.youtube-nocookie.com/embed/ovyFXJ5YkM0\" title=\"Aktiesport | Summer Event | Utrecht Skate Parade\" loading=\"lazy\" allow=\"accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen referrerpolicy=\"strict-origin-when-cross-origin\"\u003e\u003c/iframe\u003e\u003c/figure\u003e\n\n","date_modified":"2023-10-11T10:32:32+02:00","date_published":"2023-06-23T14:08:23+02:00","id":"https://www.allsubjectsmatter.nl/nl/inline-skating-these-days/","language":"nl","summary":"Iets wat totaal niets met Agile of tech te maken heeft, maar een klein verhaal over hoe het voelt om het inline skaten weer opgepakt te hebben, en over mijn verleden met inline skaten.","tags":["Inline skating"],"title":"Inline skaten tegenwoordig","url":"https://www.allsubjectsmatter.nl/nl/inline-skating-these-days/"},{"authors":[{"name":"Remco Loup","url":"https://www.allsubjectsmatter.nl/author/remco/"}],"content_html":"\u003ch2 id=\"introduction\"\u003eInleiding\u003c/h2\u003e\n\u003cblockquote class=\"pull-quote\"\u003e\u003cp\u003eZonder een \u0026ldquo;goede\u0026rdquo; SME (subject matter expert)-stance is elk product een slechter product dan het kan zijn.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eDat is de bom die ik hier aan het begin laat vallen.\u003c/p\u003e\n\u003cp\u003eEr zitten natuurlijk nuances in deze stelling. Hieronder leg ik uit waarom dit naar mijn mening klopt. Als SME geef je om je product, vind ik. Waarom zou je er anders zoveel van weten? En al die informatie paraat hebben? Ik vergeet dingen waar ik niet om geef\u0026hellip; dus ik moet wel om ons product geven.\u003c/p\u003e\n\u003ch2 id=\"finding-the-nuance-within-a-sme\"\u003e\u003cstrong\u003eDe nuance vinden binnen een SME\u003c/strong\u003e\u003c/h2\u003e\n\u003cp\u003eDe struggle is dat je jezelf moet leren hoe je reageert als je onzeker bent of ernaast zit. Koppig zijn omdat je bang bent om te falen is de verkeerde insteek voor deze stance. Je moet anderen ook de kans geven te leren wat jij misschien al weet. Misschien komen er zelfs nieuwe inzichten naar boven! Een antwoord dicteren zorgt er niet voor dat ze het accepteren; dat doen ze pas als ze er zelf achter komen. En nog belangrijker: het team wordt er niet slimmer van als ze dingen niet zelf uitvogelen. Het hele team moet er beter van worden, niet alleen jij.\u003c/p\u003e\n\u003cp\u003eDat vraagt om tact en het juiste moment om in bepaalde discussies in te grijpen. Je wilt zo\u0026rsquo;n discussie niet leiden, maar hem op een natuurlijke manier de juiste kant op sturen op het moment dat het nodig is. En onthoud: er is altijd nog de mogelijkheid dat jij er inderdaad naast zit!\u003c/p\u003e\n\u003ch2 id=\"a-good-subject-matter-expert\"\u003eEen \u0026ldquo;goede\u0026rdquo; subject matter expert\u003c/h2\u003e\n\u003cp\u003eEen SME zijn is verdomd handig als je de positieve kanten van een subject matter expert goed benut. Daarom is dit voor mij een van de meest interessante en miskende stances van een Product Owner. Je kunt als SME volledig de mist in gaan (miskend) of volledig goed zitten. Op een positieve manier, zonder dat je jezelf elke keer weer met alles bemoeit. En daar moet je de nuance vinden in je rol als Product Owner met een SME-mindset. Als je de details van iets weet, leg dan ook de WAAROM uit aan het team. Blijf ze betrekken in plaats van de wijsneus uit te hangen.\u003c/p\u003e\n\u003ch2 id=\"limitations\"\u003eBeperkingen\u003c/h2\u003e\n\u003cp\u003eHet komt allemaal neer op je beperkingen kennen en daar eerlijk over zijn. Je weet veel, maar niet alles, en de wereld evolueert, dus dingen kunnen inmiddels anders liggen. Voor mij betekent dit dat ik mijn mond houd als ik het niet zeker weet. En soms geef ik mijn visie op waarom we vroeger een bepaalde keuze hebben gemaakt, maar moedig ik iedereen aan om het vandaag de dag opnieuw uit te zoeken, want het probleem van toen is misschien geen probleem meer. En je leert altijd van dit soort dingen, ook al is de uitkomst hetzelfde.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eHet is een goede praktijk als er iemand is die context geeft bij de keuzes uit het verleden, zonder in een bepaalde richting te sturen. Ik denk dat uitleg op deze manier veel waarde heeft.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"an-analogy\"\u003e\u003cstrong\u003eEen analogie\u003c/strong\u003e\u003c/h2\u003e\n\u003cp\u003eHet product dat we als team hebben ontwikkeld begon als mijn kind. De visie kwam van mij. Het duurde twee jaar voordat we bevelden. En na die bevalling van twee jaar hebben we als team de handen ineengeslagen om ons kind zo goed mogelijk op te voeden. Dat gaf veel hoofdbrekens, maar het bracht ons ook zoveel: leren en ons aan elkaar aanpassen. We begonnen allemaal om dit kind te geven.\u003c/p\u003e\n\u003cp\u003eHoe kan ik daar geen SME voor zijn? Ik ben degene die met het oorspronkelijke idee kwam. Ik was erbij toen ons kind na twee jaar intensieve ontwikkeling geboren werd. Ik was er tijdens de hele jeugd bij, zag het opgroeien en volwassen worden. En ook al is het inmiddels volwassen, ons kind heeft nog steeds af en toe sturing en hulp nodig. Het blijft immers mijn kind. Dat er steeds meer mensen echt om dit kind zijn gaan geven, verandert niets aan mijn gevoel voor ons product.\u003c/p\u003e\n\u003cblockquote class=\"pull-quote\"\u003e\u003cp\u003eVerspreid de liefde voor je product over je team. Houd het niet voor jezelf. Maak het als team beter.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"conclusion\"\u003e\u003cstrong\u003eConclusie\u003c/strong\u003e\u003c/h2\u003e\n\u003cp\u003eIk kan er niets aan doen dat ik nog bijna elke keuze herinner die we in het verleden voor ons product hebben gemaakt. En ook waarom we die keuze binnen onze development cycles hebben gemaakt. Ik kies er niet per se voor om die wijsneus te zijn, maar dit onderwerp interesseert me en daarom onthoud ik het. Ik moet anderen wel ruimte geven voor hun eigen gedachten. Dus ja, ik herken mezelf in een subject matter expert, tot op zekere hoogte zelfs volledig.\u003c/p\u003e\n\u003cp\u003eJe moet wel weten/voelen WANNEER je een discussie instapt. Ga niet elke discussie in als de wijsneus. Dit klinkt misschien cocky of arrogant. Maar we willen allemaal hetzelfde, en dat is een beter product afleveren dan het al was. Als we respectvol met elkaar blijven praten zonder de wijsneus uit te hangen, zitten we als team op het juiste spoor.\u003c/p\u003e\n\u003cp\u003eEn weer! Blijf de WAAROM\u0026rsquo;s uitleggen en zeg wat je denkt. Betrek je team bij beslissingen en bij de uitleg van keuzes uit het verleden. Samen vorm je een team met alle mensen, niet alleen als PO/SME.\u003c/p\u003e\n\u003cp\u003e\u003cem\u003eDisclaimer: hoewel ik hier één van de stances van de Scrum Master uitlicht, is het juist de combinatie van alle stances die je een betere Product Owner maakt. Dit is gewoon één stance die mij interesseert, omdat het een vaak verkeerd begrepen stance is (en met een goede reden). Zonder een heldere Vision en zonder de andere stances te \u0026ldquo;zijn\u0026rdquo;, heb je het als Product Owner zwaar voor je kiezen.\u003c/em\u003e\u003c/p\u003e\n","date_modified":"2023-05-03T12:17:32+02:00","date_published":"2023-04-28T13:56:58+02:00","id":"https://www.allsubjectsmatter.nl/nl/scrum-a-understood-subject-matter-expert-sme-misunderstood-stances/","language":"nl","summary":"Zonder een \"goede\" SME (Subject Matter Expert)-rol binnen SCRUM is elk product minder dan het zou kunnen zijn. Daarom is dit voor mij een van de interessantste verkeerd begrepen houdingen van een Product Owner.","tags":["Agile"],"title":"Een begrepen Subject Matter Expert","url":"https://www.allsubjectsmatter.nl/nl/scrum-a-understood-subject-matter-expert-sme-misunderstood-stances/"},{"authors":[{"name":"Remco Loup","url":"https://www.allsubjectsmatter.nl/author/remco/"}],"content_html":"\u003ch2 id=\"introduction\"\u003eIntroductie\u003c/h2\u003e\n\u003cp\u003eIn deze post deel ik de experimenten die we de afgelopen drie sprints hebben gedaan met ons fysieke Scrum board. De focus lag op een beter begrijpelijke visuele weergave van onze sprintvoortgang. Sprints zijn voor ons op dit moment blokken van twee weken.\u003c/p\u003e\n\u003ch2 id=\"our-scrum-board-right-now\"\u003eOns Scrum board zoals het nu is\u003c/h2\u003e\n\u003cp\u003eDe afbeelding hieronder laat het board zien dat we bijna een jaar gebruiken. Toen we Scrum serieuzer gingen nemen, merkten we dat dit board aan transparantie tekortkomt.\u003c/p\u003e\n\u003cp\u003eOp dit board hebben we al veel van de eerdere problemen opgelost. Die verbeteringen leg ik uitgebreid uit in een eerdere post: \u003ca href=\"https://www.allsubjectsmatter.nl/nl/sprints-in-real-life/\" data-preview\u003eOur sprints in real life\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eOm onze reis beter te begrijpen is \u0026ldquo;\u003ca href=\"https://www.allsubjectsmatter.nl/nl/achieving-flow-with-scrum/\" data-preview\u003eAchieving FLOW with Scrum\u003c/a\u003e\u0026rdquo; ook een must-read. Die post duikt in onze achtergrond en, belangrijker nog, beantwoordt de vraag: waarom doen we dit eigenlijk?\u003c/p\u003e\n\u003cfigure class=\"figure\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003csource type=\"image/webp\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/our-journey-into-visual-scrum/images/20230307_125843-2_hu_4a63e1b954b7439e.webp\" alt=\"\" width=\"560\" height=\"632\" data-zoom-src=\"https://www.allsubjectsmatter.nl/our-journey-into-visual-scrum/images/20230307_125843-2_hu_6af505125fa866e7.webp\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003cfigcaption\u003eFase 1, net voor de start van een nieuwe experimenteerfase\u003c/figcaption\u003e\u003c/figure\u003e\n\n\u003cp\u003eDit zijn de lanes en de opzet die we op dit moment gebruiken. De laatste twee (Testing en Done) hebben we gesplitst voor meer transparantie binnen onze huidige CI/CD-opzet. Hieronder een lijst met dingen die we al getest hebben en dus op dit board zijn doorgevoerd. Deze verbeteringen blijven we meenemen bij het experimenteren met nieuwe versies van ons board.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eHet sprintdoel is duidelijk zichtbaar.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003eWe gebruiken groene kaartjes voor de items die bijdragen aan het behalen van het sprintdoel.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003eTaken die te lang in testing staan markeren we met een visueel uitroepteken.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003eIn onze Done-lane gebruiken we \u0026ldquo;released\u0026rdquo; en \u0026ldquo;not yet released\u0026rdquo;, zodat DOD-items die nog niet gedeployd zijn zichtbaar blijven voor het Scrum team.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003eDe volgorde van ons werk staat op het board in de Todo-lane van boven naar onder. Bovenaan betekent eerst, tenzij er een goede reden is om niet in die volgorde te werken :)\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003eAndere taken uit onze sprintcommitment markeren we blauw.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003eWillekeurige bugfixes en mogelijke hotfixes die tijdens een sprint opduiken, markeren we rood. Die gaan onderaan het board. Ze kunnen belangrijk zijn, maar ze kwamen tijdens een sprint naar boven en staan daarom onderaan de \u0026ldquo;TODO\u0026rdquo;-lane.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003eSubtasks worden altijd op dit board gedefinieerd. Ze zijn er voor de developers om het werk te doen waarmee de volledige story wordt opgeleverd. Ze kunnen in Jira staan, maar het fysieke board is leidend.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003eWe gebruiken naamkaartjes om te laten zien wie waaraan werkt.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eDe reden dat we zijn gaan experimenteren met andere boardopzetten, hieronder te vinden, is dat we stories steeds vaker gingen opsplitsen in subtasks. Maar als een developer een subtask afrondde, wilden we die niet direct van het board halen, om een heleboel verschillende redenen.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eEen subtask weghalen zorgt voor minder transparantie en voor minder gesprekken over afgeronde subtasks.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003eHet geeft developers die niet op kantoor waren (vrij) inzicht in afgeronde taken als ze de volgende dag binnenkomen.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote class=\"pull-quote\"\u003e\u003cp\u003eHet belangrijkste punt was het gebrek aan transparantie dat dit board nog steeds had rond subtasks!\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eHet voelt ook bevredigender om een afgeronde subtask naar een vaste plek op het board te verplaatsen. Neerpleuren en verder. Dat geeft een gevoel van afronding. Dat gevoel krijg je niet als je wat je net hebt afgerond direct weghaalt, schoonmaakt en uitwist.\u003c/p\u003e\n\u003cp\u003eIn de eerste versie van ons board hadden we hier geen rekening mee gehouden. Soms gingen subtasks mee met de story. Op andere momenten verschoof een subtask naar de \u0026ldquo;Done\u0026rdquo;-lane. Dat is fout! Er valt niets te releasen of niet te releasen. Het is een afgeronde subtask van een branch die waarschijnlijk nog niet eens gecommit is. Deze situatie zorgde voor veel problemen op het gebied van transparantie.\u003c/p\u003e\n\u003ch2 id=\"first-iteration-changes\"\u003eAanpassingen eerste iteratie\u003c/h2\u003e\n\u003cp\u003eWe zijn gaan experimenteren met het splitsen van de \u0026ldquo;Dev\u0026rdquo;-lane in een \u0026ldquo;Doing\u0026rdquo; en een \u0026ldquo;Done\u0026rdquo;. Zo konden we afgeronde subtasks in een eigen lane houden. Aanvankelijk was er wat irritatie over deze opzet. Maar het was essentieel om het uit te proberen om de effectiviteit te bepalen!\u003c/p\u003e\n\u003cfigure class=\"figure\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003csource type=\"image/webp\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/our-journey-into-visual-scrum/images/20230314_082856_hu_2559b27601cc5669.webp\" alt=\"\" width=\"560\" height=\"640\" data-zoom-src=\"https://www.allsubjectsmatter.nl/our-journey-into-visual-scrum/images/20230314_082856_hu_d520949d7c9945da.webp\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003cfigcaption\u003eFase 2. Experimenteren met een doing/done-lane in Development.\u003c/figcaption\u003e\u003c/figure\u003e\n\n\u003ch2 id=\"first-iteration-conclusion\"\u003eConclusie eerste iteratie\u003c/h2\u003e\n\u003cp\u003eNa twee weken met deze opzet gewerkt te hebben, is onze conclusie dat het nuttig is om een vaste plek te hebben voor \u0026ldquo;Done\u0026rdquo; subtasks. Maar die plek binnen de huidige lanes van het board is niet ideaal. Afgeronde subtasks zouden niet meer zo zichtbaar moeten zijn; nu lijkt het alsof ze nog verder naar rechts moeten.\u003c/p\u003e\n\u003cp\u003eSubtasks zelf gaan bijvoorbeeld niet naar Testing, want ze zijn onderdeel van een story die de waarde vasthoudt. Het is de story die verder beweegt naar Review, Testing en Done. Deze aanpak raden we dus niet aan.\u003c/p\u003e\n\u003cp\u003eWe zijn wel overeengekomen om kaartjes weg te halen als de developers collectief vonden dat het tijd was. Maar ook die methode geeft geen transparantie, omdat er geen duidelijke regel is.\u003c/p\u003e\n\u003ch2 id=\"second-iteration-changes\"\u003eAanpassingen tweede iteratie\u003c/h2\u003e\n\u003cp\u003eSamen kwamen we met een nieuwe versie van het board. Ook deze versie staat op de afbeelding hieronder.\u003c/p\u003e\n\u003cp\u003eWe zijn teruggegaan naar de lanes zoals we die aan het begin van dit experiment hadden. Todo, Dev, Review, Testing en Done. De visuele weergave van Todo hebben we weggehaald en daar staat nu het sprintdoel. We waren het er allemaal over eens dat dit een betere plek is voor het sprintdoel.\u003c/p\u003e\n\u003cfigure class=\"figure\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003csource type=\"image/webp\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/our-journey-into-visual-scrum/images/20230329_121747_hu_733fc66d19cc2329.webp\" alt=\"\" width=\"560\" height=\"1093\" data-zoom-src=\"https://www.allsubjectsmatter.nl/our-journey-into-visual-scrum/images/20230329_121747_hu_93038d66e3d24fd1.webp\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003cfigcaption\u003eFase 3. Het aantal lanes terugbrengen en een \"Burn pile\" introduceren met alle afgeronde subtasks.\u003c/figcaption\u003e\u003c/figure\u003e\n\n\u003cp\u003eWe besloten ook om werk nooit boven de items te plaatsen die het behalen van ons sprint goal vertegenwoordigen. Stel dat we beginnen aan een blauw of rood item. Dat item schuift naar \u0026ldquo;Dev\u0026rdquo;, maar blijft op dezelfde horizontale lijn staan waar het oorspronkelijk lag. Vanaf nu willen we items dus horizontaal uitgelijnd houden terwijl ze door de lanes bewegen. De enige uitzondering is de Done lane, die is verdeeld in secties voor gereleased en nog niet gereleased werk.\u003c/p\u003e\n\u003cp\u003eDe \u0026ldquo;Burn Pile\u0026rdquo; is een naam die we zelf verzonnen hebben voor alle afgeronde subtasks. We wilden voorkomen dat we twee \u0026ldquo;Done\u0026rdquo; lanes kregen. Het is gewoon bevredigend om de subtasks die je net hebt afgerond op deze plek te leggen! Deze aanpak geeft niet alleen een gevoel van afronding, maar laat ook zien wat er allemaal als subtask is afgerond.\u003c/p\u003e\n\u003ch2 id=\"second-iteration-conclusion\"\u003eConclusie tweede iteratie\u003c/h2\u003e\n\u003cp\u003eDit is de eerste iteratie van onze \u0026ldquo;Burn Pile\u0026rdquo;-versie. We hebben al wat andere overwegingen besproken om deze versie uit te proberen.\u003c/p\u003e\n\u003cblockquote class=\"pull-quote\"\u003e\u003cp\u003eMisschien moeten we voor de transparantie een verwijzing naar het story toevoegen. Of bijvoorbeeld dezelfde subtasks van een story bij elkaar groeperen.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eWe leren van deze ervaring door het gewoon in de praktijk te brengen. Het is belangrijk om dingen niet meteen onnodig ingewikkeld te maken. Zoals je op de screenshot ziet, hebben we al wat subtasks in de Burn Pile gelegd, en we zijn het er allemaal over eens: het voelt goed om dat fysiek te doen.\u003c/p\u003e\n\u003ch2 id=\"conclusion\"\u003eConclusie\u003c/h2\u003e\n\u003cp\u003eWe hebben besloten dat we voorlopig met deze versie van het bord verder gaan. De eerste iteratie werkte niet goed: te veel lanes, te veel ruimte, twee Done lanes, en subtasks die na afronding te lang zichtbaar bleven.\u003c/p\u003e\n\u003cp\u003eVanaf nu gebruiken we dus deze versie. Ik blijf deze post updaten telkens als we verbeteringen vinden in ons werk met dit bord.\u003c/p\u003e\n","date_modified":"2023-04-11T14:09:23+02:00","date_published":"2023-03-29T17:16:59+02:00","id":"https://www.allsubjectsmatter.nl/nl/our-journey-into-visual-scrum/","language":"nl","summary":"In deze post deel ik de experimenten die we de afgelopen drie sprints hebben gedaan met ons fysieke Scrum board. De focus lag op een beter begrijpelijke visuele weergave van onze sprintvoortgang. Een sprint is voor ons op dit moment een increment van twee weken.","tags":["Agile"],"title":"Op weg naar visueel Scrum","url":"https://www.allsubjectsmatter.nl/nl/our-journey-into-visual-scrum/"},{"authors":[{"name":"Remco Loup","url":"https://www.allsubjectsmatter.nl/author/remco/"}],"content_html":"\u003ch2 id=\"introduction\"\u003eIntroductie\u003c/h2\u003e\n\u003cp\u003eIk schrijf deze introductie omdat ik wil uitleggen waarom ik ChatGPT gebruik en waarvoor. In het begin was ik een beetje sceptisch over wat ChatGPT was. Voor mij leek het meer een soort fantasie.\u003c/p\u003e\n\u003cp\u003eMaar dat geldt alleen als je vragen stelt waar je zelf weinig kennis van hebt. Daar moeten we heel voorzichtig mee zijn, want je weet niet wanneer het dingen verzint. En ook: de juiste soort vragen stellen helpt ChatGPT om niet zelf dingen te verzinnen.\u003c/p\u003e\n\u003ch2 id=\"practical-examples\"\u003ePraktische voorbeelden\u003c/h2\u003e\n\u003cp\u003eIn ons werk hebben we het bijvoorbeeld getest om introductieteksten te schrijven voor een hotel dat we hadden. ChatGPT verzon van alles, over zwembaden die er zouden zijn en dat soort dingen. Dat hotel heeft helemaal geen zwembad.\u003c/p\u003e\n\u003cp\u003eDus daar is het niet nuttig. Maar over onderwerpen waar je wél verstand van hebt, is het juist wél nuttig. Je kunt de juiste vragen stellen en dan maakt het een tekst die meestal voor minstens 75% bruikbaar is. Dat scheelt tijd.\u003c/p\u003e\n\u003cblockquote class=\"pull-quote\"\u003e\u003cp\u003eChatGPT is voor taal wat de rekenmachine was voor wiskunde.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eChatGPT gaat ons leven een stuk makkelijker maken door informatie te leveren over onderwerpen waar je over wilt schrijven en al kennis van hebt.\u003c/p\u003e\n\u003ch2 id=\"chatgpt-and-allsubjectsmatter\"\u003eChatGPT en AllSubjectsMatter\u003c/h2\u003e\n\u003cp\u003eOp deze website gebruik ik ChatGPT om mijn grammatica en zinsopbouw te corrigeren. Ik ben een geboren Nederlander. En ik zou graag willen opscheppen dat ik vroeger zo goed was in mijn Engelse lessen. Ik deed op school nooit huiswerk, maar haalde toch negens en tienen.\u003c/p\u003e\n\u003cp\u003eIk moet nu toegeven dat dat allemaal flink verroest is. Vooral omdat ik jarenlang niet regelmatig in het Engels heb geschreven, tot nu toe. Dus op dit moment helpt het me om beter leesbare Engelse teksten te schrijven.\u003c/p\u003e\n\u003cp\u003eDit produceerde ChatGPT van de bovenstaande regels.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eI use ChatGPT to correct my grammar and sentence flow, as I am a native Dutch speaker. I would like to brag that I used to excel in my English classes, never completing any homework but still receiving 9\u0026rsquo;s and 10\u0026rsquo;s. However, I must admit that my English has become quite rusty since I haven\u0026rsquo;t written in the language regularly for years. Currently, ChatGPT is helping me improve my ability to write more readable English texts.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eEn dit is dezelfde tekst nadat ik hem heb gecorrigeerd met \u003ca href=\"https://hemingwayapp.com/\" data-external\u003eHemingway\u003c/a\u003e\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cem\u003eI use ChatGPT to correct my grammar and sentence flow, as I am a native Dutch speaker. I would like to brag that I used to excel in my English classes.\u003c/em\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cem\u003eI never did any homework but still received 9\u0026rsquo;s and 10\u0026rsquo;s in school. But I must admit that my English has become quite rusty since I haven\u0026rsquo;t written in the language often for years. For now ChatGPT is helping me improve my ability to write more readable English texts.\u003c/em\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eHet origineel is voor mij al niet slecht, maar dit is gewoon een voorbeeld van wat je kunt doen met tools die helpen bij leesbaarheid.\u003c/p\u003e\n\u003ch2 id=\"what-is-hemingway\"\u003eWat is Hemingway?\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"https://hemingwayapp.com/\" data-external\u003eHemingway\u003c/a\u003e is een visuele editor voor leesbaarheid die je kan helpen om zinnen te vereenvoudigen. Ik gebruik dit de hele tijd om al mijn Engelse teksten te controleren op leesbaarheid. Dit helpt ook om je teksten makkelijker leesbaar te maken.\u003c/p\u003e\n\u003cfigure class=\"figure\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003csource type=\"image/webp\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/chatgpt-and-the-future-of-blogging/images/image-4_hu_d84c0d75ce005095.webp\" alt=\"\" width=\"560\" height=\"295\" data-zoom-src=\"https://www.allsubjectsmatter.nl/chatgpt-and-the-future-of-blogging/images/image-4_hu_7c4c22a11578f1f7.webp\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003c/figure\u003e\n\n\u003ch2 id=\"conclusion\"\u003eConclusie\u003c/h2\u003e\n\u003cp\u003eIk gebruik een van deze twee tools om beter Engels te proberen te schrijven. Soms gebruik ik ze allebei. Hier kan ChatGPT echt schitteren: je schrijven verbeteren. Door ChatGPT te gebruiken begon ik meteen helderder en simpeler te leren schrijven.\u003c/p\u003e\n\u003cp\u003eHopelijk is er dus steeds minder te corrigeren wanneer ik een tekst door Hemingway of ChatGPT haal. Dat komt omdat je zelf ook leert door je teksten door deze tools te halen.\u003c/p\u003e\n\u003cp\u003eIk vind het niets om ChatGPT mijn posts volledig te laten schrijven en ze om te buigen tot iets dat \u0026ldquo;mij\u0026rdquo; vertegenwoordigt, want dat voelt voor mij niet goed. Het idee moet van mij komen en ik gebruik ChatGPT soms om het te polijsten. En niet andersom.\u003c/p\u003e\n","date_modified":"2023-06-23T14:22:45+02:00","date_published":"2023-03-22T20:09:35+01:00","id":"https://www.allsubjectsmatter.nl/nl/chatgpt-and-the-future-of-blogging/","language":"nl","summary":"Ik leg uit hoe ik als Nederlandse moedertaalspreker ChatGPT en Hemingway inzet om grammatica, zinsopbouw en leesbaarheid van mijn Engelse teksten te verbeteren, met als doel een gepolijster en begrijpelijker eindresultaat.","tags":["Tech"],"title":"ChatGPT en de toekomst van bloggen","url":"https://www.allsubjectsmatter.nl/nl/chatgpt-and-the-future-of-blogging/"},{"authors":[{"name":"Remco Loup","url":"https://www.allsubjectsmatter.nl/author/remco/"}],"content_html":"\u003ch2 id=\"update-13-11-2024\"\u003eUpdate (13-11-2024)\u003c/h2\u003e\n\u003cp\u003eOp de een of andere manier was ik de stap kwijt om een nieuw image aan te maken en daar alle builder-items uit te kopiëren. Het veranderde niets aan de werking van het image, maar de grootte is nu veel kleiner: van ruim 2gb naar onder de 100mb. Ergens tijdens mijn eerdere debuggen is die stap verloren gegaan. De laatste update staat hieronder dus opnieuw.\u003c/p\u003e\n\u003cfigure class=\"bookmark\"\u003e\u003ca class=\"bookmark__link\" href=\"https://www.allsubjectsmatter.nl/nl/azure-western-europe-zone-3-latency-issue-exploration/\"\u003e\u003cspan class=\"bookmark__body\"\u003e\u003cspan class=\"bookmark__title\"\u003eLatency Anomaly: An attempt to untangle Azure Performance issues in Western Europe\u003c/span\u003e\u003cspan class=\"bookmark__description\"\u003eUnveiling a significant Azure performance issue: Zone 3’s unexpected latency anomaly in the Western Europe region challenges cloud deployment strategies\u003c/span\u003e\u003cspan class=\"bookmark__meta\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"25px\"\u003e\u003csource type=\"image/webp\" sizes=\"25px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/docker-nginx-http3-and-brotli/bookmarks/01-icon_hu_9b466dd874965fcb.webp\" alt=\"\" class=\"bookmark__icon\" width=\"50\" height=\"50\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003cspan\u003eAll Subjects Matter\u003c/span\u003e\u003cspan\u003eRemco Loup.\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"bookmark__thumb\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/webp\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/avif\" sizes=\"250px\"\u003e\u003csource type=\"image/webp\" sizes=\"250px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/docker-nginx-http3-and-brotli/bookmarks/01-thumb_hu_27e6839ff9d3a962.webp\" alt=\"\" width=\"500\" height=\"435\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003c/span\u003e\u003c/a\u003e\u003c/figure\u003e\n\n\u003ch2 id=\"introduction\"\u003eIntroductie\u003c/h2\u003e\n\u003cp\u003eHieronder vind je een werkende Dockerfile die ik heb gemaakt, met de onderstaande compatibiliteitseisen die ik zelf had. Ik plaats dit hier als eigen archief, en natuurlijk om te delen. Ik heb de volgende ondersteuning toegevoegd.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eHTTP/2 HPACK\u003c/li\u003e\n\u003cli\u003eHTTP/3\u003c/li\u003e\n\u003cli\u003eBoringSSL\u003c/li\u003e\n\u003cli\u003eBrotli-ondersteuning\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eDeze versie van Nginx ondersteunt HTTP/3 quic en Brotli-compressie als belangrijkste voordelen, plus wat andere fixes. Tegenwoordig geen Brotli-ondersteuning hebben is gewoon een stap terug in de DeLorean.\u003c/p\u003e\n\u003cp\u003eMeer info over hoe \u003ca href=\"https://blog.cloudflare.com/hpack-the-silent-killer-feature-of-http-2/\" data-external\u003eHPACK\u003c/a\u003e je HTTP/2 versnelt. En hier lees je meer details over \u003ca href=\"https://blog.ononoki.org/enable-http3-for-nginx/\" data-external\u003eBoringSSL\u003c/a\u003e.\u003c/p\u003e\n\u003ch2 id=\"brotli-compared-to-gzip\"\u003eBrotli vergeleken met GZIP\u003c/h2\u003e\n\u003cp\u003eZoals gezegd is dit een Nginx-versie die Brotli ondersteunt. Brotli is erg nuttig omdat het een beter compressie-algoritme is dan GZIP. Ben je nog niet overtuigd, kijk dan de video hieronder.\u003c/p\u003e\n\u003cfigure class=\"embed\"\u003e\u003ciframe src=\"https://www.youtube-nocookie.com/embed/BMCvMWqoivs\" title=\"How File Compression Increases Your Website Speed: BROTLI vs GZIP Algorithms\" loading=\"lazy\" allow=\"accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen referrerpolicy=\"strict-origin-when-cross-origin\"\u003e\u003c/iframe\u003e\u003c/figure\u003e\n\n\u003ch2 id=\"nginx-base-docker-file\"\u003eNginx basis Dockerfile\u003c/h2\u003e\n\u003cp\u003eDit is de basis van mijn dockerfile die alle bovengenoemde dingen regelt. Je kunt dit natuurlijk aanpassen of andere versies van alpine of Nginx gebruiken, zolang het bouwen van de andere modules maar niet breekt. Ik heb Nginx, NJS en BoringSSL ook naar de nieuwste versies geüpdatet.\u003c/p\u003e\n\u003cp\u003eIk gebruik dit image in plaats van de officiële Nginx om al mijn webprojecten te draaien. Zie dit als een basisimage waarin je al je draaiende webserver-projecten definieert. Je krijgt er \u0026ldquo;gratis\u0026rdquo; Brotli-compressie en HTTP/3-ondersteuning bij, zodat je je sites sneller en actueler kunt maken.\u003c/p\u003e\n\u003cpre class=\"line-numbers language-docker\"\u003e\u003ccode class=\"language-docker\"\u003e##################################################\n# Nginx with Quiche (HTTP/3) and Brotli\n# VERSION 1.05 / 13-11-2024\n##################################################\n# This build compiles Nginx with Brotli and\n# HTTP/3 support. It uses a multi-stage build to\n# minimize the final image size by copying only\n# the necessary artifacts from the builder image.\n# Based on Alpine for a lean and efficient image.\n##################################################\n\n# Builder Stage\nFROM alpine:latest AS builder\n\n# Set the versions of Nginx, NJS, and BoringSSL\nENV NGINX_VERSION=1.27.2\nENV NJS_VERSION=0.8.7\nENV BORINGSSL_VERSION=c59bf8bf189dcbde868e04efcd53b705ed155231\nENV BORINGSSL=\u0026amp;#34;/tmp/boring-nginx\u0026amp;#34;\n\n# Build-time metadata\nARG BUILD_DATE\nARG VCS_REF\n\n# Install dependencies for building Nginx\nRUN addgroup -S nginx \\\n  \u0026amp;amp;\u0026amp;amp; adduser -D -S -h /var/cache/nginx -s /sbin/nologin -G nginx nginx \\\n  \u0026amp;amp;\u0026amp;amp; apk update \\\n  \u0026amp;amp;\u0026amp;amp; apk upgrade \\\n  \u0026amp;amp;\u0026amp;amp; apk add --no-cache ca-certificates openssl \\\n  \u0026amp;amp;\u0026amp;amp; update-ca-certificates \\\n  \u0026amp;amp;\u0026amp;amp; apk add --no-cache --virtual .build-deps \\\n      gcc libc-dev make pcre-dev zlib-dev linux-headers gnupg \\\n      libxslt-dev gd-dev geoip-dev perl-dev \\\n  \u0026amp;amp;\u0026amp;amp; apk add --no-cache --virtual .brotli-build-deps \\\n      autoconf libtool automake git g\u0026#43;\u0026#43; cmake go perl rust cargo patch \\\n      libxml2-dev byacc flex libstdc\u0026#43;\u0026#43; libmaxminddb-dev lmdb-dev file openrc\n\n# Install and build BoringSSL\nRUN mkdir $BORINGSSL \\\n  \u0026amp;amp;\u0026amp;amp; cd $BORINGSSL \\\n  \u0026amp;amp;\u0026amp;amp; git clone https://boringssl.googlesource.com/boringssl \\\n  \u0026amp;amp;\u0026amp;amp; cd boringssl \\\n  \u0026amp;amp;\u0026amp;amp; git checkout -q ${BORINGSSL_VERSION} \\\n  \u0026amp;amp;\u0026amp;amp; mkdir build \\\n  \u0026amp;amp;\u0026amp;amp; cd build \\\n  \u0026amp;amp;\u0026amp;amp; cmake -DBUILD_SHARED_LIBS=1 .. \\\n  \u0026amp;amp;\u0026amp;amp; make\n\n# Prepare BoringSSL for Nginx compilation\nRUN mkdir -p \u0026amp;#34;$BORINGSSL/boringssl/.openssl/lib\u0026amp;#34; \\\n  \u0026amp;amp;\u0026amp;amp; cd \u0026amp;#34;$BORINGSSL/boringssl/.openssl\u0026amp;#34; \\\n  \u0026amp;amp;\u0026amp;amp; ln -s ../include include \\\n  \u0026amp;amp;\u0026amp;amp; cd \u0026amp;#34;$BORINGSSL/boringssl\u0026amp;#34; \\\n  \u0026amp;amp;\u0026amp;amp; cp \u0026amp;#34;build/crypto/libcrypto.so\u0026amp;#34; \u0026amp;#34;.openssl/lib\u0026amp;#34; \\\n  \u0026amp;amp;\u0026amp;amp; cp \u0026amp;#34;build/ssl/libssl.so\u0026amp;#34; \u0026amp;#34;.openssl/lib\u0026amp;#34; \\\n  \u0026amp;amp;\u0026amp;amp; cp \u0026amp;#34;.openssl/lib/libssl.so\u0026amp;#34; /usr/lib/ \\\n  \u0026amp;amp;\u0026amp;amp; cp \u0026amp;#34;.openssl/lib/libcrypto.so\u0026amp;#34; /usr/lib/\n\n# Clone Nginx modules\nRUN mkdir /usr/src \\\n  \u0026amp;amp;\u0026amp;amp; cd /usr/src \\\n  \u0026amp;amp;\u0026amp;amp; git clone --depth=1 --recursive --shallow-submodules https://github.com/google/ngx_brotli \\\n  \u0026amp;amp;\u0026amp;amp; git clone --branch $NJS_VERSION --depth=1 --recursive --shallow-submodules https://github.com/nginx/njs\n\n# Build Nginx with modules\nRUN cd /usr/src \\\n  \u0026amp;amp;\u0026amp;amp; wget -qO nginx.tar.gz https://nginx.org/download/nginx-$NGINX_VERSION.tar.gz \\\n  \u0026amp;amp;\u0026amp;amp; tar -zxC /usr/src -f nginx.tar.gz \\\n  \u0026amp;amp;\u0026amp;amp; rm nginx.tar.gz \\\n  \u0026amp;amp;\u0026amp;amp; cd /usr/src/nginx-$NGINX_VERSION \\\n  \u0026amp;amp;\u0026amp;amp; mkdir /root/.cargo \\\n  \u0026amp;amp;\u0026amp;amp; echo $\u0026amp;#39;[net]\\ngit-fetch-with-cli = true\u0026amp;#39; \u0026amp;gt; /root/.cargo/config.toml \\\n  \u0026amp;amp;\u0026amp;amp; ./configure --prefix=/etc/nginx \\\n      --sbin-path=/usr/sbin/nginx \\\n      --modules-path=/usr/lib/nginx/modules \\\n      --conf-path=/etc/nginx/nginx.conf \\\n      --error-log-path=/var/log/nginx/error.log \\\n      --http-log-path=/var/log/nginx/access.log \\\n      --pid-path=/var/run/nginx.pid \\\n      --lock-path=/var/run/nginx.lock \\\n      --http-client-body-temp-path=/var/cache/nginx/client_temp \\\n      --http-proxy-temp-path=/var/cache/nginx/proxy_temp \\\n      --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp \\\n      --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp \\\n      --http-scgi-temp-path=/var/cache/nginx/scgi_temp \\\n      --user=nginx \\\n      --group=nginx \\\n      --with-pcre-jit \\\n      --with-http_ssl_module \\\n      --with-http_realip_module \\\n      --with-http_addition_module \\\n      --with-http_sub_module \\\n      --with-http_dav_module \\\n      --with-http_flv_module \\\n      --with-http_mp4_module \\\n      --with-http_gunzip_module \\\n      --with-http_gzip_static_module \\\n      --with-http_random_index_module \\\n      --with-http_secure_link_module \\\n      --with-http_stub_status_module \\\n      --with-http_auth_request_module \\\n      --with-http_xslt_module=dynamic \\\n      --with-http_image_filter_module=dynamic \\\n      --with-http_geoip_module=dynamic \\\n      --with-http_perl_module=dynamic \\\n      --with-threads \\\n      --with-stream \\\n      --with-stream_ssl_module \\\n      --with-stream_ssl_preread_module \\\n      --with-stream_realip_module \\\n      --with-stream_geoip_module=dynamic \\\n      --with-http_slice_module \\\n      --with-mail \\\n      --with-mail_ssl_module \\\n      --with-compat \\\n      --with-file-aio \\\n      --with-http_v2_module \\\n      --with-http_v3_module \\\n      --add-module=/usr/src/ngx_brotli \\\n      --add-module=/usr/src/njs/nginx \\\n      --with-cc-opt=\u0026amp;#34;-I$BORINGSSL/boringssl/include -Wno-error\u0026amp;#34; \\\n      --with-ld-opt=\u0026amp;#34;-L$BORINGSSL/boringssl/build/ssl -L$BORINGSSL/boringssl/build/crypto\u0026amp;#34; \\\n      --with-select_module \\\n      --with-poll_module \\\n      --build=\u0026amp;#34;docker-nginx-http3-$VCS_REF-$BUILD_DATE ngx_brotli-$(git --git-dir=/usr/src/ngx_brotli/.git rev-parse --short HEAD) njs-$(git --git-dir=/usr/src/njs/.git rev-parse --short HEAD)\u0026amp;#34; \\\n  \u0026amp;amp;\u0026amp;amp; touch \u0026amp;#34;$BORINGSSL/boringssl/.openssl/include/openssl/ssl.h\u0026amp;#34; \\\n  \u0026amp;amp;\u0026amp;amp; make -j$(getconf _NPROCESSORS_ONLN) \\\n  \u0026amp;amp;\u0026amp;amp; make install \\\n  \u0026amp;amp;\u0026amp;amp; rm -rf /etc/nginx/html/ \\\n  \u0026amp;amp;\u0026amp;amp; mkdir /etc/nginx/conf.d/ \\\n  \u0026amp;amp;\u0026amp;amp; mkdir -p /usr/share/nginx/html/ \\\n  \u0026amp;amp;\u0026amp;amp; install -m644 html/index.html /usr/share/nginx/html/ \\\n  \u0026amp;amp;\u0026amp;amp; install -m644 html/50x.html /usr/share/nginx/html/ \\\n  \u0026amp;amp;\u0026amp;amp; ln -s /usr/lib/nginx/modules /etc/nginx/modules \\\n  \u0026amp;amp;\u0026amp;amp; strip /usr/sbin/nginx* \\\n  \u0026amp;amp;\u0026amp;amp; strip /usr/lib/nginx/modules/*.so \\\n  \u0026amp;amp;\u0026amp;amp; rm -rf /etc/nginx/*.default /etc/nginx/*.so \\\n  \u0026amp;amp;\u0026amp;amp; rm -rf /usr/src \\\n  \u0026amp;amp;\u0026amp;amp; apk add --no-cache --virtual .gettext gettext \\\n  \u0026amp;amp;\u0026amp;amp; mv /usr/bin/envsubst /tmp/ \\\n  \\\n  # Determine runtime dependencies\n  \u0026amp;amp;\u0026amp;amp; runDeps=\u0026amp;#34;$( \\\n      scanelf --needed --nobanner /usr/sbin/nginx /usr/lib/nginx/modules/*.so /tmp/envsubst \\\n      | awk \u0026amp;#39;{ gsub(/,/, \u0026amp;#34;\\nso:\u0026amp;#34;, $2); print \u0026amp;#34;so:\u0026amp;#34; $2 }\u0026amp;#39; \\\n      | sort -u \\\n      | xargs -r apk info --installed \\\n      | sort -u \\\n    )\u0026amp;#34; \\\n  \u0026amp;amp;\u0026amp;amp; echo \u0026amp;#34;$runDeps\u0026amp;#34; \u0026amp;gt; /nginx-run-deps.txt \\\n  \\\n  # Cleanup\n  \u0026amp;amp;\u0026amp;amp; apk del .brotli-build-deps .build-deps .gettext \\\n  \u0026amp;amp;\u0026amp;amp; rm -rf /root/.cargo \\\n  \u0026amp;amp;\u0026amp;amp; rm -rf /var/cache/apk/* \\\n  \u0026amp;amp;\u0026amp;amp; mv /tmp/envsubst /usr/local/bin/ \\\n  \u0026amp;amp;\u0026amp;amp; mkdir -p /etc/ssl/private \\\n  \u0026amp;amp;\u0026amp;amp; openssl req -x509 -newkey rsa:4096 -nodes -keyout /etc/ssl/private/localhost.key -out /etc/ssl/localhost.pem -days 365 -sha256 -subj \u0026amp;#39;/CN=localhost\u0026amp;#39; \\\n  \u0026amp;amp;\u0026amp;amp; ln -sf /dev/stdout /var/log/nginx/access.log \\\n  \u0026amp;amp;\u0026amp;amp; ln -sf /dev/stderr /var/log/nginx/error.log\n\n# Final Stage\nFROM alpine:latest\n\n# Copy Nginx and related files from the builder stage\nCOPY --from=builder /usr/sbin/nginx /usr/sbin/nginx\nCOPY --from=builder /etc/nginx /etc/nginx\nCOPY --from=builder /usr/lib/nginx /usr/lib/nginx\nCOPY --from=builder /usr/share/nginx /usr/share/nginx\nCOPY --from=builder /usr/local/bin/envsubst /usr/local/bin/envsubst\nCOPY --from=builder /var/log/nginx /var/log/nginx\nCOPY --from=builder /var/cache/nginx /var/cache/nginx\nCOPY --from=builder /var/run /var/run\nCOPY --from=builder /etc/ssl /etc/ssl\nCOPY --from=builder /usr/lib/libssl.so* /usr/lib/\nCOPY --from=builder /usr/lib/libcrypto.so* /usr/lib/\nCOPY --from=builder /nginx-run-deps.txt /nginx-run-deps.txt\n\n# Install runtime dependencies\nRUN apk add --no-cache ca-certificates openssl libstdc\u0026#43;\u0026#43; libgcc \\\n  \u0026amp;amp;\u0026amp;amp; apk add --no-cache $(cat /nginx-run-deps.txt) \\\n  \u0026amp;amp;\u0026amp;amp; rm /nginx-run-deps.txt \\\n  \u0026amp;amp;\u0026amp;amp; update-ca-certificates \\\n  \\\n  # Forward logs to Docker\n  \u0026amp;amp;\u0026amp;amp; ln -sf /dev/stdout /var/log/nginx/access.log \\\n  \u0026amp;amp;\u0026amp;amp; ln -sf /dev/stderr /var/log/nginx/error.log\n\n# Create nginx user and group\nRUN addgroup -S nginx \\\n  \u0026amp;amp;\u0026amp;amp; adduser -D -S -h /var/cache/nginx -s /sbin/nologin -G nginx nginx\n\n# Expose ports\nEXPOSE 80 443\n\nSTOPSIGNAL SIGTERM\n\nCMD [\u0026amp;#34;nginx\u0026amp;#34;, \u0026amp;#34;-g\u0026amp;#34;, \u0026amp;#34;daemon off;\u0026amp;#34;]\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003ch2 id=\"nginxconf-sample\"\u003eNginx.conf-voorbeeld\u003c/h2\u003e\n\u003cp\u003eHier vind je een voorbeeld van een nginx.conf die je een idee geeft hoe ik mijn nginx-omgeving draai.\u003c/p\u003e\n\u003cp\u003eHet belangrijkste verschil in dit bestand ten opzichte van de normale nginx.conf zijn de brotli-compressie-instellingen.\u003c/p\u003e\n\u003cpre class=\"line-numbers language-nginx\"\u003e\u003ccode class=\"language-nginx\"\u003euser nginx;\nworker_processes auto;\n\nerror_log  /var/log/nginx/error.log notice;\npid        /var/run/nginx.pid;\n\ninclude /etc/nginx/modules-enabled/*.conf;\n\nevents {\n\tworker_connections 1024;\n}\n\nhttp {\n\t##\n\t# Basic Settings\n\t##\n\tsendfile on;\n\ttcp_nopush on;\n\ttcp_nodelay on;\n\tkeepalive_timeout 65;\n\ttypes_hash_max_size 2048;\n\n\tproxy_connect_timeout  300s;\n\tproxy_send_timeout  300s;\n\tproxy_read_timeout  300s;\n\tfastcgi_send_timeout 300s;\n\tfastcgi_read_timeout 300s;\n\n\tinclude /etc/nginx/mime.types;\n\tdefault_type application/octet-stream;\n\n\t##\n\t# SSL Settings\n\t##\n\tssl_protocols TLSv1.2 TLSv1.3;\n\tssl_prefer_server_ciphers on;\n\n\t##\n\t# Logging Settings\n\t##\n    log_format  main  \u0026amp;#39;$remote_addr - $remote_user [$time_local] \u0026amp;#34;$request\u0026amp;#34; \u0026amp;#39;\n                      \u0026amp;#39;$status $body_bytes_sent \u0026amp;#34;$http_referer\u0026amp;#34; \u0026amp;#39;\n                      \u0026amp;#39;\u0026amp;#34;$http_user_agent\u0026amp;#34; \u0026amp;#34;$http_x_forwarded_for\u0026amp;#34;\u0026amp;#39;;\n\n    access_log  /var/log/nginx/access.log  main;\n\n\t##\n\t# Gzip Settings\n\t##\n\tgzip on;\n\tgzip_vary on;\n\tgzip_proxied any;\n\tgzip_comp_level 6;\n\tgzip_buffers 16 8k;\n\tgzip_http_version 1.1;\n\tgzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml\u0026#43;rss text/javascript;\n\n\t##\n\t# Brotli Settings (modify the brotli_comp_level to match your optimal cpu load vs compression)\n\t##\n\tbrotli on;\n\tbrotli_static on;\n\tbrotli_comp_level 11;\n\tbrotli_types\n\ttext/plain\n\ttext/css\n\ttext/xml\n\ttext/javascript\n\ttext/x-component\n\tapplication/xml\n\tapplication/xml\u0026#43;rss\n\tapplication/javascript\n\tapplication/json\n\tapplication/atom\u0026#43;xml\n\tapplication/vnd.ms-fontobject\n\tapplication/x-font-ttf\n\tapplication/x-font-opentype\n\tapplication/x-font-truetype\n\tapplication/x-web-app-manifest\u0026#43;json\n\tapplication/xhtml\u0026#43;xml\n\tapplication/octet-stream\n\tfont/opentype\n\tfont/truetype\n\tfont/eot\n\tfont/otf\n\tfont/woff\n\tfont/woff2\n\timage/svg\u0026#43;xml\n\timage/x-icon\n\timage/vnd.microsoft.icon\n\timage/bmp;\n\n\t##\n\t# Virtual Host Configs\n\t##\n\tinclude /etc/nginx/conf.d/*.conf;\n}\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003ch2 id=\"siteconf-sample\"\u003eSite.conf-voorbeeld\u003c/h2\u003e\n\u003cp\u003eLet op: hoe hoger je de brotli-compressie instelt, hoe cpu-intensiever het wordt. TTFB (time to first byte) kan een probleem worden als de compressieratio te hoog staat in combinatie met een trage kubernetes-omgeving, hoge loads, of alles daartussenin. Test dus met realistische scenario\u0026rsquo;s en pas het daarop aan. Dit is niet alleen een Brotli-probleem; Gzip heeft dezelfde nadelen als je de compressieniveaus te hoog zet, terwijl de afname in bestandsgrootte het niet waard is.\u003c/p\u003e\n\u003cp\u003eJe kunt dit bestand gebruiken om alle HTTP/3-instellingen hieronder over te nemen. Dit optimaliseert en activeert HTTP/3-ondersteuning voor je site.\u003c/p\u003e\n\u003cpre class=\"line-numbers language-nginx\"\u003e\u003ccode class=\"language-nginx\"\u003eserver {\n       \t# HTTP/3 and Quic Listen\n      \tlisten 443 quic reuseport;\n        http3 on;\n\n        # HTTP/3 Add Alt-Svc header negotiation.\n        add_header alt-svc \u0026amp;#39;h2=\u0026amp;#34;:443\u0026amp;#34;; ma=86400, h3-29=\u0026amp;#34;:443\u0026amp;#34;; ma=86400, h3=\u0026amp;#34;:443\u0026amp;#34;; ma=86400\u0026amp;#39; always;\n        \n        # HTTP/2 Listen\n        listen 443 ssl http2;\n        http2 on;\n\n        # Certificate locations for HTTPS.\n\t    ssl_certificate /etc/ssl/certs/wildcard-cert-23.crt;\n        ssl_certificate_key /etc/ssl/private/wildcard-cert-23.key;\n\n        # Enable SSL settings for performance.\n        ssl_session_cache shared:SSL:50m;\n        ssl_session_timeout 1d;\n        ssl_session_tickets off;\n        # Enable SSL settings for security.\n        ssl_prefer_server_ciphers on;\n        ssl_protocols TLSv1.3;\n        ssl_ciphers HIGH:!aNULL:!MD5;\n\n        # Base settings (root and hostname).\n        root /var/www/html;\n        index index.php index.html index.htm;\n        server_name kubernetes.servername.nl;\n\n        # HTTP/2 settings\n        http2_push_preload on;\n        # /End of HTTP/2.\n\n        # HTTP/3 settings\n        # Enable TLSv1.3\u0026amp;#39;s 0-RTT. Use $ssl_early_data when reverse proxying to\n        # prevent replay attacks.\n        # @see: http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_early_data\n        ssl_early_data on;\n        quic_retry on;\n        quic_gso on;\n        quic_host_key /etc/ssl/private/wildcard-cert-23.key;\n        # /End of HTTP/3.\n\n        location / {\n                try_files $uri $uri/ /index.php?$query_string;\n\t    }\n\n        location ~ \\.php$ {\n                try_files $uri =404;\n                # Bunch of PHP and FPM settings\n                fastcgi_split_path_info ^(.\u0026#43;\\.php)(/.\u0026#43;)$;\n                fastcgi_pass your-php-fpm-instance:9000;\n                fastcgi_index index.php;\n                include fastcgi_params;\n                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n                fastcgi_param PATH_INFO $fastcgi_path_info\n                \n                # Add a bunch of other headers based on your desired settings.\n                add_header X-protocol $server_protocol always;   \n                add_header X-Cache $upstream_cache_status always;\n                add_header X-Frame-Options SAMEORIGIN always;\n                add_header X-Content-Type-Options nosniff always;\n                add_header X-XSS-Protection \u0026amp;#34;1; mode=block\u0026amp;#34;;\n                add_header Strict-Transport-Security \u0026amp;#34;max-age=31536000; includeSubdomains; preload\u0026amp;#34; always;\n                add_header Referrer-Policy strict-origin-when-cross-origin;\n                add_header Pragma public;\n                add_header Cache-Control \u0026amp;#34;public\u0026amp;#34;;\n        }\n}\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003ch2 id=\"multiple-h3-connections-from-a-single-nginx\"\u003eMeerdere H3-verbindingen vanaf één Nginx\u003c/h2\u003e\n\u003cp\u003eHier leg ik uit hoe je meerdere server blocks binnen Nginx met HTTP/3 kunt laten werken. Er zitten wat haken en ogen aan, omdat http3 UDP gebruikt met de instelling reuseport, waardoor je poort 443 niet meerdere keren kunt toewijzen bij gebruik van HTTP/3.\u003c/p\u003e\n\u003cpre class=\"line-numbers language-nginx\"\u003e\u003ccode class=\"language-nginx\"\u003eadd_header alt-svc \u0026amp;#39;h2=\u0026amp;#34;:443\u0026amp;#34;; ma=86400, h3-29=\u0026amp;#34;:443\u0026amp;#34;; ma=86400, h3=\u0026amp;#34;:443\u0026amp;#34;; ma=86400\u0026amp;#39; always;\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cp\u003eDit deel van de header meldt dat onze HTTP/3-verbinding poort 443 accepteert. Heb je een enkele nginx-container met meerdere geconfigureerde server blocks, dan krijg je problemen met het hergebruik van poort 443.\u003c/p\u003e\n\u003cp\u003eMet HTTP/3 kun je een poort maar één keer definiëren binnen een server block. Heb je meerdere server blocks, dan kun je je website via andere poorten dan 443 laten communiceren met behulp van deze alt-svc-header, die de browser vertelt dat hij HTTP/3 accepteert en op welke poort.\u003c/p\u003e\n\u003cpre class=\"line-numbers language-nginx\"\u003e\u003ccode class=\"language-nginx\"\u003eadd_header alt-svc \u0026amp;#39;h2=\u0026amp;#34;:443\u0026amp;#34;; ma=86400, h3-29=\u0026amp;#34;:443\u0026amp;#34;; ma=86400, h3=\u0026amp;#34;:443\u0026amp;#34;; ma=86400\u0026amp;#39; always;\n\nadd_header alt-svc \u0026amp;#39;h2=\u0026amp;#34;:443\u0026amp;#34;; ma=86400, h3-29=\u0026amp;#34;:444\u0026amp;#34;; ma=86400, h3=\u0026amp;#34;:444\u0026amp;#34;; ma=86400\u0026amp;#39; always;\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cp\u003eLet op de verschillen in de h3-poorten hier. Zo kun je meerdere server blocks h3 laten gebruiken door unieke poorten op te geven.\u003c/p\u003e\n\u003cp\u003eHet enige wat je hoeft te doen is je instance naar de juiste poort laten luisteren die in je alt-svc-header staat. Als er een succesvolle verbinding mogelijk is, zal de browser proberen over te stappen op h3. Hieronder een voorbeeld van hoe je kunt luisteren op poort 443 en 444 met HTTP/3.\u003c/p\u003e\n\u003cpre class=\"line-numbers language-nginx\"\u003e\u003ccode class=\"language-nginx\"\u003eserver {\n\n        server_name http3-port-443\n\n       \t# HTTP/3 and Quic Listen\n      \tlisten 443 quic reuseport;\n        http3 on;\n\n        # HTTP/3 Add Alt-Svc header negotiation.\n        add_header alt-svc \u0026amp;#39;h2=\u0026amp;#34;:443\u0026amp;#34;; ma=86400, h3-29=\u0026amp;#34;:443\u0026amp;#34;; ma=86400, h3=\u0026amp;#34;:443\u0026amp;#34;; ma=86400\u0026amp;#39; always;\n        \n        # HTTP/2 Listen\n        listen 443 ssl http2;\n        http2 on;\n        \n        # HTTP/3 settings\n        # Enable TLSv1.3\u0026amp;#39;s 0-RTT. Use $ssl_early_data when reverse proxying to\n        # prevent replay attacks.\n        # @see: http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_early_data\n        ssl_early_data on;\n        quic_retry on;\n        quic_gso on;\n        quic_host_key /etc/ssl/private/wildcard-cert-23.key;\n        # /End of HTTP/3.\n\n        location / {\n                try_files $uri $uri/ /index.php?$query_string;\n\t}\n}\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cpre class=\"line-numbers language-nginx\"\u003e\u003ccode class=\"language-nginx\"\u003eserver {\n\n        server_name http3-port-444\n\n       \t# HTTP/3 and Quic Listen\n      \tlisten 444 quic reuseport;\n        http3 on;\n\n        # HTTP/3 Add Alt-Svc header negotiation.\n        add_header alt-svc \u0026amp;#39;h2=\u0026amp;#34;:443\u0026amp;#34;; ma=86400, h3-29=\u0026amp;#34;:444\u0026amp;#34;; ma=86400, h3=\u0026amp;#34;:444\u0026amp;#34;; ma=86400\u0026amp;#39; always;\n        \n        # HTTP/2 Listen\n        listen 443 ssl http2;\n        http2 on;\n        \n        # HTTP/3 settings\n        # Enable TLSv1.3\u0026amp;#39;s 0-RTT. Use $ssl_early_data when reverse proxying to\n        # prevent replay attacks.\n        # @see: http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_early_data\n        ssl_early_data on;\n        quic_retry on;\n        quic_gso on;\n        quic_host_key /etc/ssl/private/wildcard-cert-23.key;\n        # /End of HTTP/3.\n\n        location / {\n                try_files $uri $uri/ /index.php?$query_string;\n\t}\n}\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cp\u003eZo eenvoudig is het om je reverse proxy of meerdere server blocks een H3-verbinding te laten opzetten. Je moet wel de juiste port forwards instellen in je firewall/netwerk.\u003c/p\u003e\n\u003cp\u003eMet docker configureer je dit zoals hieronder weergegeven. Bij het opgeven van UDP moet je in docker elke poort apart specificeren. Hieronder mijn voorbeeld met UDP-poorten 443 tot en met 449.\u003c/p\u003e\n\u003cpre class=\"line-numbers language-yaml\"\u003e\u003ccode class=\"language-yaml\"\u003eservices:\n  ############################################################\n  # proxy webservice to connect to multiple nginx instances\n  ############################################################\n  reverse-proxy:\n    image: custom-build-nginx:latest\n    restart: unless-stopped\n    ports:\n      - \u0026amp;#39;80:80\u0026amp;#39;\n      - \u0026amp;#39;443:443\u0026amp;#39;\n      - \u0026amp;#39;443:443/udp\u0026amp;#39;\n      - \u0026amp;#39;444:444/udp\u0026amp;#39;\n      - \u0026amp;#39;445:445/udp\u0026amp;#39;\n      - \u0026amp;#39;446:446/udp\u0026amp;#39;\n      - \u0026amp;#39;447:447/udp\u0026amp;#39;\n      - \u0026amp;#39;448:448/udp\u0026amp;#39;\n      - \u0026amp;#39;449:449/udp\u0026amp;#39;\n    volumes:\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003ch2 id=\"update-03-04-2024\"\u003eUpdate (03-04-2024)\u003c/h2\u003e\n\u003cp\u003eWat versienummers toegevoegd aan mijn dockerfile om versies beter te kunnen vergelijken. Ik heb de build van gisteren ook opgeschoond en besloten om een ENV toe te voegen voor het instellen van de boringssl-versie. Omdat hun build mijn dockerfile kapotmaakte, updaten we die maar handmatig. Verder heb ik nginx geüpdatet naar 1.25.4 en NJS naar 0.8.3 en de build geverifieerd.\u003c/p\u003e\n\u003ch2 id=\"update-02-04-2024\"\u003eUpdate (02-04-2024)\u003c/h2\u003e\n\u003cp\u003ePas recent kwam ik erachter dat een update van BoringSSL van rond 28 februari mijn build van BoringSSL had gebroken. Hieronder de bijgewerkte dockerfile die weer compileert. Ik moest libssl als shared library bouwen om het weer te laten werken. Ik ga dit image nog wat verder opruimen, maar het werkt in elk geval weer.\u003c/p\u003e\n\u003ch2 id=\"update-18-03-2024\"\u003eUpdate (18-03-2024)\u003c/h2\u003e\n\u003cp\u003eIk heb een sectie toegevoegd die uitlegt hoe je meerdere H3-verbindingen kunt gebruiken als je een Nginx hebt met meer dan één server block. Dit is ook de sleutel als je Nginx als Reverse Proxy gebruikt en gebruik wilt maken van H3.\u003c/p\u003e\n\u003ch2 id=\"update-04-09-2023\"\u003eUpdate (04-09-2023)\u003c/h2\u003e\n\u003cp\u003eIk kwam erachter dat deze build niet meer compileerde door een verandering in de brotli-broncode? Het had misschien te maken met updates van GitHub die ik niet had vastgelegd in deze Dockerfile. Ik heb het bestand volledig omgebouwd zodat het werkt met de bijgewerkte Nginx 1.25.2, die HTTP/3-ondersteuning \u0026ldquo;ingebouwd\u0026rdquo; heeft. Ook heb ik ervoor gezorgd dat we weer native Brotli-ondersteuning hebben. Dit zou vanaf nu moeten blijven werken.\u003c/p\u003e\n\u003ch2 id=\"update-30-08-2023\"\u003eUpdate (30-08-2023)\u003c/h2\u003e\n\u003cp\u003eIk heb de modsec-ondersteuning die er eerst in zat verwijderd. Die gaf willekeurige errors en omdat ik hem toch niet gebruik, heb ik hem eruit gehaald. Misschien komt hij later terug.\u003c/p\u003e\n","date_modified":"2024-11-13T12:22:12+01:00","date_published":"2023-03-20T11:20:00+01:00","id":"https://www.allsubjectsmatter.nl/nl/docker-nginx-http3-and-brotli/","language":"nl","summary":"Hier vind je een werkende Dockerfile die ik heb gemaakt als basis voor mijn projecten. Dit is een recente Nginx met gecompileerde HTTP/3-ondersteuning en Brotli-ondersteuning voor betere compressie. Ik plaats het hier om het met iedereen te delen.","tags":["Tech","AI and LM"],"title":"Docker Nginx HTTP/3 en Brotli","url":"https://www.allsubjectsmatter.nl/nl/docker-nginx-http3-and-brotli/"},{"authors":[{"name":"Remco Loup","url":"https://www.allsubjectsmatter.nl/author/remco/"}],"content_html":"\u003ch2 id=\"introduction\"\u003eInleiding\u003c/h2\u003e\n\u003cp\u003eIk heb lang getwijfeld om een blog te starten. Niet zomaar om een blog te hebben, maar om mijn kijk op bepaalde onderwerpen te delen met wie er iets aan heeft. Dit is mijn eerste poging om uit te leggen hoe ik denk en werk.\u003c/p\u003e\n\u003cp\u003eHet idee om uit te leggen hoe wij bij 333travel FLOW bereiken, ontstond tijdens het lezen van het boek \u003ca href=\"https://www.goodreads.com/book/show/59691869-applying-scrum-with-kanban\" data-external\u003e\u003cem\u003eApplying Scrum with kanban\u003c/em\u003e\u003c/a\u003e. Terwijl ik las, merkte ik dat veel van de ideeën in dit boek dingen waren die we al deden. Dat gaf me de moed om dit vanuit mijn eigen perspectief te schrijven.\u003c/p\u003e\n\u003cp\u003eIk schrijf deze post als ik halverwege het boek ben. Op het punt waar Katie met haar team, waarin ze Scrum Master is, positieve FLOW aan het ontdekken is. Toen het boek over FLOW begon, werd er diep vanbinnen iets bij me getriggerd.\u003c/p\u003e\n\u003cp\u003eIk kreeg kippenvel bij het lezen van dat hoofdstuk, omdat ik me realiseerde dat we veel van de ideeën uit het boek al hadden geïmplementeerd. Zonder te weten wat Scrum of Kanban was, in mijn begindagen. Gewoon door te proberen wat aanvoelde als een natuurlijke en goede manier van werken, al terug in 2017 met agile methoden.\u003c/p\u003e\n\u003ch2 id=\"outcomes\"\u003eResultaten\u003c/h2\u003e\n\u003cp\u003eWe hebben sindsdien veel vooruitgang geboekt. Ik kan nu al zeggen dat we veel van de uitkomsten die het boek beschrijft in de praktijk brengen.\u003c/p\u003e\n\u003cp\u003eWe begonnen in 2017 met Jira, met een aangepaste versie van swim lanes.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eEen board met swim lanes die de onderwerpen van issues dekken.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003eSommige swim lanes geschrapt, omdat onderhoud beheersbaar moet blijven en er een doel moet zijn om ze te gebruiken.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eIn de jaren daarna, toen we ons way of working echt wilden professionaliseren, kwamen de volgende onderwerpen onze workflow binnen. Deze inzichten staan in willekeurige volgorde.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eEen definition of done (DOD) schrijven voor elke status van de swim lanes.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003eHet sprintdoel zichtbaar op ons fysieke whiteboard.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003eEen fysiek whiteboard gebruiken dat de single source of truth is, boven Jira.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003eSpecifieke kleuren op de stories die we in een sprint oppakken, met een speciale kleur (groen) voor de stories die het meest bijdragen aan het sprintdoel.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003eSpecifieke kleuren voor andere taken, hotfixes en dergelijke.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003eBlocked is geen status waar issues kunnen blijven staan. Geblokkeerde issues moeten zo snel mogelijk gedeblokkeerd worden, of teruggedraaid nadat we hebben vastgesteld dat we niet verder kunnen.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003eWerken vanuit het gevoel dat we elke taak/story die door de lanes zwemt als team helemaal naar rechts moeten afmaken, voordat we besluiten nieuw werk (stories) te starten.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eVoor een visueel beeld van ons huidige Scrum board verwijs ik je naar de link aan het einde van deze post.\u003c/p\u003e\n\u003ch2 id=\"get-a-flow-going\"\u003eZet een FLOW op gang!\u003c/h2\u003e\n\u003cp\u003eWaarom dit me zo raakte, hield me lang bezig. Als ik er nu over nadenk, begrijp ik dat dit de katalysatoren waren die onze flow op gang brachten. Wat ik in mijn team zag, was dat we pas echt gingen samenwerken toen mensen écht samen gingen werken. (In de zin van: aan dezelfde stories werken en de subtasks daarbinnen oppakken.) Pas toen gingen de gesprekken over de stories waaraan we werken echt open.\u003c/p\u003e\n\u003cp\u003eMaar om dat te kunnen doen, moesten we eerst een bepaalde manier van werken creëren, met alle bovenstaande punten als basis voor transparantie en focus.\u003c/p\u003e\n\u003cblockquote class=\"pull-quote\"\u003e\u003cp\u003eScrum is een framework dat gesprekken faciliteert. Om een flow op gang te brengen moet je samenwerken. Aan dezelfde stories werken met alle developers richting het sprintdoel. Idealiter maak je iets af voordat je aan iets nieuws begint.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eDevelopers proberen echt samenwerken vaak te vermijden, omdat ze denken dat het meer werk oplevert. Ze splitsen stories liever verder op, zodat ze solo aan stories kunnen werken. In plaats van aan subtasks binnen dezelfde story. Deze kleine gedragsverandering vraagt wat gewenning van developers. Maar als ze als team gaan opereren, kunnen ze in een staat van positieve flow komen waarin alles werkt.\u003c/p\u003e\n\u003cp\u003eAls je dit solo Scrum binnen een sprint blijft doen, verlies je veel van de voordelen die Scrum kan bieden. Je kunt natuurlijk zeggen dat je Scrum doet. Maar wil je zeggen dat je Scrum doet? Of wil je samen in een epische flow komen en de manier waarop je werkt voor altijd veranderen! (wie kan het schelen hoe het heet, toch?).\u003c/p\u003e\n\u003cp\u003eHet is ons standaardgedrag om binnen een sprint eerst te werken aan taken die we al het beste kennen (dan ben je sneller klaar, toch?). Ik daag mijn developers ook uit om te werken aan dingen waar ze binnen onze projecten nog niet aan gewerkt hebben. We moeten als Scrum team beter worden. Dat gebeurt niet als we in onze eigen kennisbubbels blijven werken. Developers moeten uitgedaagd worden om samen richting het productdoel te werken. Dat geeft elke developer meer autonomie en zelfvertrouwen.\u003c/p\u003e\n\u003cblockquote class=\"pull-quote\"\u003e\u003cp\u003eScrum gaat niet over de snelheid van development. Dat is slechts een bijkomstig voordeel als je met je team een bepaalde staat van flow bereikt.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"instill-a-vision\"\u003eEen visie instillen\u003c/h2\u003e\n\u003cp\u003eEen visie neerzetten is ook cruciaal voor FLOW. Een idee hebben van waarom je dit product bouwt, is een van de belangrijkste dingen om mee te beginnen. Een productvisie of doel en een duidelijk sprint goal om vanuit te starten zijn een must. Je moet je doel kunnen uitleggen. Zo weet je waar we aan werken en wat we daarna moeten doen om dat doel te halen.\u003c/p\u003e\n\u003cblockquote class=\"pull-quote\"\u003e\u003cp\u003eHet definiëren van technische schuld hangt sterk samen met je productvisie/-doel. Daardoor kun je de huidige staat van je product vergelijken met waar je het wil hebben. En welke technische schuld we moeten oplossen om daar te komen.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eEen productvisie is niet concreet, en hij hoeft ook niet lang te zijn. Hij evolueert met de tijd, naarmate je meer ontdekt over wat het product gaat doen en wat de markt wil. Maar het begint allemaal met een visie waar je naartoe werkt. Er moet iets zijn dat het WAAROM uitlegt.\u003c/p\u003e\n\u003ch2 id=\"conclusion\"\u003eConclusie\u003c/h2\u003e\n\u003cp\u003eIk hoop dat ik een paar nuttige inzichten heb gedeeld over hoe wij FLOW hebben weten te bereiken in ons Scrum team. We krassen volgens mij nog maar aan de oppervlakte, en er moet in ons team nog veel meer geoptimaliseerd worden. Maar we zitten zeker op het juiste spoor.\u003c/p\u003e\n\u003cp\u003eZeker omdat ons hele Scrum team het gevoel heeft dat we in een FLOW opereren. En dat is een verdraaid goed gevoel om in te werken!\u003c/p\u003e\n\u003cfigure class=\"bookmark\"\u003e\u003ca class=\"bookmark__link\" href=\"https://www.allsubjectsmatter.nl/nl/sprints-in-real-life/\"\u003e\u003cspan class=\"bookmark__body\"\u003e\u003cspan class=\"bookmark__title\"\u003eOur sprints in a real life example\u003c/span\u003e\u003cspan class=\"bookmark__description\"\u003eThis post explains how we as a team manage our current Board on which we keep our sprints. This board is leading over Jira we love working analog next to our digital work.\u003c/span\u003e\u003cspan class=\"bookmark__meta\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"25px\"\u003e\u003csource type=\"image/webp\" sizes=\"25px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/achieving-flow-with-scrum/bookmarks/01-icon_hu_9b466dd874965fcb.webp\" alt=\"\" class=\"bookmark__icon\" width=\"50\" height=\"50\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003cspan\u003eAll Subjects Matter\u003c/span\u003e\u003cspan\u003eRemco\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"bookmark__thumb\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/webp\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/avif\" sizes=\"250px\"\u003e\u003csource type=\"image/webp\" sizes=\"250px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/achieving-flow-with-scrum/bookmarks/01-thumb_hu_12ce7537b849d91a.webp\" alt=\"\" width=\"500\" height=\"435\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003c/span\u003e\u003c/a\u003e\u003c/figure\u003e\n\n","date_modified":"2024-02-16T08:45:37+01:00","date_published":"2023-03-07T10:20:56+01:00","id":"https://www.allsubjectsmatter.nl/nl/achieving-flow-with-scrum/","language":"nl","summary":"In deze post deel ik hoe we er bij mijn team nu mee bezig zijn om Scrum- en Kanban-principes te implementeren. Het gaat vooral om het belang van echte samenwerking en het neerzetten van een productvisie, die ons helpt om FLOW te bereiken. Dat vertaalt zich voor ons naar Scrum met hoge waarde.","tags":["Agile"],"title":"FLOW bereiken met Scrum","url":"https://www.allsubjectsmatter.nl/nl/achieving-flow-with-scrum/"},{"authors":[{"name":"Remco Loup","url":"https://www.allsubjectsmatter.nl/author/remco/"}],"content_html":"\u003ch2 id=\"introduction\"\u003eIntroductie\u003c/h2\u003e\n\u003cp\u003eIn deze post leg ik mijn persoonlijke setup uit: hoe ik mijn Windows Terminal gebruik, plus wat andere tips. Dus als je iemand wilt imponeren terwijl je werkt in je favoriete koffiezaak, door ze het gevoel te geven dat je voor Nasa werkt — dan zit je hier goed! Grapjes terzijde: als je dag in dag uit met iets werkt, mag het er op z\u0026rsquo;n minst een beetje mooi uitzien, toch?\u003c/p\u003e\n\u003cp\u003eIk begin met een lijst van alles wat ik geïnstalleerd heb, daarna leg ik uit wat het allemaal doet.\u003c/p\u003e\n\u003ch2 id=\"zsh-and-the-p10k-theme\"\u003eZSH en het p10k-thema\u003c/h2\u003e\n\u003cfigure class=\"bookmark\"\u003e\u003ca class=\"bookmark__link\" href=\"https://github.com/ohmyzsh/ohmyzsh\" rel=\"noopener\"\u003e\u003cspan class=\"bookmark__body\"\u003e\u003cspan class=\"bookmark__title\"\u003eGitHub - ohmyzsh/ohmyzsh: 🙃 A delightful community-driven (with 2,100\u0026#43; contributors) framework for managing your zsh configuration. Includes 300\u0026#43; optional plugins (rails, git, macOS, hub, docker, homebrew, node, php, python, etc), 140\u0026#43; themes to spice up your morning, and an auto-update tool so that makes it easy to keep up with the latest updates from the community.\u003c/span\u003e\u003cspan class=\"bookmark__description\"\u003e🙃 A delightful community-driven (with 2,100\u0026#43; contributors) framework for managing your zsh configuration. Includes 300\u0026#43; optional plugins (rails, git, macOS, hub, docker, homebrew, node, php, pyth…\u003c/span\u003e\u003cspan class=\"bookmark__meta\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"25px\"\u003e\u003csource type=\"image/webp\" sizes=\"25px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/the-ultimate-zsh-and-windows-terminal-look-and-feel/bookmarks/01-icon_hu_a8d8b8bb716d7df8.webp\" alt=\"\" class=\"bookmark__icon\" width=\"50\" height=\"50\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003cspan\u003eGitHub\u003c/span\u003e\u003cspan\u003eohmyzsh\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"bookmark__thumb\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/webp\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/avif\" sizes=\"250px\"\u003e\u003csource type=\"image/webp\" sizes=\"250px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/the-ultimate-zsh-and-windows-terminal-look-and-feel/bookmarks/01-thumb_hu_cd46afdcd6f277f0.webp\" alt=\"\" width=\"500\" height=\"435\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003c/span\u003e\u003c/a\u003e\u003c/figure\u003e\n\n\u003cfigure class=\"bookmark\"\u003e\u003ca class=\"bookmark__link\" href=\"https://github.com/romkatv/powerlevel10k\" rel=\"noopener\"\u003e\u003cspan class=\"bookmark__body\"\u003e\u003cspan class=\"bookmark__title\"\u003eGitHub - romkatv/powerlevel10k: A Zsh theme\u003c/span\u003e\u003cspan class=\"bookmark__description\"\u003eA Zsh theme. Contribute to romkatv/powerlevel10k development by creating an account on GitHub.\u003c/span\u003e\u003cspan class=\"bookmark__meta\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"25px\"\u003e\u003csource type=\"image/webp\" sizes=\"25px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/the-ultimate-zsh-and-windows-terminal-look-and-feel/bookmarks/02-icon_hu_a8d8b8bb716d7df8.webp\" alt=\"\" class=\"bookmark__icon\" width=\"50\" height=\"50\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003cspan\u003eGitHub\u003c/span\u003e\u003cspan\u003eromkatv\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"bookmark__thumb\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/webp\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/avif\" sizes=\"250px\"\u003e\u003csource type=\"image/webp\" sizes=\"250px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/the-ultimate-zsh-and-windows-terminal-look-and-feel/bookmarks/02-thumb_hu_e0ce4be87804921b.webp\" alt=\"\" width=\"500\" height=\"435\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003c/span\u003e\u003c/a\u003e\u003c/figure\u003e\n\n\u003cfigure class=\"bookmark\"\u003e\u003ca class=\"bookmark__link\" href=\"https://spacevim.org/quick-start-guide/\" rel=\"noopener\"\u003e\u003cspan class=\"bookmark__body\"\u003e\u003cspan class=\"bookmark__title\"\u003eQuick start guide | SpaceVim\u003c/span\u003e\u003cspan class=\"bookmark__description\"\u003eA quick start guide which will tell you how to install and configure SpaceVim, also provides a list of resources for learning SpaceVim.\u003c/span\u003e\u003cspan class=\"bookmark__meta\"\u003e\u003cspan\u003eSpaceVim\u003c/span\u003e\u003cspan\u003eShidong Wang\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"bookmark__thumb\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/webp\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/avif\" sizes=\"250px\"\u003e\u003csource type=\"image/webp\" sizes=\"250px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/the-ultimate-zsh-and-windows-terminal-look-and-feel/bookmarks/03-thumb_hu_fd18ea080be99d12.webp\" alt=\"\" width=\"500\" height=\"435\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003c/span\u003e\u003c/a\u003e\u003c/figure\u003e\n\n\u003cp\u003eDe bovenstaande items heb ik geïnstalleerd voor de look and feel van mijn shell. Ik gebruik Windows Terminal en daardoor ziet mijn terminal er goed uit en worden de juiste gegevens onderscheiden door kleuren in de shell.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eohmyzsh\u003c/strong\u003e is het framework waarmee ik mijn ZSH-configuratie beheer. Het thema dat ik geïnstalleerd heb is p10k. Dit thema is enorm flexibel aan te passen.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eSpaceVim\u003c/strong\u003e is een van de beste visuele editors om alles in Linux te bewerken wat je nodig hebt. Kijk zelf maar via de link.\u003c/p\u003e\n\u003cp\u003eHier een screenshot van hoe mijn persoonlijke shell eruitziet. Ik deel hieronder ook de JSON-config van mijn Windows Terminal. Het font dat ik gebruik is de standaard nerd font die bij p10k zit. Dat is de Meslo Gorgeous monospace Nerd font.\u003c/p\u003e\n\u003cfigure class=\"figure\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003csource type=\"image/webp\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/the-ultimate-zsh-and-windows-terminal-look-and-feel/images/image-1_hu_98a6094c3d3897a5.webp\" alt=\"\" width=\"560\" height=\"342\" data-zoom-src=\"https://www.allsubjectsmatter.nl/the-ultimate-zsh-and-windows-terminal-look-and-feel/images/image-1_hu_f6fef50f1a2399a5.webp\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003c/figure\u003e\n\n\u003cp\u003eDe kleuren die ik gebruik heten Atom One Dark. Een subtiel pastelkleurig font.\u003c/p\u003e\n\u003cfigure class=\"figure\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003csource type=\"image/webp\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/the-ultimate-zsh-and-windows-terminal-look-and-feel/images/image_hu_6020c119054673fb.webp\" alt=\"\" width=\"400\" height=\"70\" data-zoom-src=\"https://www.allsubjectsmatter.nl/the-ultimate-zsh-and-windows-terminal-look-and-feel/images/image_hu_6020c119054673fb.webp\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003c/figure\u003e\n\n\u003cp\u003eIn het JSON-bestand hieronder vind je een paar leuke extra\u0026rsquo;s om je ervaring nog verder aan te passen.\u003c/p\u003e\n\u003ch2 id=\"kali-linux\"\u003eKali Linux\u003c/h2\u003e\n\u003cp\u003eIk heb Kali Linux geïnstalleerd als seamless overlay. Dat betekent dat je Kali visueel native binnen Windows kunt draaien. Zie de screenshot hieronder om te zien hoe netjes dat eruitziet! Via de link hieronder lees je hoe je dit met Kali aan de praat krijgt.\u003c/p\u003e\n\u003cfigure class=\"bookmark\"\u003e\u003ca class=\"bookmark__link\" href=\"https://www.kali.org/docs/wsl/win-kex/\" rel=\"noopener\"\u003e\u003cspan class=\"bookmark__body\"\u003e\u003cspan class=\"bookmark__title\"\u003eWin-KeX | Kali Linux Documentation\u003c/span\u003e\u003cspan class=\"bookmark__description\"\u003eWindows Subsystem for Linux 2 \u0026amp; Win-KeX\u003c/span\u003e\u003cspan class=\"bookmark__meta\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"25px\"\u003e\u003csource type=\"image/webp\" sizes=\"25px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/the-ultimate-zsh-and-windows-terminal-look-and-feel/bookmarks/04-icon_hu_5cd2a9b225aaa8ff.webp\" alt=\"\" class=\"bookmark__icon\" width=\"32\" height=\"32\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003cspan\u003eKali Linux\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"bookmark__thumb\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/the-ultimate-zsh-and-windows-terminal-look-and-feel/bookmarks/04-thumb.svg\" sizes=\"250px\" alt=\"\" loading=\"lazy\" decoding=\"async\"\u003e\u003c/span\u003e\u003c/a\u003e\u003c/figure\u003e\n\n\u003cpre class=\"line-numbers language-json\"\u003e\u003ccode class=\"language-json\"\u003e\u0026amp;#34;commandline\u0026amp;#34;: \u0026amp;#34;wsl -d kali-linux kex --sl --wtstart -s\u0026amp;#34;,\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cfigure class=\"figure\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003csource type=\"image/webp\" sizes=\"(max-width: 47.99rem) calc(100vw - 2.5rem), 720px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/the-ultimate-zsh-and-windows-terminal-look-and-feel/images/image-2_hu_727fbce17435cc82.webp\" alt=\"\" width=\"560\" height=\"351\" data-zoom-src=\"https://www.allsubjectsmatter.nl/the-ultimate-zsh-and-windows-terminal-look-and-feel/images/image-2_hu_b5bbf743fe36003f.webp\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003cfigcaption\u003eNaadloze integratie met Kali en Windows\u003c/figcaption\u003e\u003c/figure\u003e\n\n\u003ch2 id=\"external-ssh-in-windows-terminal\"\u003eExterne SSH in Windows Terminal\u003c/h2\u003e\n\u003cp\u003eJe kunt met Windows Terminal ook makkelijk inloggen op (externe) servers die tokens vereisen.\u003c/p\u003e\n\u003cpre class=\"line-numbers language-json\"\u003e\u003ccode class=\"language-json\"\u003e\u0026amp;#34;commandline\u0026amp;#34;: \u0026amp;#34;ssh -i \\\u0026amp;#34;~/.ssh/id_rsa\\\u0026amp;#34; username@ip-address\u0026amp;#34;,\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003cp\u003eKijk via de link hieronder hoe je dit configureert.\u003c/p\u003e\n\u003cfigure class=\"bookmark\"\u003e\u003ca class=\"bookmark__link\" href=\"https://blog.anurut.com/ssh-with-private-key-in-windows-terminal/\" rel=\"noopener\"\u003e\u003cspan class=\"bookmark__body\"\u003e\u003cspan class=\"bookmark__title\"\u003eHow to SSH into a remote server using private key and Windows Terminal\u003c/span\u003e\u003cspan class=\"bookmark__description\"\u003eSSH into a remote computer in just one click using a private key and Windows Terminal | Super easy.\u003c/span\u003e\u003cspan class=\"bookmark__meta\"\u003e\u003cspan\u003e./anurut\u0026#39;s-blog\u003c/span\u003e\u003cspan\u003eAnurut\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/a\u003e\u003c/figure\u003e\n\n\u003ch2 id=\"windows-terminal-config\"\u003eWindows Terminal-config\u003c/h2\u003e\n\u003cp\u003eHier is de JSON voor de Windows Terminal, met als laatste deel het kleurenthema dat ik gebruik, en de instellingen voor de look and feel. Er staan ook voorbeelden in voor het seamless draaien van Kali en een SSH-sessie met tokens.\u003c/p\u003e\n\u003cpre class=\"line-numbers language-json\"\u003e\u003ccode class=\"language-json\"\u003e    \u0026amp;#34;profiles\u0026amp;#34;: \n    {\n        \u0026amp;#34;defaults\u0026amp;#34;: \n        {\n            \u0026amp;#34;antialiasingMode\u0026amp;#34;: \u0026amp;#34;cleartype\u0026amp;#34;,\n            \u0026amp;#34;backgroundImage\u0026amp;#34;: null,\n            \u0026amp;#34;backgroundImageAlignment\u0026amp;#34;: \u0026amp;#34;bottom\u0026amp;#34;,\n            \u0026amp;#34;backgroundImageOpacity\u0026amp;#34;: 0.8,\n            \u0026amp;#34;colorScheme\u0026amp;#34;: \u0026amp;#34;Atom One Dark\u0026amp;#34;,\n            \u0026amp;#34;cursorShape\u0026amp;#34;: \u0026amp;#34;vintage\u0026amp;#34;,\n            \u0026amp;#34;experimental.retroTerminalEffect\u0026amp;#34;: false,\n            \u0026amp;#34;font\u0026amp;#34;: \n            {\n                \u0026amp;#34;face\u0026amp;#34;: \u0026amp;#34;MesloLGS NF\u0026amp;#34;\n            },\n            \u0026amp;#34;opacity\u0026amp;#34;: 75,\n            \u0026amp;#34;useAcrylic\u0026amp;#34;: true\n        },\n        \u0026amp;#34;list\u0026amp;#34;: \n        [\n            {\n                \u0026amp;#34;commandline\u0026amp;#34;: \u0026amp;#34;wsl -d kali-linux kex --sl --wtstart -s\u0026amp;#34;,\n                \u0026amp;#34;guid\u0026amp;#34;: \u0026amp;#34;{55ca431a-3a87-5fb3-83cd-11ececc031d2}\u0026amp;#34;,\n                \u0026amp;#34;hidden\u0026amp;#34;: false,\n                \u0026amp;#34;icon\u0026amp;#34;: \u0026amp;#34;C:\\\\Users\\\\icons\\\\kali-menu.png\u0026amp;#34;,\n                \u0026amp;#34;name\u0026amp;#34;: \u0026amp;#34;Kali Seamless overlay\u0026amp;#34;,\n                \u0026amp;#34;startingDirectory\u0026amp;#34;: \u0026amp;#34;%USERPROFILE%\u0026amp;#34;\n            },\n            {\n                \u0026amp;#34;commandline\u0026amp;#34;: \u0026amp;#34;ssh -i \\\u0026amp;#34;~/.ssh/id_rsa\\\u0026amp;#34; username@ip-address\u0026amp;#34;,\n                \u0026amp;#34;guid\u0026amp;#34;: \u0026amp;#34;{0caa0dad-35be-5f56-a8ff-afceaeaa6103}\u0026amp;#34;,\n                \u0026amp;#34;hidden\u0026amp;#34;: false,\n                \u0026amp;#34;name\u0026amp;#34;: \u0026amp;#34;External test server\u0026amp;#34;\n            }\n        ]\n    },\n    \u0026amp;#34;schemes\u0026amp;#34;: \n    [\n        {\n            \u0026amp;#34;background\u0026amp;#34;: \u0026amp;#34;#282C34\u0026amp;#34;,\n            \u0026amp;#34;black\u0026amp;#34;: \u0026amp;#34;#000000\u0026amp;#34;,\n            \u0026amp;#34;blue\u0026amp;#34;: \u0026amp;#34;#61AFEF\u0026amp;#34;,\n            \u0026amp;#34;brightBlack\u0026amp;#34;: \u0026amp;#34;#5C6370\u0026amp;#34;,\n            \u0026amp;#34;brightBlue\u0026amp;#34;: \u0026amp;#34;#61AFEF\u0026amp;#34;,\n            \u0026amp;#34;brightCyan\u0026amp;#34;: \u0026amp;#34;#56B6C2\u0026amp;#34;,\n            \u0026amp;#34;brightGreen\u0026amp;#34;: \u0026amp;#34;#98C379\u0026amp;#34;,\n            \u0026amp;#34;brightPurple\u0026amp;#34;: \u0026amp;#34;#C678DD\u0026amp;#34;,\n            \u0026amp;#34;brightRed\u0026amp;#34;: \u0026amp;#34;#E06C75\u0026amp;#34;,\n            \u0026amp;#34;brightWhite\u0026amp;#34;: \u0026amp;#34;#FFFFFF\u0026amp;#34;,\n            \u0026amp;#34;brightYellow\u0026amp;#34;: \u0026amp;#34;#D19A66\u0026amp;#34;,\n            \u0026amp;#34;cursorColor\u0026amp;#34;: \u0026amp;#34;#FFFFFF\u0026amp;#34;,\n            \u0026amp;#34;cyan\u0026amp;#34;: \u0026amp;#34;#56B6C2\u0026amp;#34;,\n            \u0026amp;#34;foreground\u0026amp;#34;: \u0026amp;#34;#CCCCCC\u0026amp;#34;,\n            \u0026amp;#34;green\u0026amp;#34;: \u0026amp;#34;#98C379\u0026amp;#34;,\n            \u0026amp;#34;name\u0026amp;#34;: \u0026amp;#34;Atom One Dark\u0026amp;#34;,\n            \u0026amp;#34;purple\u0026amp;#34;: \u0026amp;#34;#C678DD\u0026amp;#34;,\n            \u0026amp;#34;red\u0026amp;#34;: \u0026amp;#34;#E06C75\u0026amp;#34;,\n            \u0026amp;#34;selectionBackground\u0026amp;#34;: \u0026amp;#34;#FFFFFF\u0026amp;#34;,\n            \u0026amp;#34;white\u0026amp;#34;: \u0026amp;#34;#ABB2BF\u0026amp;#34;,\n            \u0026amp;#34;yellow\u0026amp;#34;: \u0026amp;#34;#D19A66\u0026amp;#34;\n        }\n    ],\n    \u0026amp;#34;themes\u0026amp;#34;: [],\n    \u0026amp;#34;useAcrylicInTabRow\u0026amp;#34;: true\u003c/code\u003e\u003cspan class=\"line-numbers-rows\" aria-hidden=\"true\"\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003c/span\u003e\u003c/pre\u003e\u003ch2 id=\"addins-which-make-your-shell-look-and-function-even-better\"\u003eAddins die je shell er nog beter uit laten zien en nog beter laten werken\u003c/h2\u003e\n\u003cp\u003eHieronder vind je nog wat links die je shell-ervaring nog beter maken. Het is een verzameling tools die ik dagelijks gebruik. Aan de bookmarks kun je al zien wat ze doen. Ik zou aanraden om elke link door te lezen en te bepalen of dit ook je leven makkelijker maakt.\u003c/p\u003e\n\u003cp\u003eZelf vind ik Exa het leukst. Dit is een vervanging voor het \u0026rsquo;ls\u0026rsquo;-commando, met veel meer zichtbaarheid dankzij kleuren en iconen voor mappen en dergelijke. Ook htop gebruik ik al jaren als mijn go-to process manager.\u003c/p\u003e\n\u003cfigure class=\"bookmark\"\u003e\u003ca class=\"bookmark__link\" href=\"https://github.com/BurntSushi/ripgrep\" rel=\"noopener\"\u003e\u003cspan class=\"bookmark__body\"\u003e\u003cspan class=\"bookmark__title\"\u003eGitHub - BurntSushi/ripgrep: ripgrep recursively searches directories for a regex pattern while respecting your gitignore\u003c/span\u003e\u003cspan class=\"bookmark__description\"\u003eripgrep recursively searches directories for a regex pattern while respecting your gitignore - GitHub - BurntSushi/ripgrep: ripgrep recursively searches directories for a regex pattern while respec…\u003c/span\u003e\u003cspan class=\"bookmark__meta\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"25px\"\u003e\u003csource type=\"image/webp\" sizes=\"25px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/the-ultimate-zsh-and-windows-terminal-look-and-feel/bookmarks/06-icon_hu_a8d8b8bb716d7df8.webp\" alt=\"\" class=\"bookmark__icon\" width=\"50\" height=\"50\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003cspan\u003eGitHub\u003c/span\u003e\u003cspan\u003eBurntSushi\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"bookmark__thumb\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/webp\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/avif\" sizes=\"250px\"\u003e\u003csource type=\"image/webp\" sizes=\"250px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/the-ultimate-zsh-and-windows-terminal-look-and-feel/bookmarks/06-thumb_hu_b211a5b1ef787f02.webp\" alt=\"\" width=\"500\" height=\"435\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003c/span\u003e\u003c/a\u003e\u003c/figure\u003e\n\n\u003cfigure class=\"bookmark\"\u003e\u003ca class=\"bookmark__link\" href=\"https://github.com/nvbn/thefuck\" rel=\"noopener\"\u003e\u003cspan class=\"bookmark__body\"\u003e\u003cspan class=\"bookmark__title\"\u003eGitHub - nvbn/thefuck: Magnificent app which corrects your previous console command.\u003c/span\u003e\u003cspan class=\"bookmark__description\"\u003eMagnificent app which corrects your previous console command. - GitHub - nvbn/thefuck: Magnificent app which corrects your previous console command.\u003c/span\u003e\u003cspan class=\"bookmark__meta\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"25px\"\u003e\u003csource type=\"image/webp\" sizes=\"25px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/the-ultimate-zsh-and-windows-terminal-look-and-feel/bookmarks/07-icon_hu_a8d8b8bb716d7df8.webp\" alt=\"\" class=\"bookmark__icon\" width=\"50\" height=\"50\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003cspan\u003eGitHub\u003c/span\u003e\u003cspan\u003envbn\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"bookmark__thumb\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/webp\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/avif\" sizes=\"250px\"\u003e\u003csource type=\"image/webp\" sizes=\"250px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/the-ultimate-zsh-and-windows-terminal-look-and-feel/bookmarks/07-thumb_hu_b2b48ab666833f4.webp\" alt=\"\" width=\"500\" height=\"435\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003c/span\u003e\u003c/a\u003e\u003c/figure\u003e\n\n\u003cfigure class=\"bookmark\"\u003e\u003ca class=\"bookmark__link\" href=\"https://github.com/rupa/z\" rel=\"noopener\"\u003e\u003cspan class=\"bookmark__body\"\u003e\u003cspan class=\"bookmark__title\"\u003eGitHub - rupa/z: z - jump around\u003c/span\u003e\u003cspan class=\"bookmark__description\"\u003ez - jump around. Contribute to rupa/z development by creating an account on GitHub.\u003c/span\u003e\u003cspan class=\"bookmark__meta\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"25px\"\u003e\u003csource type=\"image/webp\" sizes=\"25px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/the-ultimate-zsh-and-windows-terminal-look-and-feel/bookmarks/08-icon_hu_a8d8b8bb716d7df8.webp\" alt=\"\" class=\"bookmark__icon\" width=\"50\" height=\"50\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003cspan\u003eGitHub\u003c/span\u003e\u003cspan\u003erupa\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"bookmark__thumb\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/webp\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/avif\" sizes=\"250px\"\u003e\u003csource type=\"image/webp\" sizes=\"250px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/the-ultimate-zsh-and-windows-terminal-look-and-feel/bookmarks/08-thumb_hu_4400b46cb8d5382e.webp\" alt=\"\" width=\"500\" height=\"435\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003c/span\u003e\u003c/a\u003e\u003c/figure\u003e\n\n\u003cfigure class=\"bookmark\"\u003e\u003ca class=\"bookmark__link\" href=\"https://github.com/ogham/exa\" rel=\"noopener\"\u003e\u003cspan class=\"bookmark__body\"\u003e\u003cspan class=\"bookmark__title\"\u003eGitHub - ogham/exa: A modern replacement for ‘ls’.\u003c/span\u003e\u003cspan class=\"bookmark__description\"\u003eA modern replacement for ‘ls’. Contribute to ogham/exa development by creating an account on GitHub.\u003c/span\u003e\u003cspan class=\"bookmark__meta\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" sizes=\"25px\"\u003e\u003csource type=\"image/webp\" sizes=\"25px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/the-ultimate-zsh-and-windows-terminal-look-and-feel/bookmarks/09-icon_hu_a8d8b8bb716d7df8.webp\" alt=\"\" class=\"bookmark__icon\" width=\"50\" height=\"50\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003cspan\u003eGitHub\u003c/span\u003e\u003cspan\u003eogham\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"bookmark__thumb\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/webp\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/avif\" sizes=\"250px\"\u003e\u003csource type=\"image/webp\" sizes=\"250px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/the-ultimate-zsh-and-windows-terminal-look-and-feel/bookmarks/09-thumb_hu_67f6b8479bab6f37.webp\" alt=\"\" width=\"500\" height=\"435\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003c/span\u003e\u003c/a\u003e\u003c/figure\u003e\n\n\u003cfigure class=\"bookmark\"\u003e\u003ca class=\"bookmark__link\" href=\"https://htop.dev/\" rel=\"noopener\"\u003e\u003cspan class=\"bookmark__body\"\u003e\u003cspan class=\"bookmark__title\"\u003ehtop - an interactive process viewer\u003c/span\u003e\u003cspan class=\"bookmark__description\"\u003e\u003c/span\u003e\u003cspan class=\"bookmark__meta\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/the-ultimate-zsh-and-windows-terminal-look-and-feel/bookmarks/10-icon.ico\" sizes=\"25px\" alt=\"\" class=\"bookmark__icon\" loading=\"lazy\" decoding=\"async\"\u003e\u003cspan\u003ean interactive process viewer\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"bookmark__thumb\"\u003e\u003cpicture\u003e\u003csource type=\"image/avif\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/webp\" media=\"(max-width: 47.99rem)\" sizes=\"calc(100vw - 2.5rem)\"\u003e\u003csource type=\"image/avif\" sizes=\"250px\"\u003e\u003csource type=\"image/webp\" sizes=\"250px\"\u003e\u003cimg src=\"https://www.allsubjectsmatter.nl/the-ultimate-zsh-and-windows-terminal-look-and-feel/bookmarks/10-thumb_hu_745f32083f045968.webp\" alt=\"\" width=\"500\" height=\"435\" loading=\"lazy\" decoding=\"async\"\u003e\n  \u003c/picture\u003e\u003c/span\u003e\u003c/a\u003e\u003c/figure\u003e\n\n","date_modified":"2023-03-20T11:13:48+01:00","date_published":"2023-02-08T10:57:00+01:00","id":"https://www.allsubjectsmatter.nl/nl/the-ultimate-zsh-and-windows-terminal-look-and-feel/","language":"nl","summary":"In deze post leg ik mijn eigen setup uit: hoe ik mijn Windows Terminal gebruik, plus wat andere tips. Dus als je iemand indruk wilt maken terwijl je in je favoriete koffietent werkt, en ze wilt laten denken dat je bij Nasa werkt.","tags":["Tech"],"title":"Het ultieme ZSH- en Windows Terminal-look and feel","url":"https://www.allsubjectsmatter.nl/nl/the-ultimate-zsh-and-windows-terminal-look-and-feel/"}],"language":"nl","title":"All Subjects Matter","version":"https://jsonfeed.org/version/1.1"}