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 new <name>`).
+- **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.
- **Automatic mirroring to GitHub** on push, when configured per-repo. Each repo carries its own deploy key — no agent socket to plumb across the host/container boundary.
- **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.
## Status
-Design phase. See `PLAN.md` for the build sequence and open questions.
+Early development. SSH dispatch, repo management, and Fennel config loading work; web view, CI, mirroring, and notifications are still ahead. See `PLAN.md` for the build sequence and open questions.
- **Web visibility: public by default, per-repo opt-outs.** Repos are public (they go to GitHub anyway); CI logs require auth. Per-repo `(private true)` and `(public_runs true)` flags cover the exceptions.
- **Trust the proxy-injected identity header.** `Remote-User` is trusted because the reverse proxy is the only ingress. Proxy must strip any client-supplied version before injecting its own — this is the security-critical invariant.
- **Explicit repo creation, not implicit on first push.** `ssh git@host quire new <n>`. No magic, no shims parsing first pushes.
-- **Hooks via `hook.<n>.command` config.** Git 2.36+. No shim scripts on disk; `hook.<n>.command = /usr/local/bin/quire hook <n>`. Set at creation time.
+- **Hooks via `hook.<n>.command` config.** Git 2.54+ (the version we build into the container image). No shim scripts on disk; `hook.<n>.command = /usr/local/bin/quire hook <n>`. Set at creation time.
- **Mirror push is synchronous in post-receive.** Slow GitHub = slow push. Worth it; silent drift is the worst outcome.
- **No reverse-direction mirroring.** quire is the source of truth; GitHub is the replica.
- **CI pipelines are Fennel macros, not data tables.** The whole point is real code. Shared steps can be factored into `.quire/lib/*.fnl` and `require`'d.
- **Vendored Fennel compiler** — `fennel.lua` from upstream (BSD-3,
single Lua file). Bundled via `include_str!`, registered into the VM
as a module at construction.
-- **`Fennel` struct** — owns the `Lua` instance and a reference to the
- loaded `fennel` module. Constructed once per process; `load_file` and
- `load_string` are methods.
+- **`Fennel` struct** — owns a `Lua` instance with the Fennel compiler
+ registered as a Lua global. `load_file` and `load_string` are methods
+ that look the global up on each call.
## Decisions
:on [:ci-failed :mirror-failed]}}
```
-One `Fennel` per process, reused across loads. Hooks load 1–2 files;
-`quire serve` loads many. Avoids re-loading the compiler on each
-call. Cheap enough that tests construct freely.
+Today each call site (`Quire::global_config`, `Repo::config`)
+constructs a fresh `Fennel`. Cheap enough at current call volume.
+Reusing a single instance across loads is a planned optimization for
+when `quire serve` lands and starts loading per-request.
`load_string` is the primitive; `load_file` wraps it. Per-repo config
comes from `git show` stdout, not a path on disk, so the string form is
and any line/column info Lua surfaces. Hook log lines should point at
the offending file and line, not just "syntax error."
-New top-level `src/fennel.rs`. Used by the still-to-come
-`src/config/global.rs` and `src/config/repo.rs`.
+Lives in `src/fennel.rs`. Used by `Quire::global_config` and
+`Repo::config` in `src/quire.rs`, which also define the `GlobalConfig`
+and `RepoConfig` schemas.
## Contracts
Errors: file-not-found, parse error, eval error, type mismatch — all
`miette::Result` with named source labels where Lua provides them.
-## Out of scope
+## Related modules
-- `SecretString` / `!cmd` resolution — chunk 2. Fennel produces plain
- strings; `SecretString` is a `serde` newtype that resolves on access.
-- `git show HEAD:.quire/config.fnl` plumbing — chunk 3.
-- Any `mirror`/`notifications`/`private` schema — defined when chunks 2
- and 3 land.
+- `src/secret.rs` — `SecretString` wraps Fennel-loaded strings that
+ resolve from a file or shell command on access.
+- `src/quire.rs` — `Repo::config` reads per-repo Fennel via `git show
+ HEAD:.quire/config.fnl`; `Quire::global_config` reads the global
+ config from disk. Both define the schema structs they parse into.
## Test plan
--user "$(id -u git):$(id -g git)" \
-e HOME=/tmp \
-v /var/quire:/var/quire \
+ -p 127.0.0.1:3000:3000 \
quire
In a compose file, the equivalent is `user: "${QUIRE_UID}:${QUIRE_GID}"`
5. Create a test repo inside the container:
- docker exec quire-container quire new foo.git
+ docker exec quire-container quire repo new foo.git
6. Test the dispatch path:
otherwise match a container user. `HOME=/tmp` is set because the host
uid has no `/etc/passwd` entry inside the container, and git needs a
writable `HOME` for its config probing.
+
+The HTTP port (3000) is published to host loopback only. A reverse proxy
+on the host terminates TLS and reverse-proxies to it; nothing else
+should reach it directly.