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. 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>/work/pipeline/job/

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.