--- /dev/null
+# Host setup for quire
+
+Reference configs for dispatching SSH connections into the quire container.
+
+## Files
+
+- `sshd_config` — drop into `/etc/ssh/sshd_config.d/` on the host
+- `quire-dispatch` — copy to `/usr/local/bin/quire-dispatch` and `chmod +x`
+
+## Setup
+
+1. Create the git user on the host:
+
+ sudo useradd --system --create-home git
+
+2. Add your public key:
+
+ sudo mkdir -p /home/git/.ssh
+ sudo cp ~/.ssh/id_ed25519.pub /home/git/.ssh/authorized_keys
+ sudo chown -R git:git /home/git/.ssh
+ sudo chmod 700 /home/git/.ssh
+ sudo chmod 600 /home/git/.ssh/authorized_keys
+
+3. Install the dispatch script:
+
+ sudo cp quire-dispatch /usr/local/bin/quire-dispatch
+ sudo chmod +x /usr/local/bin/quire-dispatch
+
+4. Install the sshd config:
+
+ sudo cp sshd_config /etc/ssh/sshd_config.d/quire.conf
+ sudo systemctl reload sshd
+
+5. Start the quire container:
+
+ docker run -d --name quire-container quire
+
+6. Test:
+
+ git clone git@localhost:foo.git /tmp/test-clone
+
+## Notes
+
+The dispatch script is the security boundary between the host and the container.
+It only allows `git-receive-pack`, `git-upload-pack`, and `git-upload-archive`.
+Repo paths are validated: no `..` traversal, must end in `.git`, no double slashes.
+
+When `quire exec` is built (step 2), the ForceCommand will change to:
+
+ ForceCommand docker exec -i quire-container quire exec "$SSH_ORIGINAL_COMMAND"
+
+and this dispatch script will be replaced by the quire binary's own allowlist.
--- /dev/null
+#!/usr/bin/env bash
+# /usr/local/bin/quire-dispatch
+#
+# Invoked by sshd ForceCommand for the git user.
+# Parses $SSH_ORIGINAL_COMMAND to extract the repo path and git command,
+# then dispatches into the quire container via docker exec.
+#
+# $SSH_ORIGINAL_COMMAND examples:
+# git-receive-pack '/foo.git'
+# git-upload-pack '/foo.git'
+# git-upload-archive '/foo.git'
+
+set -euo pipefail
+
+# shellcheck disable=SC2154
+cmd="${SSH_ORIGINAL_COMMAND:-}"
+
+if [[ -z "$cmd" ]]; then
+ echo "quire: interactive sessions are not supported" >&2
+ exit 1
+fi
+
+# Extract the git subcommand (git-receive-pack, git-upload-pack, etc).
+git_cmd="${cmd%% *}"
+
+# Extract the repo path argument. Git sends it as a quoted string,
+# e.g. git-receive-pack '/foo.git'. Strip the leading command and
+# surrounding quotes.
+repo="${cmd#* }"
+repo="${repo#\'}"
+repo="${repo%\'}"
+repo="${repo#/}"
+
+case "$git_cmd" in
+ git-receive-pack|git-upload-pack|git-upload-archive)
+ ;;
+ *)
+ echo "quire: unsupported command: $git_cmd" >&2
+ exit 1
+ ;;
+esac
+
+# Validate repo path: no .., must end in .git, no double slashes.
+if [[ "$repo" == *".."* ]] || [[ "$repo" != *.git ]] || [[ "$repo" == *//* ]]; then
+ echo "quire: invalid repository: $repo" >&2
+ exit 1
+fi
+
+exec docker exec -i quire-container \
+ /bin/sh -c "cd /var/quire/repos/${repo} && ${git_cmd} ."
--- /dev/null
+# /etc/ssh/sshd_config.d/quire.conf
+#
+# Drop this file into sshd_config.d (or append to sshd_config).
+# Forces all connections as the git user through the quire dispatch script.
+# No port forwarding, agent forwarding, X11, or PTY — these are git/quire
+# connections, not interactive shells.
+
+Match User git
+ AuthorizedKeysFile /home/git/.ssh/authorized_keys
+ ForceCommand /usr/local/bin/quire-dispatch
+ AllowTcpForwarding no
+ AllowAgentForwarding no
+ X11Forwarding no
+ PermitTTY no