publish / README.md
1,321 words

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

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:

  1. Copy templates/publish-repo.sh to deploy/publish-repo.sh, make it executable, and set the two variables at the top:
  2. APP=<name> — the path under the host (<name>.git)
  3. IDENT=<ident> — the anonymous identity from step 1
  4. Write site/index.html — the landing page. Say what the repo is, show the clone command, link back to the root.
  5. Ignore the build output: add deploy/.site/ to .gitignore.

What publish-repo.sh does, in order:

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:

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:

  1. Single anonymous author — every author and committer across all refs must be exactly <ident> <ident@noreply.local>.
  2. 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.
  3. 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:

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.

static mirror of HEAD · about · clone: git clone https://ird.cod-sursa.ro/publish.git