Minimale git-remote met publiek/prive-toggle

main

Deze repository wordt als momentopname gepubliceerd: een clone bevat de volledige inhoud, maar geen commitgeschiedenis.

8.9 kB Ruw bestand

pub-repo

Een minimale git-remote voor je eigen site. Je pusht met één token, en zet per repository met één knop of hij publiek zichtbaar en anoniem te clonen is.

Geen accounts, geen issues, geen pull requests, geen CI. Wel: echte git clone en git push over HTTPS, en een bladerbare weergave van je publieke code.

Wat het doet

Pushen Alleen jij, met één owner-token via HTTPS Basic auth
Publieke repo Anoniem te clonen én te bladeren op je site — als momentopname
Privé repo Onzichtbaar: geeft exact hetzelfde antwoord als een repo die niet bestaat
Bladeren Bestandsboom, bestandsinhoud met syntax highlighting, README-rendering, branchkeuze
Beheer /admin: repo aanmaken, beschrijving zetten, publiek/privé omzetten, verwijderen

Bewust weggelaten: issues, forks, pull requests, meerdere gebruikers — en commitgeschiedenis, zie hieronder.

Geen geschiedenis

Wat je publiceert is de inhoud, niet hoe je eraan gekomen bent. Een anonieme clone levert per branch één commit zonder ouders op: alle bestanden, geen verleden. Jouw eigen geschiedenis blijft wél op de server staan en haal je terug via een tweede transport dat altijd om je token vraagt.

git push  ->  <naam>.git              jouw repository, volledige geschiedenis
                  |                    bereikbaar via /git-full  (token vereist)
             momentopname
                  v
              .published/<naam>.git   losse repository, 1 commit per branch
                                       bereikbaar via /git       (publiek)

Dat zijn twee aparte repositories op schijf, en dat is geen overdaad. Binnen één repository valt dit niet af te schermen: transfer.hideRefs en git-namespaces houden refs alleen uit de advertentie, waarna een client de objecten alsnog met git fetch <sha> ophaalt. De handleiding van git zegt het zelf, in gitnamespaces(7):

The fetch and push protocols are not designed to prevent one side from stealing data from the other repository that was not intended to be shared. If you have private data that you need to protect from a malicious peer, your best option is to store it in another repository.

Dus staat het in een andere repository. Wat er niet in zit, kan er niet uit.

Omdat de momentopname geen ouders heeft, gaan bij het publiceren alleen de boom en de bestanden mee. Een push die niets aan de inhoud verandert levert geen nieuwe momentopname op, dus het gepubliceerde commit-id blijft dan stabiel. Oude momentopnames worden direct opgeruimd — ook die zijn geschiedenis.

Hoe het werkt

Repositories zijn gewone bare git-repos onder REPO_ROOT, als <naam>.git, met de gepubliceerde momentopnames in REPO_ROOT/.published/. Het git-protocol zelf wordt afgehandeld door git http-backend — het CGI-programma dat git meelevert. Deze applicatie doet daar de toegangscontrole voor, en leest voor de webweergave de objecten uit met gewone git-commando's.

Welke van de twee repositories http-backend te zien krijgt, is simpelweg een kwestie van welke map als GIT_PROJECT_ROOT wordt meegegeven. Anoniem ophalen kan de repositories met geschiedenis dus niet eens zien staan.

Je data zit nooit gevangen: REPO_ROOT is een map met bare repos die je kunt rsyncen, backuppen of ergens anders neerzetten. De enige extra informatie is per repo een pub-repo.json met de zichtbaarheid en beschrijving.

src/
  index.ts              server, middleware-volgorde, security headers
  config.ts             omgevingsvariabelen
  auth.ts               owner-token, Basic auth, sessiecookie
  repos.ts              repos op schijf: aanmaken, metadata, lijst
  git.ts                veilige wrappers om git-commando's
  routes/smart-http.ts  toegangscontrole + git http-backend
  routes/web.ts         publieke weergave
  routes/admin.ts       inloggen en beheer
views.ts                HTML-rendering
public/styles.css       styling, licht en donker

Lokaal draaien

Vereist Node 22+ (draait TypeScript rechtstreeks) en git in PATH.

npm install
cp .env.example .env      # vul OWNER_TOKEN en SESSION_SECRET in
npm run dev

Genereer de twee geheimen met openssl rand -hex 32 (of node -e "console.log(require('crypto').randomBytes(32).toString('hex'))").

Daarna: ga naar /login, log in met je OWNER_TOKEN, maak op /admin een repo aan en push ernaartoe.

Uitrollen

deploy/setup.sh doet de provisionering op een Debian- of Ubuntu-box met nginx en certbot: systeemgebruiker, mappen, een bare deploy-repo met post-receive hook, verse geheimen, een systemd-unit, een certificaat en de vhost. Het script is idempotent.

De unit en de vhost komen uit de repo zelf, dus die zijn er pas na de eerste push. Vandaar twee rondes:

export DOMAIN=git.voorbeeld.nl LE_EMAIL=jij@voorbeeld.nl

# 1. gebruiker, mappen, deploy-repo, .env met verse geheimen, certificaat
ssh root@server 'bash -s' < deploy/setup.sh

# 2. code erheen
git remote add production ssh://root@server/srv/git/pub-repo.git
git push production main

# 3. unit en vhost installeren, service starten
ssh root@server 'bash -s' < deploy/setup.sh

Daarna deployt elke git push production main zichzelf via de post-receive hook: checkout bijwerken, npm ci --omit=dev, service herstarten.

Wat waar staat

Pad Wat
/opt/pub-repo de code, een git-checkout van /srv/git/pub-repo.git
/opt/pub-repo/.env de geheimen, chmod 600
/var/lib/pub-repo/repos je repositories met geschiedenis — dit is je data
/var/lib/pub-repo/repos/.published de momentopnames; volledig afleidbaar uit het bovenstaande
/srv/git/pub-repo.git deploy-remote, staat los van je gepubliceerde repos

Twee valkuilen

Beide zitten in het script verwerkt, maar als je het met de hand doet:

nginx. client_max_body_size 0 en proxy_request_buffering off op de git-locatie zijn niet optioneel. Een push is één grote POST; met de standaardlimiet van 1 MB mislukt elke push van enige omvang, en zonder de tweede schrijft nginx de hele pack eerst naar schijf.

safe.directory. De bare deploy-repo is van root, de fetch in de hook draait als pubrepo. Zonder onderstaande regel faalt elke deploy met detected dubious ownership:

sudo -u pubrepo -H git config --global --add safe.directory /srv/git/pub-repo.git

Het owner-token gaat als Basic auth over de lijn, dus draai dit nooit zonder TLS.

Gebruik

Maak de repo eerst aan op /admin, en koppel hem dan vanaf je werkmachine:

git remote add origin https://git.voorbeeld.nl/git/mijn-project.git
git push -u origin main

Git vraagt om een gebruikersnaam en wachtwoord. De gebruikersnaam maakt niet uit (vul bijvoorbeeld owner in), het wachtwoord is je OWNER_TOKEN. Wil je niet elke keer typen, laat git het onthouden:

git config --global credential.helper store    # of: osxkeychain, manager

Zichtbaarheid omzetten doe je op /admin — de knop toont de huidige stand en zet hem om. Publiek gemaakte repos verschijnen direct op de voorpagina.

Wil je je eigen geschiedenis terughalen, bijvoorbeeld omdat je lokale kopie weg is, gebruik dan het volledige transport. Dat vraagt altijd om je token, ook bij een publieke repository:

git clone https://git.voorbeeld.nl/git-full/mijn-project.git

Beveiliging

Wat er is afgedekt, zodat je weet waar je op moet letten:

Eén ding om te weten: als je een typefout maakt in de naam van een publieke repo, vraagt git om een wachtwoord in plaats van te zeggen dat hij niet bestaat. Dat is de prijs voor het niet lekken van privé-namen.

Back-up

rsync -a root@server:/var/lib/pub-repo/repos/ ./repos-backup/   # incl. .published/

Meer is er niet. Er is geen database.