# Rook

![logo](https://hg.sr.ht/~ser/rook/raw/assets/rook.png?rev=tip)

[![Build status](https://builds.sr.ht/~ser/rook/.build.yml.svg)](https://builds.sr.ht/~ser/rook/.build.yml?)
★ [Releases](https://downloads.ser1.net/software/rook/)
★ ![Latest release](https://downloads.ser1.net/software/rook/badges/latest_release.svg)
★ [![AUR version](https://img.shields.io/aur/version/rook)](https://aur.archlinux.org/packages/rook)  
[![License](https://img.shields.io/aur/license/rook)](https://hg.sr.ht/~ser/rook/raw/LICENSE?rev=tip)
★ [Changelog](https://hg.sr.ht/~ser/rook/browse/CHANGELOG.md?rev=tip)
★ [Software metrics](https://downloads.ser1.net/software/rook/badges/METRICS.md)
★ [Issues](https://todo.sr.ht/~ser/rook)
★ [Mailing List](https://lists.sr.ht/~ser/rook)  
[![Go Report Card](https://goreportcard.com/badge/ser1.net/rook)](https://goreportcard.com/report/ser1.net/rook)
★ ![Code linting](https://downloads.ser1.net/software/rook/badges/linting.svg)
★ ![Code security](https://downloads.ser1.net/software/rook/badges/vulnerabilities.svg)
★ ![Markdown linting](https://downloads.ser1.net/software/rook/badges/readme_linting.svg)
★ ![Unit tests](https://downloads.ser1.net/software/rook/badges/unit_tests.svg)  
![Code coverage](https://downloads.ser1.net/software/rook/badges/coverage.svg)
★ ![Code complexity](https://downloads.ser1.net/software/rook/badges/complexity.svg)
★ ![ABC metrics](https://downloads.ser1.net/software/rook/badges/abc_score.svg)
★ ![Lines of code](https://downloads.ser1.net/software/rook/badges/loc.svg)

A lightweight, stand-alone, headless secret service tool backed by a Keepass v2
database.

- [Usage](#usage)
- [Install](#install)
- [Security](SECURITY.md)
- [Roadmap](#roadmap)
- [Contributing](CONTRIBUTING.md)
- [Credits and Related](#credits-and-related)
- [Other solutions](#other-solutions)

Rook allows you to use a KeePass v2 database as storage for secrets. It provides
client and server modes; the server unlocks the database and stays in memory;
the client communicates over a socket with the server and fetches data. This
allows:

- Using your KeePass database, instead of having to have all your secrets _also_
  stored in a second system
- Allows you to unlock the database once, instead of every time the system needs
  a secret
- Allows you to use your secrets in scripts and tools such as
  [mbsync/isync](https://github.com/gburd/isync),
  [vdirsyncer](https://github.com/pimutils/vdirsyncer),
  [offlineimap](https://github.com/OfflineIMAP/offlineimap),
  [msmtp](https://marlam.de/msmtp), encrypted file system mounters, encrypted
  and/or offsite backup programs, IMAP email clients, and so on.
- Allows you to run a _headless_ secret server
- Is easily auditable, and as a single binary is more easily protectable

There are other ways of accomplishing each of these, but none that accomplish
all of them. passhole and kpmenu are close alternatives. passhole is Python with
a lot of dependencies to audit. Recent versions of kpmenu also have large
dependency trees, and the mode of operation makes it harder to use for non-GUI
scripting.

Rook is not compatible with any other secret service tools; it does not
communicate over, or rely on, DBus, or anything systemd. It should work as well
on Darwin, *BSD, or any other POSIX-ish OS as it does on Linux, although this
has not been tested. I don't know what it'll do on Windows.

## Status & Features

Rook is mostly complete. Some features are missing; see the Roadmap section
below. It might crash, although it's been a while since I've seen one. It should
be currently able to replace most secret-service type calls you might make from
scripts; you can easily fetch passwords by account with the client, which is
what you really need.

- Read kdbx v2 Keeass database files
- List entries from the DB
- Search entries, by title, URL, or tag
- Show entries
- Show individual fields, such as username & password
- Reload a (presumably changed) database; open a different database
- Lock / unlock a database
- Pin-controlled client calls for additional security (see Secure section,
  below)
- Headless
- One-time pin soft locking & unlocking
- Minimal dependencies and an auditable amount of sourcecode -- you can check to
  make sure it's not doing anything fishy, even if you aren't a Go expert. This
  repo contains around 1k LOC in 5 files. Assuming you trust the core Go team,
  you only then have to audit two other external libraries: a [flags
  lib](https://hg.sr.ht/~ser/claptrap) written by me that is a single Go file
  with no external dependencies and which has no networking code; and [the
  library](https://github.com/tobischo/gokeepasslib) that provides access to the
  KeepassDB (gokeepasslib has itself one external dependency, a crypto library
  written by the same author) - neither of which have networking code.
- Supports either password, or password + keyfile credentials

## Usage

`rook help` prints a list of the supported commands. Start a rook server process
with `rook serve`, and then access it by calling `rook` again with one of the
other commands. There is no configuration, or configuration file.

Exit the rook server with `rook exit` or `^c`. If, for some reason, it leaves
behind the socket file, the next time you start rook it'll complain about one
already running. You can override that warning with the `-f` argument.

Rook will watch the DB file and auto-refresh the memory cache if the file
changes.

### Serving a database

Start a rook server with:

```bash
rook -f serve -P /path/to/your.kdbx
```

`-f` here forces rook to ignore and overwrite any pre-existing socket file; you
probably shouldn't use it unless you are certain rook isn't already running.
`-P` tells rook to prompt for the database password. `-P` can be used either
when starting a server, or when calling `open` from a client. Alternatively
(and, probably, more normally), start the rook server with no password, and
unlock it with a client call when you can prompt for a password:

```bash
$ rook serve /path/to/kdbx &
2025/04/08 10:40:57 [ERROR] unable to decode database "/path/to/kdbx": Wrong password? HMAC-SHA256 of header mismatching
2025/04/08 10:40:57 [ERROR] reload() error on server creation
$ rook open -P
Password:
```

The errors in the `serve` command is because rook is unable to open the database
as no password has been provided. You can avoid this error by running it
interactively like this:

```
$ rook serve -P /path/to/kdbx
Password:
Rook server started with "/path/to/kdbx"
```

This obviously can't be done if you're auto-starting rook as part of your login
process. If you both want to autostart rook _and_ start it unlocked, you can use
a command like [zenity](https://gitlab.gnome.org/GNOME/zenity) to prompt for the
password:

```
zenity --password --title "Unlock rook" | rook serve --stdin /path/to/kdbx
```

If you use a key file for a second security factor, you can specify it with the
`-k` argument to either `serve` or `open`.

Or, the server can be called with no database, and the client can tell it which
database to open by passing it a path; in this case, it must be a path the
server can find the file through, usually a fully qualified path. However, if
the rook server was run in the same directory as one or more databases, the
client can provide a simple relative path:

```bash
$ rook open -P my.kdbx
Password:
SUCCESS
```

A database supplied by `open` replaces whatever the server was started with.

As an alternative to all of password prompting, rook will also take a password
from the `ROOK_PASSWORD` environment variable. The prompt takes precedence.
Remember that passwords are plain text, and environment variables are -- while
reasonably secure -- still more persistent and accessible than other mechanisms.
The password may also be provided to the `serve` command via stdin pipe with the
`--stdin` argument; this allows starting a Rook server with a password vault
without exposing the password through environment variables and without
requiring user interaction. This means you could also do this in an interactive
bash, as an alternative to `-P`:

```
$ rook serve --stdin database.kdbx
supersecretpassword
^d
```

It's possible to tell whether the database is unlocked by making most any other
request, e.g. `info`, as the server will return a `NO OPEN DATABASE` error
message. If no database is open, the `lock` and `commands` requests will not
throw errors, and the only other request that will work is `open`.

Databases can be live-reloaded with the `reload` command; this assumes the
credentials have not changed,and it simply replaces the in-memory database from
the current version on disk. There is not yet -- and may never be -- fancy DB
merging such as KeepassXC does; it's a straight-up replacement.

If you want to move the socket location, e.g. to `/var/run/user/$(id
-u)/rook.sock` then use the `--socket` argument. If you do this, you **must**
set it for **both** the server and every client call. Easiest would be to create
an alias in your shell's rc file, but you can also just specify it everywhere
you call Rook.

### Searching & showing

If the entry Title is unique, then `show` only needs that; otherwise, it'll show
all the matching entities -- which may not be what you want when using rook for
script authentication purposes. In the case of duplicates, provide the path -
separated by `/` - to the entity -- or part of it. For example, if you Keepass
database looks like:

```
Root
  Internet
    Accounts
      me
  Server
    Accounts
      me
  Identities
    me
    myself
```

then `rook show me` will show of all three `me` entries; `rook show Accounts me`
will show two of them; `rook show myself` will show only the one entry; and
`rook show Server/Accounts me` or `rook show Identities me` will show only the
one entry. A fully qualified path would be `rook show Root/Internet/Accounts
me`, but rook requires only enough to uniquely identify entries.

Rook can also display your database as a tree by using the `--tree` argument:

```
$ rook ls -t
┌─ /
├─┬─ Subgroup  1
│ ├─┬─ Subsubgroup 2
│ │ └── Subsub item
│ └── Subgroup 1 item 1
├── First entry
├── Second test
├── Third entry
├── fourth entry
└── Something wicked
```

Empty groups are not shown; leaves in the tree are _always_ entries.

### Locking

Databases can be hard-locked with the `lock` command, e.g. `rook lock`. With a
hard lock, rook forgets everything it knows about the database, except for the
path on the filesystem. To unlock the database, a client call of `rook open -P`
is required —- this will prompt for the DB password.

Databases can also be soft-locked by calling `lock --pin`; in this case, the
rook server will generate a 4-digit one-time pin and return it to the client.
This pin can be used one time to unlock the database with `open --pin <digits>`.
If `open --pin` is called incorrectly 3 times, the database is hard-locked.  Any
other calls to rook while the database is in this state are soft-failures, in
that they return an error, but do not count as failed unlock attempts. This
prevents cron-jobbed scripts looking up paswords from hard-locking the database,
and since none of the requests can be used to brute-force rook, they're
considered mostly harmless. Similarly, attempts to call `lock --pin` while the
database is locked also soft-fail.

If a database is soft-locked, a client call to `open -P` (providing the full
password) will also unlock the database.

The keyring pin may **not** be used as a pin to unlock a locked database. Only a
one-time pin created with `lock --pin` or a full password provided by `-P` may
be used with `open` to unlock a database. Running `--keyring open` will result
in an error (but no server call).

Rook does not have any built-in support for timing-out and locking databases.
This can be accomplished with cron, or systemd timer units. Since the intended
purpose of rook is to provide secrets to cron jobs, such as email and calendar
syncing, there is no "timeout after disuse period" functionality. However, in
some cases a more ideal behavior can be tied into the screen locker; an example
is provided in [Examples](#examples), below.

#### Failures

Some conditions will cause a hard-lock of the database, requiring the full
password to unlock. In all failure cases that count towards a hard lock, the
caller has 3 tries, after which the server will lock and the DB and clear it
from memory. Successful attempts to read the DB clear the ticker.

Attempts to _read_ the database without proper authorization, such as calling
`ls` on a `--secure`d or `--keyring` secured DB count towards a hard-lock.

Failed attempts to open the database, as with the wrong password or pin, count
towards a hard-lock. This hinders brute-forcing the password.

If a database is soft-locked with a one-time pin, reads are _not_ counted as
failures towards a hard-lock; this prevents automated background jobs attempting
to read the DB from triggering a hard-lock. The reads will still fail, but they
won't trigger a hard-lock.

Open attempt failures on a soft-locked DB **do** count towards a hard-lock.

### Improving security

rook knows how to use the kernel keyring to store a pin in the user's session.
Using this feature greatly increases security, as only processes in the user's
session (children of the X or Wayland session) can access this keyring. This
protects other processes -- including root processes -- from sending commands
directly to the socket and accessing secrets.

To use this feature, run the rook server and client with the `--keyring`
argument. Rook will automatically set and fetch the pin from the keyring's user
session.

This can be done manually, using the `keyctl` command from the `keyutils`
package; this process is described in the [Security](SECURITY.md) document.

While `--keyring` automates using the key ring, it uses the kernel API. In
particular, it does not fork out to the `keyctl` command. Using this feature
from within a container is undefined and unsupported -- if you have success with
this, great; if not, please don't bother filing a bug. Container permissions and
settings are beyond the scope of this project.

### Caveats

`rook list` shows all entries in the DB, with an index number and a path. While
the index can be used, it's not a fixed thing and could change as the database
changes, so it's best to not rely on it in scripts.

`rook exit` immediately performms a hard `os.Exit()` without requiring a
password, and which will terminate any other concurrent pending client requests.
This is intended behavior. As with all behavior, rook errs on the side security.
I could be convinced to require an authenticated request; send an email to the
mailing list if you have a compelling argument (or need) for that.

### Examples

The alias in the following example isn't necessary; it's just to keep rook
running everything in `${PWD}`.

![](https://cloud.ser1.net/api/public/dl/a7v3uu5d?inline=true)

Here are some code snippets from various config files (if you're on Linux, I
highly recommend you use the `--keyring` feature; just add it after `rook` in
all the examples):

**mbsync** (`~/.config/isyncrc`)

```
PassCmd "rook show -p 'Email account'"
```

**aerc** (`~/.config/aerc/accounts.conf`)

```
source-cred-cmd = rook show -p Accounts 'Email account' 
```

**vdirsyncer** (`~/.config/vdirsyncer/config`)

```
password.fetch = ["command", "rook","show","-p","CardDAV account"] 
```

**msmtp** (`~/.msmtprc`)

```
passwordeval   rook show -p Identities "SMTP server"
```

**restic** (environment variables)

You could store all of your restic backup information in Keepass:

```
RESTIC_PASSWORD=$(rook show -p Accounts/Backups glamdring) \
  RESTIC_REPOSITORY=$(rook show -f restic_repository Accounts/Backups glamdring) \
  EXCLUDES=($(rook show -f excludes Accounts/Backups glamdring)) \
  /usr/bin/restic backup ${EXCLUDES[@]/#/-e } --one-file-system \
  $(rook show -f files Accounts/Backups glamdring)
```

#### Searching

Searches look in several places for matches: titles, URLs, and tags. However,
`show` only matches against titles. Both `search` and `show` also take an
optional path as the first argument, which filters the entities before matching;
e.g.:

```bash
$ rook ls
1    Accounts   Entry 1
2    Accounts   Entry 2
3    Accounts/Hosting   Entry 1
4    Accounts/Hosting   Entry 2
5    Internet   Entry 1
6    Internet   Entry 2
7    Internet/Hosting   Entry 1
$ rook search 'Entry 2'
Index: 2
Path: Accounts
Title: Entry 2

Index: 4
Path: Accounts/Hosting
Title: Entry 2

Index: 6
Path: Internet
Title: Entry 2
$ rook search Internet 'Entry 2'
Index: 6
Path: Internet
Title: Entry 2
```

The `list` command can be used to search for containers by passing it a
`/`-separated path:

```bash
$ rook ls Hosting
3    Accounts/Hosting   Entry 1
4    Accounts/Hosting   Entry 2
7    Internet/Hosting   Entry 1
$ rook ls Internet/Hosting
7    Internet/Hosting   Entry 1
```

This also works with the `-t` (tree) option:

```bash
$ rook ls -t Hosting
┌─ /
├─┬─ Accounts
│ └─┬─ Hosting
│   ├── Entry 1
│   └── Entry 2
└─┬─ Internet
  └─┬─ Hosting
    └── Entry 1
```

Note that `/` as the path may return unexpected results if you use `/` in your
entry names.

#### Locking and unlocking with a screensaver

If you're looking at rook, you may have a similar system, as I describe here,
all of these functions are usually provided by the DE; if you're replacing one,
you've probably replaced all of them.

On my system, I use [xss-lock](https://bitbucket.org/raymonad/xss-lock) as an
auto-locker, although
[xautolock](https://ibiblio.org/pub/Linux/X11/screensavers/) works just as well.
In both cases, the locker called is a shell script that wraps
[i3lock](https://github.com/Raymo111/i3lock-color); I do this because there are
things I want to happen when the screen locks, such as disabling dunst
messaging. In this script, you can call `rook lock --pin`, get the pin and save
it, and then use the pin to unlock rook when the screen is unlocked.

A simplified version of what I run is:

```
$ xss-lock ~/.bin/superlock
$ cat ~/.bin/superlock
##!/usr/bin/zsh

dunstctl set-paused true

lock() {
  i3lock-fancy-rapid 16 3 -enkS 1
  rook open --pin $1
}
lock $(rook lock --pin)

dunstctl set-paused false
```

or,

```
rook lock --pin | (
  i3lock-fancy-rapid 16 3 -enkS 1
  read pin && rook open --pin $pin
)
```

Just be careful about using long-term environment variables to store the pin,
because environment variables can be read from `/proc` and it increases the
attack surface. For example, I'd avoid:

```
PIN=$(rook lock --pin)
i3lock
rook open --pin $PIN
```

### Autotype

There are shell scripts in the `utils/` directory: `autotype.sh` and
`getAttr.sh`. `autotype.sh` can provide form-filling utility; it works by
grabbing the focused window's title and uses `rook match` to find the best match
in the DB -- rook uses the Keepass Autotype rules for this, if they exist -- and
the best match is returned with the first line being the defined autotype
sequence for the entry. The script then parses out the key/value pairs and uses
`xdotool` to type the sequence. `getAttr.sh` uses rofi to allow the user to
select an entry from the DB, and then an attribute from the entry, and then it
performs a user-requested action with the data.

`autotype.sh` and `getAttr.sh` are shell scripts; if you use them, you should
also read through them. They're not exactly trivial scripts, but at ~100 lines
long each they're auditable even if you're not a shell expert. Again, **I**
trust me implicitly, but you shouldn't.

`autotype.sh` is straightforward -- it just does its thing: tries to find the
best entry and type the autotype sequence. If it gets multiple matches, it shows
them in a rofi dialog, you choose one, and off it goes.

`getAttr.sh` has a `-h` argument for help, and more complex options. It can use
[fzf](https://github.com/junegunn/fzf) instead of `rofi`, and be used entirely
from the command line. It can copy the selected value to the system clipboard,
print the value out, or type the value out with `xdotool`.

`getAttr.sh` can look up an entry using the current window's title, as
`autotype.sh` does, with the `-a` argument ("autodetect"). Used with the `-u`,
`-p`, or `-o` arguments ("username", "password", "OTP"), and the `-c` argument
("copy to clipboard"), you could e.g. bind hot keys to copy values to the
clipboard which you could manually paste into the appropriate fields. This is
handy when `autotype.sh` fails (as it easily can).

It's important to recognize that `autotype.sh` is working almost blindly. It
gets, at most, a window title to try to match against records, and then it just
executes the key presses using `xdotool`. The upside is that it is not prone to
the hijacking attack DOM-based password tools (including KeePassXC itself) are
prone to. The downside is that any failure of the website to _reasonably_ focus
input fields in a reliable order can cause issues. Especially agreggious are the
sites which break up username/password into two different forms, or performm
AJAX loading, or any other messing around with the login process. In these
cases, using `getAttr.sh` is preferrable. It can still perform keypress entry,
for those _really_ hostile, obnoxious web sites which disable pasting, but it
can also copy to the clipboard. For example, with HerbstluftWM:

```bash
$ herbstclient keybind Mod4+p spawn getAttr.sh -acu
$ herbstclient keybind Mod4+u spawn getAttr.sh -acp
$ herbstclient keybind Mod4+u spawn getAttr.sh -aco
```

then logging in to, e.g., SlayPal might be a sequence:

1. Get to the SlayPal login page
2. Press `Mod4+u` to get your SlayPall username
3. Click on the "user name" field and `^v` to paste the username
4. Press `Enter` and get to the password field
5. Press `Mod4+p` to get your password
6. `^v` to paste the password, and hit `Enter`

#### autotype & getAttr Dependencies

These are dependencies **only** for the utility scripts -- the rook binary
itself does not use any of these.

- [zsh](https://www.zsh.org/), because I just couldn't be arsed to do the extra
  work to make some things in `autotype.sh` work the harder way in bash.
- [ripgrep](https://github.com/BurntSushi/ripgrep), because -- again -- it makes
  some things just so much easier.
- [xdotool](https://www.semicomplete.com/projects/xdotool/), for teh tappy-tap-tap.
- [xprop](https://gitlab.freedesktop.org/xorg/app/xprop), because Luakit needs
  hacks to work properly. If you don't use Luakit, you can (should?) delete the
  hacks -- it'll probably make things more robust.
- [yad](https://github.com/v1cont/yad), for providing info dialogs.
- [xsel](https://vergenet.net/~conrad/software/xsel/), used in `autoType.sh` to
  put values in the clipboard.

KeepassXC autotype sequences have a lot of keywords; the script understands a
small number of these -- ENTER, DELAY, TOTP, USERNAME, PASSWORD.  It should be
easy to add to this list by modifying the case switch at the end of the file --
improvement patches are welcome.

To use the script, put it in your path and bind it to a hotkey, e.g.

```bash
herbstluftwm keybind Mod4+t spawn ${HOME}/bin/autotype.sh
#### OR, if you installed with a package manager:
herbstluftwm keybind Mod4+t spawn /usr/bin/rook-autotype
```

If you installed rook with one of the binary distribution packages, and you have
the necessary required programs listed above (rofi, ripgrep, yad, etc.), then
the utility scripts will be installed in `/usr/bin/` as `rook-autotype` and
`rook-getattr`.

### Select Entry Attribute

There's a shell script in `utils/` called `getAttr.sh` which shows a list of all
entries, and when one is selected shows a list of all attributes, one of which
can be selected. If called with the `-t` argument, the selected value will be
typed using `xdotool`. If called with the `-c` arg, the value will be copied
into the clipboard using `xsel`. If no argument is given, the script prints the
value to STDOUT.

`getAttr.sh` can prompt either with `rofi` or `fzf`; the `-f` argument selects
`fzf`, and `rofi` is used by default. Using `fzf` allows `getAttr` to be used
without a GUI.

### Testing

You can see how `autotype.sh` will work by faking an input dialog with `yad`. To
get as close to real life, you'll want to run the application that will ask for
the login -- if it's Paypal, then go to Paypal.com in an incognito window and
click "Logn". If it's some GTK application, run the application and start the
login process. Then use `xprop` to get the window title -- this is what
`autotype.sh`, and incidentally, KeepassXC, uses for entry matching. Then run
`yad` with that window title as the `--title` parameter -- something like this:

```bash
yad  --form --field "Username" --field "Password:H" --field "TOTP:NUM" --title "Log in to Sourcehut"
```

Give that Username field focus and trigger your autotype -- once you hit OK, the
values will be printed on the command line.

## Install

### From your distribution

rook packages are available in Apline testing, and in Arch AUR. For Alpine,
you'll have to have the testing repo enabled:

```shell
echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing/" >> /etc/apk/repositories
apk add --no-cache rook
```

For Arch, use your AUR enabled tool:

```shell
yay -Sy rook
```

### From pre-built binaries

Download the latest release from the [releases
page](https://downloads.ser1.net/software/rook/). Optionally, also download the
binary signature and validate the binary. Decompress the binary with `gzip`,
copy it somewhere in your path, and off you go.

I provide builds for Linux arm64 and amd64, and the _latest_ version as RPM,
deb, apk, and Arch zst packages for each of these.

You can get my public key either from [Sourcehut](https://meta.sr.ht/~ser.pgp),
or from one of the public key servers. You'll want to grab the one that says:

`Sean E. Russell (Signing key for Sourcehut builds) <ser@ser1.net>`, which is
key `5E0D7ABD6668FDD1`. To get it from Sourcehut, where the builds are made:

```bash
curl https://meta.sr.ht/~ser.pgp | gpg --import
####      OR    ###
gpg --keyserver keys.openpgp.org --recv-keys 6668FDD1
####  AND THEN  ###
gpg --verify rook_arm64_latest.gz.sig
```

If you install from one of the pre-built packages, the script `autotype.sh` will
be installed as `/usr/bin/rook-autotype`, and `getAttr.sh` as
`/usr/bin/rook-getattr`.

### Build from source

If you want to try building and running on something other than Linux -- say,
one of the BSDs, or Darwin -- then this or the [next section](#Install the Go
way) is what you'll need to use.

You'll need [Mercurial](https://www.mercurial-scm.org/) and
[Go](https://golang.org). For the manpages, you'll need Go 1.24 or greater.

```bash
hg clone https://hg.sr.ht/~ser/rook
go build .
## OR, to embed the correct version s.t. `rook version` works,
go build -ldflags "-X main.Version=$(hg log -r tip --template '{bookmarks}')" .
```

If you have Go 1.24 installed, you can generate the man pages with:

```
go generate
```

They'll be put in a directory called `man1`; you can then copy them into some
place in your `man --path`.

### Install the Go way

```bash
go install ser1.net/rook@latest
## OR, if you know the version you want
go install -ldflags '-X main.Version=v0.0.9' ser1.net/rook@v0.0.9
```

### nfpm

The repo contains an [`nfpm.yaml`](https://nfpm.goreleaser.com/) file, from
which can be built packages for Arch, rpm, apk, and deb systems. You could use
this to build old versions, or versions for other architectures such as
arm5/6/7. If you want to do this, you're best off reading the nfpm usage docs,
since repeating them here would be silly. But, basically, you'll need to make
sure some environment variables are set, and run (to get an apk; replacable with
`rpm`, `deb`, or `archlinux`):

```bash
VERSION=$(hg log -r tip --template '{bookmarks}') \
  GOOS=$(go env GOOS) \
  GOARCH=$(go env GOARCH) && \
  nfpm pkg -p apk
```

## Roadmap

Most of the roadmap is kept in the sourcecode. A good way of looking at it is to
get [legume](https://sr.ht/~ser/legume) and run leg in the source directory.
However, there is a general thrust to most of what's coming:

- Bug fixes
- DB modification commands. I'm bearish about adding these: I think they're best
  done through tools like KeepassXC, which provide nice UIs, and which handle
  conflicts and merging well; they also add a **lot** of complexity that would
  make auditing harder, and for questionable value. Rook is not intended to
  fully replace KeepassXC tools. That said: small changes, like changing a
  password or adding TOTP to an entry, might be handy to do on the CLI. We shall
  see.

## Credits and Related

There are countless giants that should be listed, that we all take so much for
granted that they are unknown to most of us. However, I'd like to call out a
couple of individuals whose code I've leveraged heavily, or sometimes
(basically) stolen. Those I've copied/pasted from I've also done my best to
audit in the process.

- [tobischo](https://github.com/tobischo), for whom I'm very grateful for
  allowing me to avoid reading the entire Keepass v2 spec and implement it by
  hand. Although I probably should have, in following with my own attempt to
  minimize the attack surface of rook.
- [jlinoff](https://github.com/jlinoff), whose
  [getPassword](https://gist.github.com/jlinoff/e8e26b4ffa38d379c7f1891fd174a6d0)
  (MIT) code filled a tedious hole. I very much appreciate that jlinoff not only
  provided a solution, but provided _3_ solutions with different dependencies
  ranging from stdlib only, stdlib but with some of the more esoteric packages,
  and a final one that pulls in a `golang.org/x` package.
- The rook logo was derived from SVG art titled ['Raven With Key' by Karen
  Arnold](https://openclipart.org/detail/252186/raven-with-key). The original
  was licensed CC0, and so, therefore, is this.
- So much OSS software goes into my development process. I depend, quite
  completely, on:

  - [Helix](https://helix-editor.com/) - a modern, fast editor this a vi-like
    modal paradigm and advanced features like multi-range selects.
  - [Go](https://golang.org)
  - [gopls](https://github.com/golang/tools/tree/master/gopls), the Really Very
    Good Go language server.
  - [delve](https://github.com/go-delve/delve), Go's defacto standard debugger. It
    has a GUI, even, if you're into that sort of thing.
  - [Mercurial](https://mercurial-scm.org) - still the best VCS. I spend a lot of
    time in git, and while Jujutsu makes git suck less, it's still not quite
    Mercurial.
  - [Gnupg](https://www.gnupg.org/)
  - [ripgrep](https://github.com/BurntSushi/ripgrep) - while it's popular to write
    replacements for the old POSIX toolset, ripgrep is really the only one that
    vastly improves over the original enough to switch.
  - and Linux, X, herbstluftwm, zsh, the entire standard Linux POSIX-ish
    toolkit.

  I also advocate the following tools as being Extremely Useful and Sometimes
  Indispensible™ to the development process:

  - [Sourcehut](https://sr.ht) - it's not free, but it's also not bloated with
    Javascript and CSS, and it still supports Mercurial repositories. It offers
    everything an OSS project could need - lists, tickets, a really nice CI
    system, homepage-like Project containers. It's inexpensive, especially for
    what you get, but if anything more than $0 is too expensive, it's also OSS
    -- you can download every component and host it yourself; what you're really
    paying for is the hosting.
  - [revive](https://revive.run) - a much nicer alternative to golint
  - [csvtk](https://github.com/shenwei356/csvtk) - a strange entry for a dev tool
    list, but I use this thing All. The. Time. It's part of the CI. CSV is still
    one of the best semi-structured document formats, far less bloated than JSON
    (which is already pretty light). It's indispensible in my build CI.
  - [go-licenses](https://github.com/google/go-licenses) - if you have to have dependencies, tools like this become important. It's a license jungle out there.
  - [scc](https://github.com/boyter/scc). I've used a lot of different LOC
    counters; scc is nice because it also does some complexity calculations,
    generates CSV, can disable the pretty-printing, is multi-threaded, can
    calculate DRY metrics, respects .ignore files, can identify generated and
  minified files, count duplicated LOC... it is a very versatile tool.
  - [govulncheck](https://pkg.go.dev/golang.org/x/tools/gopls/internal/vulncheck/govulncheck)
  - -
    Increasingly imortant; it doesn't cover all of the security risks, but it
    helps.
  - [markdownlinkchecker](https://github.com/becheran/mlc) - I _just_ discovered
    this when a user brought a broken link in a README to my attention. I went
    through and checked all of my projects and found a distressing number of
    broken links -- and discovered an overly-restrictive firewall rule on one of
    my servers that was preventing access to a service! So I'm currently extremely
    grateful for this tool.
  - [shields.io](https://shields.io) - provides a really valuable, free service
    for generating badges (as used at the top of this readme). I can't thank them
    enough. My CI process uses their service at build time and generates static
    versions to reduce load on their service.

rook is developed using a set of decentralized, CLI-based tools. Web tools are
fine, and they're sometimes necessary, but they centralize data, are hard to
script, and just generally get in my way. When there are many people from
different domains collaborating on a thing, this extra impediment is necessary;
when there aren't, it's not. I use code comments for issue tracking and
[legume](https://hg.sr.ht/~ser/legume) to make that easier to view; I use
[changelog](https://hg.sr.ht/~ser/changelog) to generate the
[CHANGELOG.md](https://hg.sr.ht/~ser/rook/browse/CHANGELOG.md?rev=tip).

rook was inspired by [kpmenu](https://github.com/AlessioDP/kpmenu), which
approaches security differently, requiring interaction from the user on every
request. This makes it harder to use as a secret service for automating scripts,
and since kpmenu has a very specific threat model it's been resistant to
[patches](https://git.sr.ht/~ser/kpmenu) that would weaken their solution, and
even with those patches [additional tools](https://hg.sr.ht/~ser/quasiauto) are
needed to support autotype. As mentioned in [Security](SECURITY.md), Rook assumes
the user environment is secure and can therefore take a more relaxed approach;
these are the same assumptions and approaches used by any secret service
software, such as Gnome's
[secret-tool](https://gnome.pages.gitlab.gnome.org/libsecret/),
[keyring](https://github.com/jaraco/keyring), or
[pass](https://www.passwordstore.org/).

## Other solutions

Linux users have many options for storing passwords. I can't list all of them
here, but I can list the ones I've used, and why I believe **rook** was
necessary. In every case where the tool has a bespoke data store, it means the
user having to _manually_ syncing passwords between Keepass and whatever the
tool is using, and is for this reason is IMO unsuitable.

- [KeepassXC](https://keepassxc.org). This is an outstanding program that you
  should use. It does include a _proper_ secret service option, in that it is
  compatible with keyring and Gnome's secret-tool. It is, however, a GUI tool,
  and the CLI program that comes with it requires a password every time it is
  called, making it unsuitable for background scripts.
- [kpmenu](https://github.com/AlessioDP/kpmenu). As mentioned above, kpmenu's
  architecture does not lend itself to running as a secret service that can be
  used by background jobs needing credentials.
- [secret-service](https://github.com/yousefvand/secret-service) is another
  keyring-compatible solution. It uses its own backend DB store, and I want to
  use a Keepass DB. It also has a fairly large number of external, non-stdlib
  dependencies (19), making it much harder to fully audit.
- [secret-tool](https://gnome.pages.gitlab.gnome.org/libsecret/). This is what
  Gnome uses for password stores; it has a bespoke password database (not
  Keepass), and communicates over DBus -- using secret-tool outside of Gnome
  pulls in several extra services and software dependencies, which is relatively
  heavy if a secret service is all you're using it for.
- [keyring](https://github.com/jaraco/keyring). This is (I believe) the CLI tool
  for KDE and KWallet; it is compatible with Gnome's secret service, again
  communicating over DBus. It requires the KWallet service to be running in the
  background, and IIRC that kicks off several related KDE services. Like secret-
  tool, if you're not running the KDE desktop, you're getting a chunk of the
  load of it by running `keyring`.
- [pass](https://www.passwordstore.org/). git for passwords, encrypted with GPG!
  It's a fantastic idea, and by using `gpg-agent` it's not adding much to your
  service load. It has two downsides: first, it's _still_ a bespoke backend
  password database, meaning having to manually sync any passwords or entries
  with Keepass by hand; and second, it exposes a huge amount of metadata about
  the secrets. Since the entire DB is a filesystem, where the entry titles are
  directory names and attributes are files in the directory, the entire
  structure of the database is exposed, unencrypted, and readable. Attackers may
  not know what your Pornhub password is, but they know you have an account.
  This is fine for `pass`, which is designed more for secret sharing across
  teams, and so the account names are not considered confidential information.
  It's pretty horrible for anyone else, and especially anyone considered a
  dissident for whom that metadata could be used in persecution.
- [gopass](https://github.com/gopasspw/gopass) is a Go-based client for `pass`
  databases. If you use pass, it's a pretty nice tool. `pass` itself is a
  collection of shell scripts; `gopass` is a single self-contained binary. It
  doesn't have any connection with Keepass, though.
