docs: capitalise the headings and the proper nouns

Sentence case for the headings, Django, Docker, Git and Rust where the
product is meant rather than the command, and SQL and URL as initialisms.
The role names `ahab status` prints stay lowercase, being names.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DFPds8Do63ZSRMwZ3guDyg
This commit is contained in:
2026-09-09 13:53:10 +00:00
parent 19b34cd946
commit 68c1f03503

View File

@@ -1,16 +1,16 @@
# ahab # ahab
A wrapper around `docker compose` for our dockerized django projects, so the A wrapper around `docker compose` for our dockerized Django projects, so the
same commands work in every repository. same commands work in every repository.
## installing ## Installing
You will need rust installed. Clone repo and run: You will need Rust installed. Clone repo and run:
```bash ```bash
cargo install --path . cargo install --path .
``` ```
## what it makes of a project ## What it makes of a project
`ahab status` prints the services it picked, the containers behind them, the `ahab status` prints the services it picked, the containers behind them, the
credentials it would use and what the store holds, reporting whatever it cannot credentials it would use and what the store holds, reporting whatever it cannot
@@ -30,11 +30,11 @@ store: /home/you/.local/share/ahab/git.aflabs.org/urnik/afurnik
`ahab link check` lists what a sandbox can still read `ahab link check` lists what a sandbox can still read
``` ```
## seeing what it runs ## Seeing what it runs
`-v` prints each docker command as it runs, `--dry-run` prints the ones it would `-v` prints each Docker command as it runs, `--dry-run` prints the ones it would
run without running them, and `-q` prints only what was asked for, dropping the run without running them, and `-q` prints only what was asked for, dropping the
progress along the way (compose's own progress included). All work before or progress along the way (Compose's own progress included). All work before or
after the subcommand. after the subcommand.
```bash ```bash
@@ -46,18 +46,18 @@ ahab -q postgres dump ./dump
Nothing is written under `--dry-run`, filesystem included: `link add` and Nothing is written under `--dry-run`, filesystem included: `link add` and
`django make-command` say what they would do and leave the tree alone. `django make-command` say what they would do and leave the tree alone.
## exit codes ## Exit codes
- `0`it ran and had nothing to report - `0`It ran and had nothing to report
- `1`it could not finish - `1`It could not finish
- `2`the arguments were wrong - `2`The arguments were wrong
- `4`it ran fine and found something worth reporting, i.e. - `4`It ran fine and found something worth reporting, i.e.
`link check --exit-code` with anything outside the store `link check --exit-code` with anything outside the store
## shell completion ## Shell completion
`ahab completions <shell>` writes a completion script to stdout, for bash, `ahab completions <shell>` writes a completion script to stdout, for Bash,
elvish, fish, powershell or zsh: Elvish, Fish, PowerShell or Zsh:
```bash ```bash
ahab completions zsh > ~/.local/share/zsh/completions/_ahab ahab completions zsh > ~/.local/share/zsh/completions/_ahab
@@ -76,25 +76,25 @@ SHELL_COMPLETIONS_DIR_FISH=~/.config/fish/completions \
`SHELL_COMPLETIONS_DIR` writes every shell into one directory instead. `SHELL_COMPLETIONS_DIR` writes every shell into one directory instead.
## the compose file ## The compose file
`ahab` does not pass `-f`. docker compose finds the file itself, so set `ahab` does not pass `-f`. Docker Compose finds the file itself, so set
docker's own `COMPOSE_FILE` when it is not in the working directory, including Docker's own `COMPOSE_FILE` when it is not in the working directory, including
its `base.yaml:override.yaml` form. A project's `.env` is a good place for it, its `base.yaml:override.yaml` form. A project's `.env` is a good place for it,
since docker reads that too: since Docker reads that too:
``` ```
COMPOSE_FILE=docker/docker-compose.yaml COMPOSE_FILE=docker/docker-compose.yaml
``` ```
## service detection ## Service detection
`ahab` finds the services it needs in `docker compose config`, so they can be `ahab` finds the services it needs in `docker compose config`, so they can be
named anything: named anything:
- **postgres**: the service whose image is a postgres flavour, matching - **postgres**: The service whose image is a Postgres flavour, matching
`postg`, `timescale`, `pgvector` or `citus` `postg`, `timescale`, `pgvector` or `citus`
- **django**: the service that both builds an image and sets - **django**: The service that both builds an image and sets
`DJANGO_SETTINGS_MODULE`. Where more than one does — a worker beside the web `DJANGO_SETTINGS_MODULE`. Where more than one does — a worker beside the web
service, say — the one publishing ports wins service, say — the one publishing ports wins
@@ -103,15 +103,15 @@ lists the services it looked at rather than guessing. Two services that both
build and set the settings module are ambiguous unless exactly one of them build and set the settings module are ambiguous unless exactly one of them
publishes ports. publishes ports.
`POSTGRES_USER` and `POSTGRES_DB` are read off the detected postgres service, so `POSTGRES_USER` and `POSTGRES_DB` are read off the detected Postgres service, so
`dropdb`, `createdb`, `pg_restore` and `pg_dump` use the role and database the `dropdb`, `createdb`, `pg_restore` and `pg_dump` use the role and database the
project declares, falling back to `db` when it declares neither. Both have to project declares, falling back to `db` when it declares neither. Both have to
be names: `ahab` hands them to those tools as a role and a database, and libpq be names: `ahab` hands them to those tools as a role and a database, and libpq
reads a database name holding an `=` or a url as a whole connection string, reads a database name holding an `=` or a URL as a whole connection string,
which would send a dump to whatever server it names. A value that could be read which would send a dump to whatever server it names. A value that could be read
as something other than a name is refused rather than passed on. as something other than a name is refused rather than passed on.
## django ## Django
```bash ```bash
ahab django run <cmd> # in a fresh container, through the entrypoint ahab django run <cmd> # in a fresh container, through the entrypoint
@@ -131,11 +131,11 @@ written for pytest, so a wrong guess reads as a pass. Name the runner instead:
ahab django run pytest -x tests/ ahab django run pytest -x tests/
``` ```
## postgres ## Postgres
```bash ```bash
ahab postgres dump <path> # pg_dump, custom format ahab postgres dump <path> # pg_dump, custom format
ahab postgres dump -F plain <path> # pg_dump, plain sql ahab postgres dump -F plain <path> # pg_dump, plain SQL
ahab postgres dump -F tar <path> # pg_dump, tar ahab postgres dump -F tar <path> # pg_dump, tar
ahab postgres dump -F directory <d> # pg_dump, a directory of files ahab postgres dump -F directory <d> # pg_dump, a directory of files
ahab postgres dump -F cluster <path> # pg_dumpall, roles and all databases ahab postgres dump -F cluster <path> # pg_dumpall, roles and all databases
@@ -148,7 +148,7 @@ A dump is written `0600` and under a name of its own until it is complete, then
renamed over the target: a cluster dump carries every role's password hash, and renamed over the target: a cluster dump carries every role's password hash, and
the default `0644` would hand it to anyone else with an account on the machine. the default `0644` would hand it to anyone else with an account on the machine.
`psql` passes its arguments through and only asks docker for a terminal when it `psql` passes its arguments through and only asks Docker for a terminal when it
has one to hand over, so both of these work: has one to hand over, so both of these work:
```bash ```bash
@@ -159,14 +159,14 @@ ahab postgres psql -tAc 'select count(*) from auth_user' | wc -l
The format of a dump being imported is read from the file rather than its name. The format of a dump being imported is read from the file rather than its name.
A custom format dump starts with `PGDMP` and a tar one with `toc.dat`, both of A custom format dump starts with `PGDMP` and a tar one with `toc.dat`, both of
which go to `pg_restore`, as does a directory produced by `pg_dump -Fd`. which go to `pg_restore`, as does a directory produced by `pg_dump -Fd`.
Anything else is treated as sql and fed to `psql` with `ON_ERROR_STOP` and Anything else is treated as SQL and fed to `psql` with `ON_ERROR_STOP` and
`--single-transaction`, so a bad file rolls back instead of half applying. `--single-transaction`, so a bad file rolls back instead of half applying.
A whole cluster dump from `pg_dumpall` is recognised by its header and handled A whole cluster dump from `pg_dumpall` is recognised by its header and handled
differently again: it creates its own databases and carries role statements, so differently again: it creates its own databases and carries role statements, so
the database is dropped but not recreated, the dump goes to `psql` connected to the database is dropped but not recreated, the dump goes to `psql` connected to
`postgres`, and it runs without `ON_ERROR_STOP` because roles that already exist `postgres`, and it runs without `ON_ERROR_STOP` because roles that already exist
report errors that are expected. Any error it does not expect is reported and report errors that are expected. Any error it does not expect is reported and
fails the import, since psql without `ON_ERROR_STOP` exits 0 having applied fails the import, since `psql` without `ON_ERROR_STOP` exits 0 having applied
only part of the dump. Gzipped dumps are decompressed on the way in, whichever only part of the dump. Gzipped dumps are decompressed on the way in, whichever
of the three they hold. A cluster that has never held the database yet is a of the three they hold. A cluster that has never held the database yet is a
valid target either way, so importing into a fresh one works. valid target either way, so importing into a fresh one works.
@@ -174,7 +174,7 @@ valid target either way, so importing into a fresh one works.
`import` stops the project before it starts, so an import that fails leaves it `import` stops the project before it starts, so an import that fails leaves it
stopped and says so: `docker compose up` brings it back. stopped and says so: `docker compose up` brings it back.
## link ## Link
`ahab link` moves untracked paths out of the repository into an out-of-repo `ahab link` moves untracked paths out of the repository into an out-of-repo
store and symlinks them back, so a sandbox that mounts the repository sees a store and symlinks them back, so a sandbox that mounts the repository sees a
@@ -195,7 +195,7 @@ where it was, and the store keeps nothing. A second checkout linking the same
path is left with a dangling symlink, since there is only ever one stored copy. path is left with a dangling symlink, since there is only ever one stored copy.
`list` says what state each stored path is in, which `check` cannot see because `list` says what state each stored path is in, which `check` cannot see because
it asks git about the repository rather than reading the store: it asks Git about the repository rather than reading the store:
``` ```
$ ahab link list $ ahab link list
@@ -215,7 +215,7 @@ there is nothing to undo for a path whose symlink is gone.
end with a NUL, and the two paths of a symlink leading elsewhere are separated end with a NUL, and the two paths of a symlink leading elsewhere are separated
by one as well, the way `git status -z` reports a rename. by one as well, the way `git status -z` reports a rename.
`check` looks at tracked paths too, since git tracks symlinks and one can lead `check` looks at tracked paths too, since Git tracks symlinks and one can lead
out of the repository without appearing in any untracked listing. `add` cannot out of the repository without appearing in any untracked listing. `add` cannot
externalize a tracked path, so all `check` can do is report it, under the code externalize a tracked path, so all `check` can do is report it, under the code
`T>`. `T>`.
@@ -227,17 +227,17 @@ the store, called the repository clean.
The store lives under The store lives under
`${XDG_DATA_HOME:-$HOME/.local/share}/ahab/<host>/<owner>/<repo>/`, derived `${XDG_DATA_HOME:-$HOME/.local/share}/ahab/<host>/<owner>/<repo>/`, derived
from the git `origin` remote, or `_local/<checkout>` when there is no remote to from the Git `origin` remote, or `_local/<checkout>` when there is no remote to
name it after. Its directories are created `0700`: it exists to hold what name it after. Its directories are created `0700`: it exists to hold what
should not be readable from the repository, and on a shared machine the default should not be readable from the repository, and on a shared machine the default
`0755` would leave that to whoever else has an account. A store path derived `0755` would leave that to whoever else has an account. A store path derived
from a remote, or a checkout name, that needed characters replacing carries a from a remote, or a checkout name, that needed characters replacing carries a
short fingerprint of the original, so two of them cannot land on one directory. short fingerprint of the original, so two of them cannot land on one directory.
## configuration ## Configuration
Currently `ahab` respects the following environment variables. Currently `ahab` respects the following environment variables.
- `AHAB_LINK_ROOT`: where `ahab link` keeps its store - the same as `--store`, - `AHAB_LINK_ROOT`: Where `ahab link` keeps its store the same as `--store`,
which takes precedence, and defaults to `${XDG_DATA_HOME:-$HOME/.local/share}/ahab`. which takes precedence, and defaults to `${XDG_DATA_HOME:-$HOME/.local/share}/ahab`.
`ahab status` reads it too, so it reports the store the link commands use `ahab status` reads it too, so it reports the store the link commands use