The validate_repo_name free function is now Repo::validate_name, a
private method. Repo gains two constructors: new(base, name) for
name-based construction and from_path(base, path) for path-based
construction. Quire::repo and Quire::repo_from_path delegate to these.
Canonicalize GIT_DIR in hook before resolving repo
git sets GIT_DIR to "." when invoking hooks via hook.<name>.command.
The repo resolver strips a prefix to find the repo name, so a relative
path fails the prefix check. Canonicalize in the hook so the resolver
gets an absolute path.
Gitweb ships with git (already built from source), so adding it is
just a build step, lighttpd, perl, and two config files. The container
entrypoint is now lighttpd instead of quire serve — SSH dispatch via
docker exec still works since quire stays on the PATH.
All gitweb additions are tagged TODO(vys) for easy grep when the
native web view replaces them.
Push actual refs to mirror instead of hardcoding main
The post-receive hook now parses stdin to find which refs were updated
and pushes only those. This handles repos with any default branch name,
not just main. Deleted refs (all-zero new sha) are skipped.
Use cargo-chef to cache Rust dependencies in Docker builds
Separates dependency compilation from source builds so that changing
application code no longer triggers a full dependency rebuild. Adds
cargo/git/db cache mount alongside the existing registry cache.
Based on https://quanttype.net/p/perfect-dockerfile-for-rust-backends/
The standalone git-receive-pack binary lives in libexec/git-core/, which
is not on PATH in the container. Using `git receive-pack` lets the git
binary handle the dispatch internally.
Move the push out of the hook into Repo::push_to_mirror so the actual
push logic is unit-testable independent of GIT_DIR/stdin orchestration.
Reject mirror URLs that embed credentials at deserialization, since a
typo would leak the token via tracing or Sentry. Make terminal-stdin
invocation an error rather than a silent no-op — running the hook
manually is a misuse, not a state.
Documents the env-var token exposure (visible in /proc/<pid>/environ)
as a known limitation in the helper's docstring; revisit when the CI
runner lands. Updates README to describe the PAT approach instead of
the per-repo deploy key the original design called for.
Implement post-receive hook to push main to mirror
Post-receive reads GIT_DIR to resolve the repo, loads per-repo config
for the mirror URL and global config for the GitHub PAT, then pushes
main to the mirror. No mirror configured = no-op. Token is passed via
GIT_CONFIG env vars, never written to disk.
Also adds Quire::repo_from_path for resolving hooks that receive GIT_DIR,
and makes Repo::git public for use in command handlers.
The audit found docs claiming `quire new` (CLI is `quire repo new`),
git 2.36+ (Dockerfile builds 2.54), and a stale fennel.md describing
a once-per-process Fennel that the code constructs per-call. README's
"Design phase" status also undersold actual progress.
Polish docs and fix per-repo config drift in PLAN.md
PLAN.md placed per-repo config in the bare repo's quire/ directory, but
the code reads HEAD:.quire/config.fnl. Other edits are clarity polish
from a technical-writing pass.
Optional sentry config in global config.fnl with SecretString DSN.
Best-effort init in main — logs a warning and continues if config is
missing, DSN resolution fails, or Sentry is not configured. Uses rustls
to avoid native OpenSSL dependency.
Check HEAD existence with rev-parse instead of parsing stderr
Two-command approach: git rev-parse --verify HEAD uses exit codes
reliably, then git show only runs when HEAD exists so any failure means
the file is absent. No more fragility from matching stderr text.
Use more accurate error variants in Repo::config()
Unexpected git failures now surface as Error::Git rather than
Error::NotFound, and UTF-8 decoding failures use Error::Io with
InvalidData rather than Error::NotFound.
Repo::config() reads HEAD:.quire/config.fnl from bare repos, returning
a default RepoConfig when HEAD is missing (fresh repo), the file is
absent, or the :mirror key is omitted. Malformed Fennel surfaces as a
miette error with source labels pointing at the right line.
This is the foundation for per-repo mirror URLs — the config lives in
the repo's content, not in server-side operator state.
Re-declare GIT_VERSION inside the build stage so it expands
A pre-FROM ARG is a global build arg and is not in scope inside any
build stage. With it unset in the stage, ${GIT_VERSION} in the curl
URL expanded to an empty string, so we fetched
github.com/git/git/archive/refs/tags/v.tar.gz, got a 404, and the
piped tar exited 2. BuildKit reports the entire RUN command in its
error, which made it look like make was the failing step.
Verified locally: with `ARG GIT_VERSION` re-declared inside the
stage, the variable expands to 2.54.0 as intended.
Quire now stores a single base_dir (/var/quire) instead of
separate repos_dir and config_path fields. GlobalConfig parses
the Fennel config file and exposes github.token as a SecretString.
Missing config produces a typed ConfigNotFound error.
Config was a thin wrapper around repos_dir. Quire now owns all
configuration directly and gains a config_path field, in
preparation for the global config loader.
The OnceLock was vestigial on Plain secrets. Bundles two
related fixes: strip_suffix preserves all but one trailing
newline (Docker convention), and from_plain/from_file are
now public.
Claude [Sun, 26 Apr 2026 01:56:08 +0000 (01:56 +0000)]
Install perl in git-builder stage so make can build git
debian:trixie-slim ships without perl, but git's Makefile invokes
/usr/bin/perl during the build to generate sources (command-list,
GIT-VERSION-FILE, perl/Makefile via ExtUtils::MakeMaker, etc.).
Without it, `make` aborts almost immediately with exit 2, which is
what run 24932667835's Build job hit.
The git INSTALL doc lists Perl 5.26+ as a required build dep.
Claude [Sun, 26 Apr 2026 01:50:39 +0000 (01:50 +0000)]
Drop unused tooling from CI test job
grcov, cargo-mutants, llvm-tools, and astral-sh/setup-uv were left over
from the coverage and mutation steps disabled in b33250f. Installing
them on every run added several minutes and a transient failure surface
without exercising anything. The leading comment now records what to
restore when those steps come back.
Untagged serde enum accepts a plain string or a file path table
({:file "/run/secrets/name"}). File contents are resolved lazily
on first call to reveal() and cached via OnceLock. Debug impl
redacts the value. Missing files produce a typed SecretResolve error.
extract_line_offset split on : from the left, so names like
HEAD:.quire/config.fnl broke the parser. Use regex-lite to match
the first :LINE:COLUMN: run instead, which is unambiguous.
Embed Fennel compiler and implement load_string round-trip
Vendor fennel.lua 1.5.1 and wrap it behind a Fennel struct that owns
a Lua VM. Uses mlua with lua54 + serde features so fennel.eval results
deserialize directly into typed Rust structs. Uses unsafe_new because
Fennel needs the debug standard library internally.
Extract Quire struct to replace Repo and Config threading
Repo was a thin validated-name wrapper that required callers to pass
repos_dir alongside it at every call site. Quire holds the config and
provides repo() -> PathBuf and repos() -> Iterator, collapsing two
arguments into one at every command handler.
All repo name validation now goes through Repo::from_name(). The exec
command strips the SSH leading slash before calling it, keeping the
Repo type focused on name rules only. Removed the duplicate
validate_repo_path function from exec.rs.
New `quire repo` subcommand group with new/list/rm. Name validation
rejects traversal, deep nesting, missing .git suffix, and reserved
segments (.git). The exec allowlist dispatches `quire repo` to the
binary. Post-receive hook is configured system-wide via gitconfig
instead of per-repo.
Trixie ships newer packages overall. Git still needs building from
source (trixie has 2.47, we need 2.54+) but trixie is a better
foundation going forward.
Build git 2.54 from source and add hook subcommand
Debian bookworm ships git 2.39 but we need 2.54+ for
hook.<name>.command config support, which lets quire register
hooks via git config instead of writing shim scripts to disk.
The hook subcommand is a no-op that logs invocations — enough to
verify the dispatch path works end-to-end.
Bind-mounted repos under /var/quire are owned by the host's git user;
baking a quire user with a different uid into the image triggered
git's safe.directory "dubious ownership" check. Setting the user at
docker run time keeps ownership aligned end-to-end.
Clippy and miette conventions are already enforced by tooling.
Use "just all" instead of "cargo test" since the justfile is
the canonical check runner.
Simplify Config to Default and use miette bail/ensure macros
Config::load() with env var parsing was premature. Default impl is
enough for now. Switch exec.rs from if/return Err(miette!) to
ensure! and bail! for idiomatic miette usage.
Switch from color-eyre to miette for error reporting
Miette provides better diagnostic output and integrates with thiserror
via the Diagnostic derive. Library errors now derive both Error and
Diagnostic. Binary uses miette::Result with into_diagnostic() for
third-party errors.
Implement quire exec dispatch with git command allowlist
Parses SSH_ORIGINAL_COMMAND, validates git commands against an
allowlist (receive-pack, upload-pack, upload-archive), sanitizes
repo paths, and execs the git subprocess. Removes the separate
quire-dispatch shell script — the binary handles dispatch directly.
Updates host reference configs to use quire exec in ForceCommand.
Replace binary release with Docker image build and push
Multi-stage Dockerfile builds the Rust binary then copies it into a
slim runtime image with git and ca-certificates. CI workflow follows
the ketchup pattern: single ci.yml with test, zizmor, and build jobs.
Build pushes to GHCR on green main, tags with YYYYMMDD-<short-sha>,
and creates a GitHub release. Removes the old release workflow and
dotslash config.
sshd_config Match block, quire-dispatch script with repo path
validation and git-command allowlist, and a README covering setup
steps. These are reference configs versioned with the repo — the
dispatch script will be replaced by quire exec in step 2.
Minimal image: debian bookworm, git, quire user, bare test repo
at /var/quire/repos/foo.git. Entrypoint passes through to docker
exec invocations. No sshd, no quire binary yet.
Scaffold Rust binary project with clap, CI, and tooling
Restructure from a minimal lib+main into a proper binary project
layout: library target with error types, separate binary target
under src/bin/quire/ with clap subcommands (serve, exec), CalVer
versioning via build.rs, integration tests, justfile, clippy config,
mutation testing config, and GitHub Actions CI/release workflows.