BlueGit

Help Architectuur

This page is not available in your language yet; it is shown in another language.

Architectuur

BlueGit is één proces. Het bedient de website, de git-HTTP-server, de API en (als het aanstaat) de SSH-listener. Een tweede goroutine plant CI-jobs. Een achtergrondtaak onderhoudt periodiek de bare repositories (git gc --auto en optioneel git maintenance). Metadata staat in SQLite. Git-objecten staan in bare repositories op de datadisk.

browser ──HTTPS──▶ ingress ──HTTP──▶ bluegit (web + smart HTTP + API)
git     ──HTTPS──▶ ingress ──HTTP──▶ bluegit ──▶ git http-backend ──▶ pre-receive hook
git     ──SSH:22─▶ ingress ──TCP:2222──▶ bluegit ──▶ git-receive-pack ──▶ dezelfde hook
bluegit ──bearer──▶ auth.timfalken.com
bluegit ──docker run──▶ jobcontainer
jobcontainer ──CI_JOB_TOKEN──▶ API van bluegit (release)
jobcontainer ──SSH-geheim──▶ de host (deploy, gewone SSH)

De ingress staat op een andere machine en doet TLS en SSH-forwarding. BlueGit luistert plain HTTP op listen. Clone-URL's komen uit base_url en uit ssh.public_host / ssh.public_port, niet uit de binnenkomende request. X-Forwarded-For en X-Real-IP gelden alleen als de directe verbinding van een adres in trusted_proxies komt. Het voorbeeld staat in INGRESS.md.

Waarom deze vorm

De mini-PC moet één ding draaien dat je kunt vervangen door een nieuwe binary. Go levert die binary zonder runtime ernaast. SQLite hoeft geen tweede dienst. De git-binary is toch al nodig voor hooks en merges; smart HTTP gaat daarom via git http-backend in plaats van een eigen implementatie van het pack-protocol.

Docker is alleen de jobgrens die de eis stelt. BlueGit mount de Docker-socket niet in de job. Een job die de host wil wijzigen, doet dat via SSH met een geheim, net als elke andere repository.

Schijf

Twee mappen. De state-map is klein en staat vast, standaard /var/lib/bluegit (state_path, de StateDirectory van systemd). Daar staan de database, secret.key en de SSH-hostkey, plus het bestand storage_path zodra de beheerder een datadisk heeft gekozen. Zonder dat bestand, en zonder een expliciet storage_path in de configuratie, is er geen datadisk. De eerste aangemelde gebruiker kiest die in /setup. Het pad moet bestaan, beschrijfbaar zijn en minstens 1 GiB vrij hebben. Git en CI antwoorden tot die tijd met een melding.

De datadisk zelf heeft geen standaardpad. Wijzigen mag later in hetzelfde scherm. BlueGit verhuist de bestanden niet; oude repositories blijven op de oude plek staan en worden daar niet meer gezocht.

Onder ProtectSystem=strict mag het proces alleen in de state-map schrijven, tot een drop-in ReadWritePaths het gekozen pad toevoegt. Die drop-in moet er zijn vóór het opslaan, anders faalt de schrijftest. Voorbeeld: packaging/storage.conf.

/var/lib/bluegit/bluegit.db
/var/lib/bluegit/secret.key
/var/lib/bluegit/storage_path
/var/lib/bluegit/ssh/host_ed25519
<datadisk>/repos/eigenaar/naam.git
<datadisk>/artifacts/job-id/…
<datadisk>/releases/eigenaar/naam/tag/…
<datadisk>/releases/.incoming/…   ongepubliceerde kopie; geen onderhoudstaak
<datadisk>/work/pipeline/job/

Op de bare repositories onder <datadisk>/repos draait BlueGit periodiek git-onderhoud (standaard elke 24 uur, instelbaar met git.maintenance_interval). Eerst git gc --auto, daarna best-effort git maintenance run voor loose-objects en incremental-repack. Dat gebruikt gits eigen locks, zodat een push of clone niet wordt gebroken; --prune=now wordt nooit meegegeven, zodat bereikbare refs en objecten blijven. Repositories worden één voor één en met lage prioriteit behandeld. Een fout slaat alleen die repo over. Zonder datadisk is de ronde een no-op. Installatiebeheerders kunnen de status opvragen en een ronde starten met GET/POST /api/v1/admin/git-maintenance (optioneel {"repo":"eigenaar/naam"}).

Geheimen in de database zijn versleuteld. Sessietokens van de auth-server staan als tekst in SQLite; de bestandsmachtiging van de database is de grens. Versleutelen van die sessies staat op de roadmap.

Auth

De interface auth.Provider heeft AuthorizeURL, Exchange, Extend en Logout. TimFalken praat met auth.timfalken.com. Dev vult dezelfde interface lokaal, zonder wachtwoord, en mag niet aan op het productiedomein.

Na de code-wissel bewaart BlueGit het bearer-token in de sessie. Korter dan zes uur voor het einde wordt POST /api/session/extend aangeroepen. Afmelden trekt het token in met POST /api/logout.

Git en de API gebruiken een apart toegangstoken (bg_…) of, alleen tijdens een job, een job-token (bgj_…). Dat zijn geen wachtwoorden: ze worden één keer getoond, als hash bewaard, en zijn in te trekken. Het OAuth-token leeft 24 uur en is een slechte git-credential.

Details en de eisen van de auth-server staan in AUTH.md.

Rechten

internal/access is bewust los van HTTP en SQL. Resolve kiest de hoogste rol uit persoonlijke eigenaar, groepslid en rechtstreeks lid. AuthorizeUpdate keurt een pre-receive-regel goed. De hook is die functie, aangeroepen als bluegit hook pre-receive, met de rol en de patronen in de omgeving. Zonder die omgeving weigert de hook, zodat een push om de server heen niet slaagt.

De eerste aangemelde gebruiker is installatiebeheerder. is_admin van de auth-server wordt opgeslagen en geeft die vlag niet. Installatiebeheer geeft geen lidmaatschap van andermans repositories of groepen en geen inzage in hun geheimen. Het scherm /setup hoort bij die vlag: daar staat alleen het pad van de datadisk.

Web

De pagina's zijn serverside HTML uit de binary. De router is de standaard ServeMux van Go 1.22. Een repository staat op /{eigenaar}/{naam}. Git staat op /{eigenaar}/{naam}.git. Vaste paden (/login, /groups, /settings, /api) winnen van die wildcard.

Formulieren sturen een CSRF-token uit de sessie mee. De API gebruikt een bearer-token en geen CSRF.

Push naar pipeline

Na een geslaagde smart-HTTP-push of SSH-push vergelijkt BlueGit de refs. Voor elke bijgewerkte branch leest het blue-ci-cd.yml op die commit. Ontbreekt het bestand, dan gebeurt er niets. Is het ongeldig, dan komt er een mislukte pipeline met de fout in het log, zodat de push zelf niet blokkeert. Jobs waarvan de rules niet kloppen, worden niet aangemaakt.

De runner zet een job op running, kloont de commit naar een werkmap, haalt artifacts van eerdere jobs binnen, en start:

docker run --rm --network <ci.network> -e NAAM …
  -v werkmap:/workspace --entrypoint sh IMAGE /workspace/.bluegit-job.sh

-e NAAM zonder waarde laat Docker de variabele uit de omgeving van BlueGit overnemen. Daardoor blijft een meerregelige SSH-sleutel heel, en staat de waarde niet in de proceslijst. Het netwerk van de container is het gewone Docker-netwerk, niet het hostnetwerk.

CI_JOB_TOKEN hoort bij die ene lopende job. De release-API eist dat het token bij deze repository hoort en dat de job nog loopt. Assets moeten al als artifact van een geslaagde job in dezelfde pipeline bestaan.

Webhooks

Een gebeurtenis gaat naar de webhooks van de repository en, als de repository van een groep is, ook naar die van de groep. Een repository-webhook ziet repositorygeheimen. Een groep-webhook ziet alleen groepgeheimen. Een beschermd geheim gaat mee als de branch van de gebeurtenis beschermd is.

Het branchfilter geldt alleen voor push. Andere gebeurtenissen negeren het. Een onbekend geheim of veld in het sjabloon is een fout en er gaat niets uit, zodat er geen webhook vertrekt met een leeg token.

De bestemming wordt bij het verbinden opgelost. Privébereiken (RFC1918, loopback, link-local, CGNAT, ULA en de IPv6-equivalenten, plus multicast en gereserveerde adressen) worden geweigerd, ook als maar één DNS-antwoord daarheen wijst. Het gekozen publieke IP wordt vastgezet; een proxy uit de omgeving wordt niet gebruikt. Elke redirect krijgt dezelfde controle. Een redirect naar een andere host neemt de headers niet mee, en een 307/308 naar een andere host wordt geweigerd zodat de body met geheimen niet meereist. Een publiek IP dat via de router terug het huis in komt, blijft toegestaan.

Vertrouwensgrens

De systeemgebruiker bluegit kan alles lezen wat op de schijf staat, inclusief geheimen (hij heeft secret.key). Mensen op de website kunnen dat niet, behalve een beheerder via het JSON-export dat de auth-server verplicht stelt. CI-jobs zien de geheimen die de regel van beschermde branches toelaat. Ze zien de Docker-socket niet.

Webhooks gaan niet naar een privénetwerk. De classificatie staat in internal/ssrf. Een allowlist van hosts, opnieuw proberen en een testknop staan nog op de roadmap.

Commitgrafiek

De project-overzichtspagina toont een commitgrafiek rechts van de centrale kolom, als die ruimte breed genoeg is. De centrale kolom schuift iets naar links; op brede schermen (flex: 1 1 auto) vult de grafiek alle ruimte rechts van de kolom tot aan de viewport-rand. Elke commit staat op één regel. Lijnen lopen in verticale lanes en buigen met afgeronde 90-graden hoeken naar een andere lane. De avatar van de auteur is de commitknoop, op de lane. De committekst staat in één linkse kolom die begint net rechts van de meest rechtse lane. Elke avatar staat in de SVG op de lane-x van die commit (<image> met clipPath, of een cirkel plus initiaal), in hetzelfde coördinatenstelsel als de lijnen. Er worden geen inline style-attributen gebruikt: Content-Security-Policy heeft style-src 'self' zonder 'unsafe-inline', dus paneelmaten komen uit stylesheet-klassen .lanes-1 t/m .lanes-40. De lanes en het SVG worden niet geschaald (geen viewBox). De tekstkolom begint altijd rechts van de meest rechtse lane. Te lange committekst vervaagt naar transparant met een mask-image in de stylesheet (geen ellipsis en geen overflow in de centrale kolom); hover toont het volledige bericht via het native title-attribuut. De grafiek is statisch (geen eigen scrollbar, geen overflow-y: auto) en heeft geen rechterpadding. Als het paneel onderaan het scherm stopt, vagen de lanes weg met een verticale mask-image (viewport-gebaseerd, zodat een korte grafiek niet vervaagt). Op brede schermen (~1400px met sidebar, of ~1160px zonder) staat de tekstkolom rechts van de meest rechtse lane. Daaronder, tot aan mobiel, is er een smalle modus zonder tekstkolom (--graph-narrow-w, refs + lanes): de hoofdkolom krijgt die ruimte terug. Hover toont onderwerp, korte sha en auteur via de native SVG <title> (geen custom data-tip of ::after). In de smalle modus toont een branchlabel alleen het deel na de laatste '/' (LeafLabel); de volledige naam staat in title. Elke commitknoop heeft een dunne stroke in de lanekleur (--blue of --graph-N); de gekozen branch houdt de dikkere gloed (stroke-width: 2.5 plus drop-shadow). Alleen op smalle (mobiele) viewports blijft de grafiek verborgen. De overzichtskolom (main, container overview) gebruikt een container query: als de kolom smaller is dan 900px (typisch bij sidebar plus smalle grafiek op 1024 of 1280px) valt het blok Project information onder de inhoud. De laatste-commit-balk wrapt (onderwerp en meta op de eerste regel, pipeline/sha/Copy/History op een volgende); de branchrij (dropdown + Code) blijft op één regel; table.files heeft table-layout: fixed zodat alle drie de kolommen met ellipsis in beeld blijven. Op een brede kolom (1920px) blijft de twee-koloms lay-out. Geen inline styles (CSP). Stylesheets en scripts worden met Cache-Control: no-cache gegeven, zodat een self-update geen oude CSS over de nieuwe tekening legt. De lane van de in de branch-dropdown gekozen branch gloeit (client-side, zonder extra request). De hoofdbranch is altijd --blue. Andere branches krijgen een deterministische kleur uit een palet van CSS-variabelen, op basis van de branchnaam.

De gelegde grafiek (recente commits over alle branches, inclusief merges en forks) staat in een begrensde LRU-cache in het geheugen, per repository (en hoofdbranch). Een herhaalde pageview doet geen git-I/O. Bij een push wist refsChanged de entry en vult refreshCache hem op de achtergrond opnieuw. Deze cache schrijft niet naar schijf en is onafhankelijk van de optionele leescache.