The README's permissions section gains a Private repos part: the flag, who may read, what a non-reader sees, the visibility commands, and what going private or public does to pages, sites, events and the mirror. The hosting, backup, import and mirror sections say what changed for them, and the guide stops saying everything on the forge is public.
- commit
- 6c5bce7410ac51d5675544a84d4bfdb321f2127d
- parent
- acb4b5ccdbf384f89e15b62da3f0061f9a2b4a6f
- author
- cat <admin@dev.site>
- kind
- docs risk score 0.1 of 3, Jev's estimate
| M | README.md | 92 | +++++++++++++++++++++++-------- |
| M | guide/ | 4 | +- |
@@ -84,9 +84,9 @@ forge comment, approve ─── ssh ──────────────>
8484 | Process | Why |8585 | --- | --- |8686 | sshd | git over ssh. Every key is forced through `forge shell <name>`. |87-| Caddy | Serves `.forge/www` as plain files, read-only HTTPS clones (`git http-backend` through fcgiwrap), and `go get` meta tags; proxies `/events/` and, with a pages domain, sites. |87+| Caddy | Serves `.forge/www` as plain files, read-only HTTPS clones of public repos (`git http-backend` through fcgiwrap; nothing else inside a bare repo is served), and `go get` meta tags; proxies `/events/` and, with a pages domain, sites. |8888 | `forge build` | Runs 11ty after each push, in the background and one repo at a time, then swaps the new pages into `www/`. Unchanged pages are hard links to the last build's. First it adds the push's commits to the repo's commit-graph (see [Cleaning up](#cleaning-up)). |89-| `lib/events-server.ts` | Tails `.forge/events.jsonl`, which hooks and builds append to, and streams it as server-sent events at `/events/<owner>/<repo>/`. Open pages say who pushed and swap in fresh content when the build finishes. Without it pages still work; their status line says offline. |89+| `lib/events-server.ts` | Tails `.forge/events.jsonl`, which hooks and builds append to, and streams it as server-sent events at `/events/<owner>/<repo>/`. Open pages say who pushed and swap in fresh content when the build finishes. Without it pages still work; their status line says offline. Private repos stream nothing. |9090 | `lib/pages-server.ts` | Only with a pages domain. Serves each site on its own hostname the way Cloudflare Pages does (`_headers`, `_redirects`, pretty URLs, the nearest `404.html` or a single-page-app fallback) and redirects old `/sites/...` paths there. Runs as the caddy user, since deploys are untrusted content. |9191 | `forge backup` | Daily, from a loop in `start.sh`. Snapshots the forge with restic into a bucket, then checks the snapshots and test-restores the new one (see [Backups and moving hosts](#backups-and-moving-hosts)). |9292 | `forge mirror` | After builds, in the background: syncs the forge's own pages to a bucket with rclone, for the edge to serve while the forge is down. |@@ -110,11 +110,12 @@ source of identity. `forge keys sync` generates two files from it:
110110 - `allowed_signers`, so the same keys verify CI attestations and signed site111111 deploys.112112 113-Repos live at `/srv/git/<owner>/<repo>.git`, and `<owner>` owns them. The owner114-and anyone listed in `<repo>.git/forge/collaborators` can push anything. Other115-users can push branches and previews under their own name (`ana/...`) and add116-CI notes, which must fast-forward. Nobody can force-push `main` unless117-`forge.allowforce` is set.113+Repos live at `/srv/git/<owner>/<repo>.git`, and `<owner>` owns them. Anyone114+can read a public repo; a private one, only the people below. The owner and115+anyone listed in `<repo>.git/forge/collaborators` can push anything. Other116+users who can read it can push branches and previews under their own name117+(`ana/...`) and add CI notes, which must fast-forward. Nobody can force-push118+`main` unless `forge.allowforce` is set.118119 119120 `ssh git@git.example.com new <name>` creates a repo under your name, and so120121 does pushing to one that doesn't exist yet: `forge shell` sees the push before@@ -129,6 +130,42 @@ The first branch pushed becomes the default if it isn't `main`. A push to a
129130 missing repo under someone else's name, or a clone or fetch of one, fails130131 as before.131132 133+### Private repos134+135+A repo is public while its bare repo has a `git-daemon-export-ok` file. That136+is git's own flag: `git daemon` and `git http-backend` serve only repos that137+have it, and Forgejo toggles the same file. `forge repo new` adds it, and138+`--private` leaves it out: `ssh git@git.example.com new --private <name>`,139+`forge repo new --private`, and the imports below.140+141+A private repo can be read by its owner, its collaborators and the names in142+`<repo>.git/forge/readers`, over ssh only. To anyone else it isn't there: a143+clone, fetch, archive, push or ssh command gets the same answer, byte for144+byte, as for a repo that doesn't exist, however the path is spelled. It has145+no pages, no entry on the index, owner pages or `repos.json`, and no live146+events. Its sites and previews aren't served unless `git config147+forge.publicsites true`. Pushes still record reviews, and `forge reviews`148+reads them from the review logs (without CI). The secret check still runs,149+since a secret stays in the history when the repo goes public later.150+151+```sh152+ssh git@git.example.com readers dami/notes add ana # rm to take it back; no argument lists them153+ssh git@git.example.com private dami/greet154+ssh git@git.example.com public dami/notes [--force]155+ssh git@git.example.com ls # yours, and the private ones you may read156+```157+158+The owner and collaborators flip a repo, the way Forgejo does. `private`159+removes the flag, the repo's pages, its entries on the index, owner pages160+and `repos.json`, and its served sites, previews and their hostnames. Then161+it starts a mirror sync, which deletes them from the bucket. Clones, caches162+and copies people already have can't be recalled. `public` first runs the163+secret check over the whole history: every commit on a branch or tag, and164+the old review revisions that review pages show, not only what a push adds.165+If it finds something, the repo stays private unless you pass `--force`.166+Then it adds the flag, links the sites and previews again from their refs,167+and rebuilds the pages.168+132169 ### Reviews133170 134171 `post-receive` appends each push of a non-default branch to@@ -164,13 +201,14 @@ ssh git@git.example.com land dami/greet 7 # the forge lands it itself; no clon
164201 ```165202 166203 - **Comments** are files, `<repo>.git/forge/comments/<review>/<n>`: header167- lines (time, user, the review's tip and revision, and for a line comment168- the path, the new-side line number and that line's text), a blank line,169- then the markdown message (at most 32 KiB). Any registered user can170- comment. Once the review gets a new revision, older comments show as171- outdated and still quote the line they were about. Line comments on the172- current revision also appear under their line in the combined diff. A173- comment made from a checkout belongs to the revision checked out.204+ lines (time, user, the review's tip and revision, and for a line comment the205+ path, the new-side line number and that line's text), a blank line, then the206+ markdown message (at most 32 KiB). Any registered user can comment, or on a207+ private repo anyone who can read it. Once the review gets a new revision,208+ older comments show as outdated and still quote the line they were about.209+ Line comments on the current revision also appear under their line in the210+ combined diff. A comment made from a checkout belongs to the revision211+ checked out.174212 - **Approvals** are lines in the review log, `approve <ts> <user> <sha>175213 <tree>`. Like CI results they are keyed by tree, so rewording or176214 reordering commits keeps them; if the tree changes, the page shows@@ -420,7 +458,7 @@ Settings forge gives every repo, when it is created and again on
420458 `transfer.hideRefs refs/forge`, so clones, fetches and pushes don't see the421459 pinned revisions (the build reads them directly, and pushes to them are422460 refused), and `commitGraph.changedPathsVersion 2`, Bloom filters that hash423-non-ASCII paths the same on every platform. The dumb-HTTP `info/refs` file461+non-ASCII paths the same on every platform. The dumb protocol's `info/refs`424462 still lists `refs/forge`; git clients use the smart protocol.425463 426464 ### Moving repos over@@ -429,6 +467,7 @@ still lists `refs/forge`; git clients use the smart protocol.
429467 git config --global forge.host git@git.example.com430468 forge import https://github.com/you/thing.git # default branch + tags431469 forge import --branches --desc "..." <any-git-url> # every branch470+forge import --private <url> # lands as a private repo432471 ```433472 434473 Imports keep the source's default branch and don't turn existing branches into@@ -440,12 +479,16 @@ gh repo list you --limit 200 --json url,description,isPrivate \
440479 while IFS="$(printf '\t')" read -r url desc; do forge import --desc "$desc" "$url"; done441480 ```442481 482+For the private ones, run the same loop with `select(.isPrivate)` and483+`forge import --private`. The clone runs on your machine, with your GitHub484+credentials.485+443486 Then point existing clones at the forge:444487 `git remote set-url origin git@git.example.com:dami/<repo>.git`.445488 446-Everything on the forge is public: pages, sites, and HTTPS clones. Keep447-private repos where they are for now, or put the whole forge behind auth (e.g.448-`basic_auth` in the Caddyfile, or Tailscale).489+A public repo is public everywhere: pages, sites, HTTPS clones and the pages490+mirror. A private one is only read over ssh for now; pages for it, behind a491+login, come later (see [Private repos](#private-repos)).449492 450493 ## Backups and moving hosts451494 @@ -460,9 +503,10 @@ hasn't run, so a box that was off catches up; a failed one is retried hourly.
460503 461504 Everything in `/srv/git` that can't be rebuilt:462505 463-- every bare repo, `<owner>/<repo>.git`, with its config and its whole464- `forge/` directory: review logs, comments, collaborators, and anything else465- kept there;506+- every bare repo, `<owner>/<repo>.git`, with its config, its507+ `git-daemon-export-ok` if it has one (so a private repo comes back508+ private), and its whole `forge/` directory: review logs, comments,509+ collaborators, readers, and anything else kept there;466510 - `.forge/keys`, `.forge/config`, and `.forge/www/hosts/`, which hostname each467511 site has;468512 - `.hostkeys/`, so clients see the same ssh host key, and `.tailcat/`, so a@@ -527,7 +571,8 @@ waits `FORGE_MIRROR_DELAY` seconds (30) after each build and only the newest
527571 one syncs, so a burst of pushes makes one sync. It never replaces the copy528572 with an empty forge. The edge serves this copy while slate is down (see529573 [above](#home-machine-behind-a-public-edge-how-srcrest-runs)). It holds530-public content only, the pages the forge serves anyway.574+public content only, the pages the forge serves anyway. When a repo goes575+private, the next sync deletes its pages there.531576 532577 ### Setting it up533578 @@ -754,4 +799,5 @@ may be missing needs an explicit check or `or ''`.
754799 - Commenting and approving from the web pages; for now both come from the755800 terminal.756801 - Bugs or issues in the args.io style.757-- Private repos.802+- Pages for private repos, behind a login. For now private repos are read803+ over ssh only.@@ -270,7 +270,7 @@ log sha256:6cc79019... 4 lines
270270 <figcaption><b>fig. 9</b>or a plain VPS</figcaption>271271 </figure>272272 </div>273-<p class="prose">Either way, Caddy serves the pages directory as plain files and hands on only three things: clones to <code>git http-backend</code>, <code>/events/</code> to the events server and, with a pages domain, sites to the pages server. The pages server runs as the caddy user, because deploys are untrusted content. Everything on the forge is public: pages, sites and clones.</p>273+<p class="prose">Either way, Caddy serves the pages directory as plain files and hands on only three things: clones to <code>git http-backend</code>, <code>/events/</code> to the events server and, with a pages domain, sites to the pages server. The pages server runs as the caddy user, because deploys are untrusted content. A public repo is public everywhere: pages, sites and clones. A private one (no <code>git-daemon-export-ok</code>) is read over ssh only, by its owner, collaborators and readers, and has no pages.</p>274274 </section>275275 276276 <section class="commit" id="cli" aria-labelledby="cli-h">@@ -324,7 +324,7 @@ log sha256:6cc79019... 4 lines
324324 <header><p class="ref"><b>09</b> not yet</p><h2 id="not-yet-h"><a href="#not-yet">What isn't there</a></h2></header>325325 <ul class="gaps">326326 <li><b>Issues.</b> Bugs and tasks in the args.io style don't exist yet.</li>327-<li><b>Private repos.</b> Everything is public. Keep private repos elsewhere, or put the whole forge behind auth, such as <code>basic_auth</code> in the Caddyfile or Tailscale.</li>327+<li><b>Pages for private repos.</b> Private repos are read over ssh only for now; pages for them, behind a login, come later.</li>328328 <li><b>Notifications.</b> Nothing emails you. Open pages update themselves, and that is all.</li>329329 <li><b>Buttons.</b> Pages only show things. Landing, deploying and administering happen through git, ssh and <code>forge</code>.</li>330330 <li><b>CI on the server.</b> By design: it runs where the code is written, and the server checks signatures.</li>