Sources
A source loads named secrets when the session starts. motus keeps the values in memory, adds their other spellings (with and without 0x, inside a URL, percent-encoded), and redacts all of them.
type |
Options | Loads |
|---|---|---|
env |
none | process.env entries whose names look secret |
command |
command |
A shell command that prints a JSON object |
mise |
listFile |
Age-encrypted mise values |
pa |
entries or whole, dir |
Entries from a pa store |
1password |
refs, fromEnv |
op:// references |
On by default, and it can’t fail. It picks variables whose names contain KEY, TOKEN, SECRET, PASS, PRIVATE, MNEMONIC, DATABASE_URL, AUTH, CREDENTIAL or DSN. A short denylist skips noise such as SSH_AUTH_SOCK and GUM_*. A DATABASE_URL that has a host but no user, password or query is not secret and is skipped.
command
Section titled “command”Runs the command with sh -c in the working directory. It must print {"NAME": "value"} on stdout and exit 0. Anything else fails the source.
[[sources]]type = "command"id = "vault"command = "my-vault export --json"For each file in listFile plus the working directory’s mise config, motus finds keys stored as { age = ... } and decrypts them with mise env --json. A config.<env>.toml file sets MISE_ENV.
[[sources]]type = "mise"listFile = "~/.config/rotate-mise-secrets/files"listFilehas one absolute path per line. Blank lines and#comments are skipped.- If the working directory has age keys that the list doesn’t, motus warns about the drift and loads them anyway.
- If one file fails, the others still load. The session degrades only when the failed file covers the working directory.
Loads entries at session start so their values get redacted. Keep pa in blockCommands so pa show stays blocked.
[[sources]]type = "pa"entries = ["web/github", "mail"]- Set exactly one of
entriesorwhole = true.wholeloads every entry except*/meta. dirdefaults to${PA_DIR:-~/.local/share/pa}.- Kept lines are non-blank and at least
minValueLengthcharacters. One kept line givesweb/github. Several giveweb/github:1,web/github:2. An entry with none loads nothing. - Entries decrypt one by one within the source’s
timeoutMs. Raise it for a longentrieslist or a largewholestore.
The unlock method decides what startup costs:
| Store key | Startup | Use |
|---|---|---|
| Plain age key (pa default) | No prompt | whole |
TPM (age-plugin-tpm) |
No prompt | whole |
| YubiKey | PIN and a touch, every startup | A short entries list |
The YubiKey PIN needs a GUI pinentry, because pi owns the terminal. Without one the source times out and the session degrades.
Several stores mean several sources, each with its own id and dir:
[[sources]]type = "pa"id = "pa-machine"dir = "~/.local/share/pa-machine"whole = true
[[sources]]type = "pa"id = "pa-yubikey"dir = "~/.local/share/pa"entries = ["web/github"]timeoutMs = 300001password
Section titled “1password”Resolves op:// references in one op inject call at session start. Expect one 1Password biometric or session prompt every session.
[[sources]]type = "1password"fromEnv = true
[sources.refs]API_TOKEN = "op://dev/api/token"refsnames each secret by its key.fromEnv = truealso loads env vars whose values areop://references, named by the variable.- Give it
refs,fromEnv = true, or both. - A locked or missing
opfails the source. Withrequired = truethe session starts degraded.