A markdown file in the repository, rendered.

barerepo / server / plans/docs/BUILD.md
rendered
log files threads runs releases config jump to file t

barerepo build guide

Read the book first, in the barerepo/book repository. This file is the order of operations.

Stack

Nothing here is mandatory, but each choice follows from a rule in chapter 5 of the book.

  • Go. Single static binary, no runtime to install, cross-compiles for the runner on windows/macos/linux from one build host.
  • Server-side HTML templates. Rule 4 forbids a client framework. html/template is sufficient. There is no build step, no bundler, no node_modules.
  • SQLite or PostgreSQL for the server-owned items in chapter 10 of the book, plus indexes. Not for repo content. One [database] url picks which; see chapter 41.7.1. SQLite is the default and is the right answer for almost every install. Postgres exists for people who already run one. Write against database/sql and keep the schema to what both dialects accept, so neither one becomes the only one that is actually tested.
  • libgit2 bindings or shelling out to git. Start by shelling out; it is faster to get right and the process overhead is invisible next to network time. Optimize only if profiling says so.
  • No ORM. A handful of tables, all of them on chapter 10's closed list. Two dialects of DDL, hand-written, and one place that turns ? into $1 for Postgres.

Order

Each stage should be independently usable. Do not build stage N+1 until N works.

1. Git over ssh and http. Serve git-upload-pack and git-receive-pack. Authenticate ssh by public key. At the end of this stage you can clone and push, and nothing else exists. This is the whole product's foundation; if it is not fast and correct, nothing above it matters.

2. Read-only web views. Log, file tree, file view with blame gutter, commit, compare. No accounts yet, public repos only. Mockups: repo-log.html, repo-files.html, repo-file.html, repo-commit.html, repo-compare.html.

Note the landing page is the log, not the file tree. Nobody navigates code by clicking folders. Every row on it is the same row: what changed, by how much, and a the hash as the link to it. No diff is drawn there.

3. Accounts. Signup, signin, keys page. Nonce challenge flow. Namespace ownership. Mockups: signup.html, signin.html, keys.html, profile.html, new-repo.html, repo-empty.html.

4. Proposals. The pre-receive hook that allocates refs/proposals/<n>. The post-receive hook that detects reachability and closes. Compare view against a proposal ref. Mockup: push-rejected.html. This page matters more than it looks, because rejection is where new contributors get stuck.

5. Threads. Notes storage, union merge strategy, comment rendering, the unified list. Mockups: threads.html, thread.html, thread-new.html.

6. Runners. Token issue and revoke. Runner protocol: the runner dials out and long-polls; it never needs an inbound port, which is what makes "paste one line on your laptop" actually work. Mockups: runner-setup.html, runners.html, runs.html, run.html.

6A. GitHub workflows. Read .github/workflows when [build] command is absent, translate the run: steps, and decline everything else by name. Match runs-on against attached runners and point at the add-a-runner page when none fits. Chapter 15A. The rule that makes it worth having is that a skipped step is never silent.

7. Search. One index, one result set across code, threads and repos. Filter every query against the requesting user's readable set, per chapter 17. Mockup: search.html.

8. Feeds and the inbox. The event log, the inbox page, Atom output, feed tokens. Nothing before this stage tells anyone that anything happened, so it is not optional. Mockup: inbox.html.

9. The rest. Releases and artifacts, webhooks with the SSRF deny list, server side copy, rename with permanent redirects, archiving. Mockup: releases.html. Chapters 20 to 23A cover these; none of them changes anything below them.

The runner command

This is the piece to get exactly right, because it is the most visible proof of the no-interstitial thesis.

curl -sL barerepo.sh | sh -s rt_live_7Kq2mXe

The token is in the copied line. There is no "now go to settings and paste this" step, no OS tab to click, no config file to create. One copy, one paste, the machine is attached and the page it was copied from updates.

Requirements:

  • Token embedded in the displayed command, per repo, revocable from keys.html.
  • Three platforms shown simultaneously. Do not use tabs, because tabs hide two thirds of the answer to save nine lines of space.
  • Runner dials out. No inbound port, no public address, works from a laptop behind NAT.
  • Same single binary as the server, different subcommand.

Performance budget

Treat these as build-failing thresholds, not aspirations. They are asserted in the test suite and are not shown to users.

View Time Payload
Any page with no diff under 10ms under 15kb
Log under 20ms under 30kb
Any page under 2kb JS
Raw file content separate host, or headers per 42.3

Diff rendering is the one genuinely hard case. Cache rendered diffs by blob pair hash; they are immutable, so the cache never needs invalidation.

Traps

  • Do not hardcode master or main. Read the repo's HEAD. Some third-party tooling assumes main; your own CI must not.
  • Do not add a merge button. Every request for one is a request to become GitHub. The answer is a fetch command.
  • Do not let the notes conflict problem slide to launch. It is the single most likely thing to make threads look broken to a second user.
  • Rate limit proposal refs per key per repo. Rule 5 is an open door.
  • Expire unreferenced proposal refs or the repo grows without bound.
  • Filter search by read access inside the query. Filtering after ranking leaks the count of private matches. This is the highest-severity mistake available in this codebase.
  • Reject oversized blobs in pre-receive, naming the file. LFS is off by default and a size limit is what makes that decision hold.
  • Deny private address ranges for webhooks, resolved and re-checked after every redirect.
  • Derive the reserved-name list from the route table, per chapter 42.5. A route added without a reservation is a route an account name can shadow.
  • The owner is exempt from the archived flag. Otherwise archiving is a one-way door, because unarchiving arrives by push. Chapter 21.3.
  • Tests run on SQLite. It needs no service, so every test gets a fresh empty database. Guard the Postgres schema with a test that compares the two DDL sets column by column; that test needs no server. Run the suite against a real Postgres before a release, not on every commit.
source · rawbarerepo 0.1.0