Publishing source repos on ird.cod-sursa.ro
A step-by-step guide to publishing a list of git repositories as anonymous,
publicly clonable repos at https://<host>/<name>.git — with no git
server. The repos are static files pinned on IPFS, served by the ird
gateway through a DNSLink domain, and cloned over git's dumb-HTTP protocol.
This repo is itself published this way:
git clone https://ird.cod-sursa.ro/publish.git
How it works
your repo ──publish-repo.sh──▶ bare mirror (loose objects, static files)
│
all mirrors + landing pages ──assemble.sh──▶ one root folder
│
ird ipfs add (pin the folder)
│
ird ipfs ipns publish <host> <cid>
│
https://<host>/<name>.git ← DNSLink domain on the ird gateway
git clone https://<host>/<name>.git works because git's dumb-HTTP
transport is just plain GETs of info/refs, HEAD and
objects/xx/yyyy… — all static files.
- Each publish pins a new root CID and re-points the domain's IPNS name at
it, then unpins the previous root. IPFS dedupes unchanged subtrees, so an
incremental publish costs roughly only the changed repo.
- One domain serves the whole ecosystem — adding a repo needs no new
DNS, certs, or infrastructure.
Layout
Two kinds of directories are involved:
<each source repo>/
deploy/publish-repo.sh builds this repo's mirror + runs the anonymity gate
site/index.html the repo's human landing page
.gitignore must ignore deploy/.site/
<assembler dir>/ (its own small git repo, NOT published)
assemble.sh builds every mirror, assembles the root, pins + publishes
index.html the ecosystem's root landing page
<root>/ the assembled tree that gets pinned (gitignored)
The assembled root looks like:
<root>/index.html root landing page
<root>/<name>.git/ clonable bare mirror → https://<host>/<name>.git
<root>/<name>/ landing page → https://<host>/<name>/
Step 0 — one-time host setup
Pick a hostname on a domain whose DNS zone is hosted on ird. Then:
ird ipfs ipns create <host> --serve
This creates the IPNS name, attaches the gateway domain, writes the DNS
records (A + _dnslink TXT) and issues the TLS cert (~1 min). Done once,
ever.
Create the assembler directory with assemble.sh and a root index.html
(clone this repo and copy templates/assemble.sh, or copy from an existing
ecosystem). Make the assembler dir a git repo too, with the same anonymous
identity, so the publishing pipeline itself is versioned.
Step 1 — anonymous identity, BEFORE the first commit
Every published repo must have exactly one author, an anonymous project
identity, on every commit:
git init
git config user.name <ident>
git config user.email <ident>@noreply.local
Set this before the first commit — history rewrites after the fact are
messy. If a repo already has commits under a real identity, rewrite:
git filter-branch -f --env-filter '
export GIT_AUTHOR_NAME=<ident> GIT_AUTHOR_EMAIL=<ident>@noreply.local
export GIT_COMMITTER_NAME=<ident> GIT_COMMITTER_EMAIL=<ident>@noreply.local
' -- --all
…and remember the old commits survive in reflog/loose objects — which is
exactly why the mirror is built with git clone --no-local (only
ref-reachable objects are copied) and why the gate re-scans every object.
Step 2 — identity patterns file (local, never committed)
The leak scan greps every published byte for your real identity strings.
The patterns live outside any repo, in an untracked local file, one
extended-regex per line:
mkdir -p ~/.config/cod-sursa
printf '%s\n' '<your-name>' '<your-email-user>@' > ~/.config/cod-sursa/idpat
Lines starting with # are comments. Add every name, username, and email
that must never appear in public bytes.
Step 3 — per-repo files
In each repo to publish:
- Copy
templates/publish-repo.sh to deploy/publish-repo.sh, make it
executable, and set the two variables at the top:
APP=<name> — the path under the host (<name>.git)
IDENT=<ident> — the anonymous identity from step 1
- Write
site/index.html — the landing page. Say what the repo is, show
the clone command, link back to the root.
- Ignore the build output: add
deploy/.site/ to .gitignore.
What publish-repo.sh does, in order:
git clone --no-local --bare the repo into deploy/.site/<APP>.git
(transport clone: only ref-reachable objects, never stray local state)
- explodes packs into loose objects — the IPFS gateway answers missing
paths with a 200 fallback page, which breaks git's pack-fallback probing;
all-loose means every path a dumb-HTTP client asks for exists
git update-server-info — writes info/refs (the dumb-HTTP entry point)
- strips
hooks/ and config from the mirror
- runs the anonymity gate (see below) and refuses to build on failure
Step 4 — register the repo in the assembler
In assemble.sh: add the repo to the repo_for case list and to the
default APPS list. In the root index.html: add a card with the clone
command and a link to /<name>/.
Step 5 — check, assemble, publish
./check-anonymity.sh <path-to-repo> # full pre-publish audit (this repo)
NO_PUBLISH=1 ./assemble.sh # assemble only; inspect <root>/
./assemble.sh # assemble + pin + publish
./assemble.sh <name> [<name>…] # republish only the named repos
assemble.sh runs each repo's own gate again — nothing reaches the pin
step unchecked. The publish step:
- generates each repo's in-browser source browser (
/<name>/browse/) with
gen-browse.py — styling comes from one shared stylesheet on the
ecosystem's CDN host (cdn.cod-sursa.ro, itself just another pinned
folder + DNSLink domain; assets are versioned by filename)
- pins the root folder, reads the new CID
- re-pins the CID under the account convention name
site:<host> and drops
the folder-named pin entry
ird ipfs ipns publish <host> <cid>
- unpins the previous root (billing for it stops)
Step 6 — verify
The new CID is live on the gateway immediately; the domain flips when its
DNSLink cache expires (zone TTL, typically ≤1h). Verify content now, by CID:
curl -s https://ipfs.ird.ro/ipfs/<cid>/<name>.git/info/refs
and the domain itself later:
git ls-remote https://<host>/<name>.git HEAD
git clone https://<host>/<name>.git /tmp/verify-<name> # full end-to-end
For Clojure repos, the mirror is directly usable as a git dep:
;; deps.edn
some.group/lib {:git/url "https://<host>/<name>.git" :git/sha "<full-sha>"}
assemble.sh also writes releases.json at the site root — for every mirror,
its head, latest tag, version and tag_sha — so landing pages can fill
their dependency snippets with the coordinates actually published (fetch it
client-side; keep a static fallback), and scripts or agents can read the
current pin without git ls-remote.
The anonymity gate
Three checks, enforced by deploy/publish-repo.sh on every build, plus a
standalone auditor (check-anonymity.sh) you can run any time against the
source repo:
- Single anonymous author — every author and committer across all
refs must be exactly
<ident> <ident@noreply.local>.
- No identity bytes — every git object is decompressed and grepped
(case-insensitive) against your patterns file. Catches names and emails
hiding in file contents, commit messages, old blobs.
- No leaked build output — refuses if
deploy/.site ever got
committed into the repo.
check-anonymity.sh additionally scans tracked files for secret-shaped
content (private keys, tokens, .env files, absolute home paths) and warns.
Rules of thumb:
- the gate is a backstop, not a license to be sloppy — set the identity
before the first commit and keep secrets in gitignored files
- never put real paths, hostnames you don't want advertised, or personal
emails in code, docs, or commit messages of a to-be-published repo
LICENSE copyright line: use the project identity, not a person
Costs
IPFS pinning bills hourly per MB stored plus per MB served. A typical
source ecosystem is a few MB; old roots are unpinned on every publish, so
storage does not accumulate. See ird ipfs usage / ird credits prices.
Files in this repo
| file |
what |
check-anonymity.sh |
standalone pre-publish audit for any repo |
templates/publish-repo.sh |
per-repo mirror builder + gate (set APP/IDENT) |
templates/assemble.sh |
ecosystem assembler + publisher (set host/repos) |
gen-browse.py |
static in-browser source browser: Pygments-highlighted code pages, markdown rendered to HTML, dir listings — generated per repo by the assembler into /<name>/browse/ |
site/index.html |
this repo's landing page |
A ready-to-use agent prompt for automating all of this lives in the
companion repo: git clone https://ird.cod-sursa.ro/prompts.git.
License
MIT.