quire

A personal source forge. Single-user, self-hosted, minimal.

Named after the old bookbinding term: a gathering of folded leaves, sewn together. Your repos are quires; the whole thing is a quire of quires.

What it is

A Rust binary that runs in a Docker container, fronted by the host's sshd and a TLS-terminating reverse proxy. It gives you:

  • Git hosting over SSH, via the host's sshd dispatching into the container. Explicit repo creation (ssh git@host quire repo new <name>).
  • A read-only web view for browsing README, tree, history, blame, diffs, and refs.
  • Fennel-based CI (Fennel is a Lisp that compiles to Lua), with pipelines defined in .quire/ci.fnl. Unsandboxed by default since every pipeline is code I've written; a bubblewrap-based opt-in is available for the day quire ever runs code I haven't.
  • Automatic mirroring to GitHub, Gitea, or any HTTPS remote on every push. Map each remote URL to a push token in .quire/config.fnl under :mirrors (the value names an entry in the global :secrets); quire force-pushes every updated ref to all of them independently of CI.
  • Email notifications for CI failures and recoveries. SMTP via msmtp; plain text; per-repo config for what to send and to whom.

No issues, no PRs, no user management, no webhooks. Use the GitHub mirror for the social stuff; quire is your forge.

Post-v1, the feature I most want to build is a richer line/file history view — blame ladder, range-follow, rename trails — the thing every forge does poorly.

Creating a repository

Quire requires explicit repository creation — repos are not created automatically on first push.

Create the bare repo on the server:

ssh git@host quire repo new foo.git

Names must end in .git. One level of grouping is supported: work/foo.git is valid, a/b/c.git is not.

Then add the remote and push:

jj git remote add origin git@host:foo.git
jj git push

Other management commands: quire repo list and quire repo rm foo.git, invoked the same way via SSH.

Design principles

Quire holds to a few principles:

  • The container is pure quire. SSH auth and TLS/web auth both live on the host (host sshd, reverse proxy). The container runs quire, git, and msmtp. One job per surface.
  • Don't own ssh. The host's sshd handles auth, channels, and key management; ForceCommand dispatches authenticated invocations into the container via docker exec. Quire's integration point is git hooks and the quire exec dispatch target.
  • Web auth at the reverse proxy. The proxy (Caddy or equivalent) handles authentication and injects a trusted identity header. Quire reads the header and applies per-repo visibility: public repos are world-readable, private repos and CI logs require auth. Any auth mechanism the proxy supports (basic, OAuth, SSO) Just Works — quire stays scheme-agnostic.
  • Git's filesystem is the source of truth. Bare repos under /var/quire/repos/ are the primary artifact. CI run history is directories on disk, not a database. A database comes back only if the filesystem approach visibly fails.
  • Built for jj. The primary client is Jujutsu, which means routine force-pushes, short-lived refs, and unstable SHAs. No git-flow-shaped assumptions in the UI or CI.
  • Push should fail fast, loudly, and correctly. No silent drift between quire and GitHub. No accepted-but-unreplicated state.
  • Config is code. Global config is Fennel. CI pipelines are Fennel. If you're going to have a scripting language, have one.

Layout

Quire's data lives under one volume:

/var/quire/
  repos/           bare git repos; per-repo config lives in-tree at .quire/config.fnl
  runs/            CI run metadata, artifacts, and logs; retention-policied
  config.fnl       global config; see docs/config.md for the schema

Host-side config (sshd_config block, Caddyfile, docker-compose file) lives on the host, version-controlled separately. See docs/PLAN.md for the reference layout, docs/config.md for the global and per-repo Fennel schemas.

Status

Early development. SSH dispatch, repo management, Fennel config loading, and mirror push via event socket work; web view, CI, and notifications are still ahead. See docs/PLAN.md for the build sequence and open questions.