Skip to content
quarry Running quarry
Login

Running quarry

Two ways: the compose stack, or by hand. The hand-rolled path is what this was developed against and is what the compose file encodes, so it is written down first — a run book that stays true even if the images are never built.

What the stack is

piece role state
zit the store. quarry shells out to it for every read and write (0001 §2) the .zit files themselves
quarry the forge: indexes, UI, API, sync fronting derived indexes, embedded NATS, OLTP database
notify notifications: workflows, subscribers, topics, the inbox quarry embeds SQLite
dagnats the workflow engine notify runs on: embedded NATS + JetStream + an HTTP bridge JetStream data

zit is not a runtime dependency in the ordinary sense — it is quarry's only store interface, so its version is part of quarry's behaviour. On 2026-08-16 three zit builds changed what quarry does: frames got 31% faster, a card-parsing fix changed which artifacts parse at all (and until it landed, sqlite's 794 tickets were invisible), and a new stat -R let quarry serve a bare store. Pin the pair; do not let a deploy resolve zit from whatever is on PATH.

By hand

dagnats

Runs under launchd on this machine as com.dmestas.dagnats, binding NATS on 4222 and its bridge on 8080, with data in ~/Library/Application Support/dagnats.

launchctl kickstart -k gui/$(id -u)/com.dmestas.dagnats

A freshly-built Go binary replacing it must be ad-hoc signed or launchd kills it with OS_REASON_CODESIGNING and the service silently fails to come back:

go build -o ~/go/bin/dagnats ./cmd/dagnats
codesign -s - -f ~/go/bin/dagnats
launchctl kickstart -k gui/$(id -u)/com.dmestas.dagnats

In standalone mode dagnats binds loopback, which is why anything talking to it shares its network namespace under compose rather than reaching it by service name.

notify

notify bootstrap -dev -db notify.db -org quarry -env dev   # prints apiKey once
notify serve -dev -db notify.db -addr 127.0.0.1:3000 \
  -nats-url nats://127.0.0.1:4222 -bridge-url http://127.0.0.1:8080
curl -s localhost:3000/readyz     # {"status":"ok","bridge":true,"db":true}

-dev derives a fixed, publicly-known master key. Outside development set NOTIFY_MASTER_KEY_HEX.

Quarry triggers two workflows by name, so they must exist, each with an in_app step or nothing reaches an inbox:

K=<apiKey>
for wf in quarry-activity quarry-mention; do
  curl -s -X POST localhost:3000/v1/workflows -H "Authorization: ApiKey $K" \
    -H 'Content-Type: application/json' -d "{
      \"name\":\"$wf\",\"triggerIdentifier\":\"$wf\",
      \"steps\":[{\"stepId\":\"S1\",\"type\":\"in_app\",
        \"controlValues\":{\"subject\":\"{{payload.summary}}\",
                           \"body\":\"{{payload.repo}} — by {{payload.actor}}\"}}]}"
done

Activity is addressed to a topic, mentions to a person. A repository's activity goes to the topic repo-<name> (quarry sanitizes its own repo:<name> convention, because notify accepts only lowercase letters, digits, hyphens and underscores in a key and rejects the whole trigger otherwise). A topic with no members notifies nobody, so subscribing is a required step, not a nicety:

curl -s -X POST localhost:3000/v1/subscribers -H "Authorization: ApiKey $K" \
  -H 'Content-Type: application/json' -d '{"subscriberId":"you"}'
curl -s -X POST localhost:3000/v1/topics -H "Authorization: ApiKey $K" \
  -H 'Content-Type: application/json' -d '{"key":"repo-synthetic","name":"synthetic activity"}'
curl -s -X POST localhost:3000/v1/topics/repo-synthetic/subscribers \
  -H "Authorization: ApiKey $K" -H 'Content-Type: application/json' \
  -d '{"subscriberId":"you"}'

quarry

quarry serve --addr 127.0.0.1:8950 --data ./data \
  --notify http://127.0.0.1:3000 --notify-key "$K" \
  --repo quarry=$HOME/quarry

--data is not optional in practice: it defaults to .quarry beside the first --repo, which writes derived state into whichever repository happens to be listed first. Always pass it.

Gates are open until they are closed, and closing them is two separate credentials on purpose — accounts are the forge identity (argon2id), the sync secret is the RFC §6.3 value the protocol fixes:

quarry account add --user you --admin --data ./data   # closes web/API writes
quarry user add --repo name=$HOME/quarry --user you --data ./data   # closes push

The account gate is global: the first account closes writes on every repository in the fleet, not just one.

With compose

export NOTIFY_SRC=../notify           # a notify checkout; it builds dagnats too
export ZIT_REF=<commit>               # pin the store implementation
docker compose up --build

Then bootstrap notify once (docker compose run --rm notify bootstrap …), put the key in QUARRY_NOTIFY_KEY, and create the workflows and topics as above.

docker run <image> cat /etc/quarry-build reports which quarry and which zit an image carries, without starting it.

Unverified. Dockerfile and compose.yml have not been built — no container runtime is installed on the development machine. The two build recipes inside them are verified natively: zit compiles from a clean clone of the GitHub mirror with zig build --release=fast, and quarry builds fully static with CGO_ENABLED=0 (its SQLite is WASM-based and Pikchr runs on wazero, so nothing needs a C toolchain). What remains untested is the image assembly and the compose wiring.

Ports

port what
4222 dagnats NATS
8080 dagnats HTTP bridge (and its console — unauthenticated, keep it on loopback)
3000 notify API
8950 quarry

quarry's own embedded NATS binds nothing (DontListen), so it never collides.