Infrastruktur Del 1: Tjugo timmar till cutover. Noll rader kod för hand.

Den 7 juli klockan 22:48 fanns den här sajten inte. Tjugo timmar senare var WordPress-installationen som drivit tokentek.ai borta från servern. Filerna var raderade, databasen tömd. Den statiska sajt du läser just nu hade tagit dess plats. DNS-pekarna behövde vi inte ens röra. Domänen pekade redan på samma server, så det hela kokade ner till att byta ut innehållet i webbroten.

Elva dagar senare hade git-loggen vuxit till 128 commits. 93 av dem bär raden Co-Authored-By: Claude Fable 5.

Det här är berättelsen om den migreringen, utläst ur vår egen git-historik. Men den intressanta frågan är egentligen inte om en AI kan bygga en webbplats i dag. Den intressanta frågan är hur en sajt måste vara uppbyggd för att en AI ska kunna arbeta med den på ett säkert sätt.

commits på elva dagar
128
rader kod skrivna för hand
0
från första commit till WordPress var borta
20 h
rader bash i deploy-skriptet
73

01

Därför fick WordPress gå

WordPress gjorde sitt jobb. Sidorna serverades snabbt ur cachen, backuperna snurrade och adminpanelen var precis som vanligt. Det var inget som var trasigt. Problemet var hur allting lagrades: sajtens hela tillstånd låg låst i en MySQL-databas, gömt bakom ett gränssnitt byggt för en människa med datormus.

Visst kan en AI klicka sig igenom en adminpanel. Men den kan inte överblicka hela sajten på en gång. Den kan inte spotta ur sig en diff som visar exakt vad en ändring innebär innan den sparas. Och den kan absolut inte föreslå en ändring som en människa i lugn och ro granskar innan den går live. Allt det där man tar för givet i ett kodrepo försvinner i en databas bakom en inloggningsskärm.

Det fanns fler irritationsmoment. Varje gång vi behövde en egen komponent blev det en kamp mot temat, i stället för att bygga med det. Sajten fanns bara på engelska, och att bygga svensk synlighet i sök kräver att varje sida finns på svenska också. I en adminpanel betyder det dubbelt handarbete och texter som sakta men säkert glider isär över tid.

Men i slutändan var det databasen som fällde avgörandet.

Målet för migreringen gick att koka ner till en enda mening: sajtens hela tillstånd ska ligga i textfiler. När allt ligger i filer kan en agent läsa allt och ändra vad som helst. Varje ändring blir en tydlig diff som en människa kan godkänna innan den publiceras. Migreringen handlade därför mindre om att lämna WordPress, och mer om att hitta ett format där en AI faktiskt kan göra sitt jobb.

02

Allt är text, allt valideras

Tittar man i repot i dag hittar man inga färdiga webbsidor. Bara källmaterial. Där ligger 25 markdownfiler med innehåll, 46 komponentfiler och ungefär 10 000 rader kod. Innehållet är uppdelat i fyra strikt typade samlingar — sidor, jobb, artiklar och case — och varje samling har ett schema som valideras stenhårt vid varje bygge.

Vi byggde in valideringen i tre lager. Om någon stavar fel på ett metadatafält, stoppar schemat bygget. Om en brödtext saknar en obligatorisk sektion, slår parsern ifrån och listar exakt vilka sektioner som saknas. Och om en bildreferens pekar fel, avbryter bildregistret med en lista över de bildnycklar som faktiskt finns.

Resultatet är att ett innehållsfel aldrig når produktion. Det kraschar bygget redan på laptopen eller i CI-miljön, och skickar ut ett felmeddelande formulerat för den som orsakade det — oavsett om det var en av oss eller en AI-modell.

03

Två språk, samma filnamn

Tvåspråkigheten, som var en evig stötesten i WordPress, löste vi med en enkel namnkonvention. Svenskan bor i roten av filsystemet, engelskan ligger under /en/, och sidorna knyts ihop genom att de har exakt samma filnamn på båda språken.

Byggprocessen letar upp de här paren och genererar hreflang-länkarna — alltså märkningen som talar om för Google vilka sidor som hör ihop — och sitemapen speglar samma struktur. För sökmotorerna ser svenskan och engelskan ut som en och samma sajt.

Svenska bor på /, engelska under /en/. Sidor och case paras ihop via identiska filnamn, artiklar pekar själva ut sin översättning, och varje byggd sida bär sina hreflang-länkar.
Svenska bor på /, engelska under /en/. Sidor och case paras ihop via identiska filnamn, artiklar pekar själva ut sin översättning, och varje byggd sida bär sina hreflang-länkar.

En del av detta löser vi med typning. Textsnuttarna i gränssnittet ligger i två ordböcker, där den engelska ordboken är typad mot den svenskas nycklar. Glömmer någon en översättning blir det ett typfel och bygget stannar. Resten hanterar vi via en enkel arbetsregel som AI-modellen fått lära sig: när vi ändrar en svensk sida, ska den engelska uppdateras i samma commit. Allt granskas som en enda diff.

04

73 rader bash

Hela vår driftsättning bygger på 73 rader bash, ett litet skript som pingar sökmotorer och två rader i crontab.

Själva publiceringen bygger på pull i stället för push. Var femtonde minut går servern ut till GitHub, hämtar hem koden och jämför den med versionen som ligger live. Hittar den något nytt drar den i gång ett bygge. Först när bygget lyser grönt kopieras de nya filerna över till webbroten.

Vi valde bort webhooks helt och hållet. En webhook innebär en öppen port som ligger och lyssnar utåt och måste försvaras. En server som hämtar data på eget bevåg exponerar ingenting. Nackdelen är förstås att publiceringen kan dröja upp till en kvart. Det väger vi upp genom att pinga IndexNow så fort bygget är klart, så att sökmotorerna får nys om uppdateringen direkt i stället för att vänta på nästa genomsökning.

En innehållsändring passerar tre förtroendezoner. GitHub verifierar men driftsätter ingenting. Servern hämtar, bygger och publicerar först efter ett grönt bygge.
En innehållsändring passerar tre förtroendezoner. GitHub verifierar men driftsätter ingenting. Servern hämtar, bygger och publicerar först efter ett grönt bygge.

GitHub bygger för övrigt också sajten vid varje push — och slänger sedan hela resultatet. Det ser ut som slöseri med resurser, men det är hela vitsen: vi separerar verifiering från publicering. GitHub har noll kontakt med servern och sitter inte på några hemligheter som kan läcka. Servern i sin tur kommer åt GitHub via en nyckel som enbart har läsrättigheter.

De där 73 raderna bash handlar till stor del om felhantering. Kommentarerna i koden förklarar varför:

# Compare against the last rev that actually DEPLOYED (stamped only after
# a successful rsync), not HEAD — otherwise a failed build leaves HEAD at
# the new rev and every following non-forced run considers it deployed
# and skips the retry.

Tricket är att skriptet stämplar en version som driftsatt först när filerna ligger säkert på disken. Missar man det ser ett trasigt bygge ut som att det redan ligger live, och servern slutar försöka bygga om det. Med stämpeln på plats läker ett tillfälligt fel sig självt vid nästa försök en kvart senare.

Den tuffaste läxan handlade om filrättigheter. Vid ett tillfälle råkade en manuell körning via SSH skapa filer som webbservern saknade behörighet att läsa, och plötsligt svarade hela sajten med 403 Forbidden. I dag låser skriptet rättigheterna på egen hand vid varje körning. Sådär växte koden fram — varje stycke felhantering är ett ärr från något som gick snett i verkligheten.

05

Ett bygge som hellre avbryter sig självt

På startsidan ligger en nyhetssektion som hämtar sina data från min systersajt techbyjohan när sajten byggs. Tittar man i koden hittar man en kommentar som lätt skulle kunna tolkas som lathet:

// Deliberately no fallback: if the feed is broken the build fails and the
// previously published version stays live.

Vi byggde med flit inte in någon reservlösning. Om RSS-flödet går ner stannar hela bygget. Eftersom servern väntar in ett grönt bygge innan den publicerar något, innebär det att den gamla, fungerande versionen ligger kvar orörd. Det är mycket bättre än att sajten obemärkt laddar upp en tom nyhetssektion och publicerar den för världen.

Och eftersom källkoden inte ändras bara för att ett nytt nyhetsbrev går ut, har vi ett schemalagt tvångsbygge varje morgon klockan 05:30. Sajten är med andra ord dynamisk en minut om dygnet, och statisk resten av tiden.

06

Det som inte finns

Säkerheten i den här arkitekturen bygger framför allt på det som inte finns.

Det finns ingen databas att hacka. Ingen PHP-process att sänka. Inga plugins som kräver säkerhetsuppdateringar. Ingen adminpanel som står öppen mot nätet, och inga api-nycklar lagrade hos GitHub. Katalogen som publiceras innehåller noll JavaScript-filer. De få skript som existerar, till exempel räkneanimationen i sajtens statistikplattor, är inkodade direkt i HTML-sidorna och körs enbart om besökarens webbläsare tillåter det. Innehållet går fram oavsett.

WordPress låg bakom en LiteSpeed-cache: träffar serverades ur en sparad kopia, men varje miss väckte PHP och MySQL. Samma LiteSpeed serverar sajten i dag — men nu är filen på disken originalet, inte en kopia av något dynamiskt.
WordPress låg bakom en LiteSpeed-cache: träffar serverades ur en sparad kopia, men varje miss väckte PHP och MySQL. Samma LiteSpeed serverar sajten i dag — men nu är filen på disken originalet, inte en kopia av något dynamiskt.

Vår gamla WordPress-sajt var för all del snabb — cachen skötte sitt jobb. Men en cache gömmer bara komplexiteten, den tar inte bort den. Under ytan fanns PHP, MySQL och hela login-maskineriet alltid kvar, krävde underhåll och stod beredda varje gång en besökare orsakade en cachemiss. Det enda som ligger mot internet i dag är en webbserver som serverar statiska filer. Det är hela attackytan. Och backupen? Den utgörs av git-historiken, plus ett Duplicator-arkiv från WordPress-tiden som vi behållit för säkerhets skull.

07

Vem gjorde vad?

Koden är nästan uteslutande skriven av Claude Fable 5, medan OpenAI:s GPT-5.6 Sol användes för granskning. Min roll var att skriva prompterna, läsa igenom varje diff, testa resultatet på riktiga enheter och fatta de faktiska besluten. AI:n migrerade sajten och byggde infrastrukturen, men den skrev inte texterna — de är våra egna.

Estetiken är också resultatet av mänskliga beslut. Modellerna fick en tydlig riktning inspirerad av Dieter Rams och Apple. Det allra första designutkastet, som hämtade inspiration från diverse AI-startups, åkte i papperskorgen direkt för att det kändes för barnsligt. Sidhuvudena jämfördes med Apples och fick omdömet "Ours are a mess", vilket drog i gång en ordentlig städrunda över hela sajten. Vi matade modellerna med referenser och begrepp tills designen satt där den skulle.

Men om man tittar i historiken är den vanligaste mänskliga insatsen faktiskt varken prompten eller koden. Det är strykningen. En hel sektion på startsidan plockades bort för att "vi nämner ju redan vad vi gör under 01, det räcker kanske?". Nyhetskorten trimmades på onödiga etiketter. Ett sökord offrades för att texten blev för styltig: "det ser inte klokt ut, då skippar jag hellre den träffen". Och när AI:n genererade en serie tekniska illustrationer strök vi alla utom en. "Remove the rest, that one is good and strong enough." Modeller gillar att lägga till saker. Människans roll är ofta att dra ifrån.

När vi körde fast var det verkligheten som fick fälla avgörandet. Den 11 juli lade vi ett femtontal commits på att försöka få till rätt färg bakom Safaris verktygsfält på iPhone. Allt testades mot en fysisk telefon, och vi gav till slut upp försöken att överlista webbläsaren. Beslutet blev i stället: "you know what let's simplify. let's remove the line".

Mörkt läge föddes på ungefär samma sätt. En kväll när jag testade sajten hittade jag tre buggar — loggan och några knappar tappade färg. Det visade sig att det var webbläsartillägget Dark Reader som hade försökt rita om sajten på eget bevåg. Den första tanken var att blockera tillägget, men svaret blev i stället "jag vill ha dark mode". Dygnet senare hade sajten ett riktigt, inbyggt mörkt tema — delvis för att Dark Readers improviserade färgpalett faktiskt såg riktigt bra ut om man bara rensade bort misstagen. Modellerna byggde mekaniken; människan bestämde vad som skulle vara kvar.

Git-loggen är ett facit på hur det här samarbetet ser ut i praktiken. Under den mest intensiva dagen gjorde vi 36 commits. Fem av dem skedde under en och samma minut, och tre ändringar rullades tillbaka i sin helhet. Det är korta loopar, små diffar, och ett arbetssätt där man vågar göra fel för att det går så fort att backa bandet.

Samma metod, på era processer

Den här sajten är bara vår egen infrastruktur, men metoden vi använde är exakt samma som den vi erbjuder våra kunder: ge AI:n en teknisk ram den förstår, bygg in hårda spärrar så att ett trasigt förslag aldrig når produktion, och låt människor fatta besluten. Vi har tidigare skrivit om fem frågor som avgör om en process passar för AI-agenter. En statisk markdownsajt med automatiserade byggen bockar av alla fem. Har ni en affärsprocess som uppfyller samma krav är det läge att vi pratar.