clone

a personal forge on plain git: static pages, stacked-diff reviews, signed local ci, repo sites and releases

The guide says what forge is; internals says how #23

3 commits: 1 unchanged, 2 rewritten.

rewritten 8225fbc → 2938c4b The guide says what forge is; how it works moves to internals cosmetic
@@ guide/internals.html: deleted 1788292812 dami <spa
<tr><th scope="row">pages server</th><td data-l="listens"><code>127.0.0.1:8092</code></td><td data-l="as">caddy</td><td>Only with <code>FORGE_PAGES_DOMAIN</code>: sites on their own hostnames. Runs as caddy because deploys are untrusted.</td></tr>
<tr><th scope="row"><code>forge build</code></th><td data-l="listens"></td><td data-l="as">git</td><td>Once at boot, in the background, since templates may have changed with the image; after that, after each push.</td></tr>
<tr><th scope="row">backup loop</th><td data-l="listens"></td><td data-l="as">root</td><td>Only with <code>FORGE_BACKUP_REPOSITORY</code>: runs <code>forge backup</code> once a day at <code>FORGE_BACKUP_AT</code>, catching up after downtime, and prints its health to the container log.</td></tr>
- <tr><th scope="row"><code>forge mirror</code></th><td data-l="listens"></td><td data-l="as">git</td><td>Only with <code>FORGE_MIRROR_S3</code>: after builds, syncs the forge's pages to the bucket the edge falls back to.</td></tr>
+ <tr><th scope="row"><code>forge mirror</code></th><td data-l="listens"></td><td data-l="as">git</td><td>Only with <code>FORGE_MIRROR_S3</code>: after builds, at most hourly, syncs the forge's pages to the bucket the edge falls back to.</td></tr>
-<tr><th scope="row">Caddy</th><td data-l="listens"><code>:8080</code></td><td data-l="as">caddy</td><td>In the foreground. Plain HTTP; TLS ends at the edge. Pages, <code>.git</code> files, <code>go get</code> meta tags, and the two proxies.</td></tr>
+<tr><th scope="row">Caddy</th><td data-l="listens"><code>:8080</code></td><td data-l="as">caddy</td><td>In the foreground. Plain HTTP; TLS ends at the edge. Pages, <code>.git</code> files, <code>go get</code> meta tags and the two proxies.</td></tr>
</tbody>
@@ guide/internals.html: deleted 1788292812 dami <spa
-<div><dt>FORGE_BACKUP_REPOSITORY, _PASSWORD, _KEY_ID, _SECRET</dt><dd>The restic repository (an R2 bucket), its password, and the bucket's keys. Keep the password off the box.</dd></div>
+<div><dt>FORGE_BACKUP_REPOSITORY, _PASSWORD, _KEY_ID, _SECRET</dt><dd>The restic repository (an R2 bucket), its password and the bucket's keys. Keep the password off the box.</dd></div>
<div><dt>FORGE_BACKUP_AT, _KEEP_DAILY, _KEEP_WEEKLY</dt><dd>When the daily backup runs (UTC) and what retention keeps: 03:30, 7 and 5.</dd></div>
- <div><dt>FORGE_MIRROR_S3, _KEY_ID, _SECRET, _DELAY</dt><dd>The pages mirror's bucket and keys, and how long it waits after a build: 30 seconds.</dd></div>
+ <div><dt>FORGE_MIRROR_S3, _KEY_ID, _SECRET, _DELAY, _MIN_INTERVAL</dt><dd>The pages mirror's bucket and keys, how long it waits after the last build, 600 seconds, and how long at least between syncs, 3600 seconds.</dd></div>
</dl></section>
@@ guide/internals.html: deleted 1788292812 dami <span class="dim"># only o
</div>
@@ guide/internals.html: deleted 1788292812 dami <spa
+<p><strong>Repo settings</strong> come from <code>repo_setup</code>, when a repo is created (<code>repo new</code>, <code>ssh new</code>, a push to a missing repo, an import) and for every repo on <code>forge server init</code>, which the container runs on boot: the hooks, <code>receive.advertisePushOptions</code>, <code>transfer.hideRefs refs/forge</code> so clones, fetches and pushes never see the pinned revisions (the build reads them directly; pre-receive also refuses them), and <code>commitGraph.changedPathsVersion <wbr>2</code>, filters that hash non-ASCII paths the same on every platform.</p>
+<p><strong>Backups</strong>: <code>forge backup</code> stages a copy of <code>/srv/git</code> without what a restore rebuilds (pages, extracted sites, caches, generated keys files, hooks, logs), uploads it with restic, prunes old snapshots, runs <code>restic check</code> and restores the new snapshot into a scratch directory to <code>git fsck</code> every repo and compare its refs. The copy is consistent mid-push: per repo it copies review logs and comments, then reads the refs, then hard-links the objects, which git writes before anything names them. It refuses a forge with no repos, or half the last backup's, so a new box can't push good snapshots out. <code>forge-admin backups</code> says how the last one went, and loudly when it failed or is a day and a half old.</p>
<p><strong>Moving</strong>: <code>forge restore latest</code> on an empty forge unpacks a snapshot and rebuilds the rest: what <code>server init</code> and <code>keys sync</code> write, hooks, pinned revisions, site files, pages. The restored tailcat key keeps the tunnel address, so the edge follows the forge to a new box by itself. The README walks through a new slate and a VPS.</p>
- <p><strong>The offline copy</strong>: after builds, <code>forge mirror</code> syncs the forge's own pages, without sites or previews, to an R2 bucket. When slate doesn't answer, the edge's Caddy serves them from there with <code>X-Forge-Offline: mirror</code>; sites, clones and pushes wait for slate.</p>
+-<p><strong>The offline copy</strong>: after builds, at most hourly, <code>forge mirror</code> syncs the forge's own pages, without sites, previews, or the pages of single files and folders, to an R2 bucket. When slate doesn't answer, the edge's Caddy serves them from there with <code>X-Forge-Offline: mirror</code>, and a page the copy leaves out is a 404 that says so; sites, clones and pushes wait for slate.</p>
++<p><strong>The offline copy</strong>: after builds, at most hourly, <code>forge mirror</code> syncs the forge's own pages, without sites, previews or the pages of single files and folders, to an R2 bucket. When slate doesn't answer, the edge's Caddy serves them from there with <code>X-Forge-Offline: mirror</code>, and a page the copy leaves out is a 404 that says so; sites, clones and pushes wait for slate.</p>
<p><strong>Updates</strong>: <code>deploy/slate/deploy.sh</code> rsyncs the checkout to the box and rebuilds the container; the boot build refreshes every page. A change to <code>lib/</code> or <code>site/</code> changes the code hash in every fingerprint, so that first build renders everything.</p>
-<p><strong>Logs</strong>: <code>.forge/build.log</code> for builds, <code>.forge/events.log</code> for the events server, and <code>backup.log</code> and <code>mirror.log</code>, kept under a megabyte. <code>events.jsonl</code> is cut back to its last thousand lines once it passes a megabyte.</p>
+<p><strong>Logs</strong>: <code>.forge/build.log</code> for builds, <code>.forge/events.log</code> for the events server, and <code>backup.log</code> and <code>mirror.log</code>, kept under a megabyte. <code>events.jsonl</code> is cut back to its last 1,000 lines once it passes a megabyte.</p>
1 unchanged commit
unchanged c1a940e → 719bd89 cat and fox are the people in the README and /design/
rewritten 1321507 → 00f0dc7 The README follows AP style, and DESIGN.md says docs do cosmetic
@@ README.md: ssh command on.
git push git@git.example.com:cat/notes.git main
@@ README.md: forge (without `--yes` it says what would go); collaborators can't, and
either root, sites, previews, hostnames, release downloads and insights go
- at once, and the mirror's next sync deletes its pages. The bare repo waits in
+ at once, and a mirror sync within 30 seconds deletes its pages. The bare repo waits in
`.forge/trash/<owner>/<repo>.git.<time>` for `FORGE_TRASH_DAYS` (14) days,
-backed up like the rest of the forge: `forge repo restore <owner>/<repo>`
+backed up like the rest of the forge. `forge repo restore <owner>/<repo>`
@@ README.md: ssh git@git.example.com ls # yours, and th
-hostnames, moves its release downloads into the private root (and their
-files into the private store, see [Releases](#releases)), and builds
-its pages again there. Then it starts
--a mirror sync, which deletes them from the bucket. Clones, caches and copies
+-a mirror sync within 30 seconds, which deletes them from the bucket. Clones, caches and copies
+hostnames. It moves its release downloads into the private root (and
+their files into the private store, see [Releases](#releases)) and builds
-+its pages again there. Then it starts a mirror sync, which deletes them
-+from the bucket. Clones, caches and copies
++its pages again there. Then it starts a mirror sync within 30 seconds,
++which deletes them from the bucket. Clones, caches and copies
people already have can't be recalled. `public` first runs the secret check
over the whole history: every commit on a branch or tag, and the old review
revisions that review pages show, not only what a push adds. If it finds
@@ README.md: open (`docker compose stop forge`, then `docker compose run --rm forg
nothing replicates until the next boot. `forge restore` takes whichever of
the backup's copy and the replica changed last (every write, deletions
too, stamps the file), which is litestream's unless replication had
-@@ README.md: waits `FORGE_MIRROR_DELAY` seconds (30) after each build and only the newest
- one syncs, so a burst of pushes makes one sync. It never replaces the copy
- with an empty forge. The edge serves this copy while slate is down (see
- [above](#home-machine-behind-a-public-edge-how-srcrest-runs)). It holds
--public content only, the pages the forge serves anyway, and public repos'
-+public content only: the pages the forge serves anyway and public repos'
- release downloads. When a repo goes private, the next sync deletes its
- pages and downloads there.
-
@@ README.md: What you set up yourself, once:
5. **The edge**: set `FORGE_MIRROR_URL` in `deploy/edge/fly.toml` to the
bucket's public URL (`FORGE_DOMAIN` is set already), then `fly deploy`. Its