1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
use clap::Subcommand;
use crate::types::AuthMethod;
#[derive(Subcommand)]
#[expect(
clippy::doc_markdown,
reason = "doc examples are literal shell commands; wrapping URLs in <> or identifiers in backticks would degrade copy-paste UX"
)]
pub enum ConfigAction {
/// Add or update a named server in the local config.
///
/// `--url` is required. Exactly one of `--api-key` (inline) or
/// `--api-key-env` (env-var indirection) must be supplied; using
/// the OS keychain instead is a separate step
/// (`bzr config set-keyring`).
///
/// TLS handling is mutually exclusive across these flags:
/// `--tls-insecure` (accept any cert), `--tls-ca-cert <path>`
/// (custom CA), `--tls-pin-sha256 <fp>` (pin a fingerprint), and
/// `--tls-pin-now` (connect once to capture the current cert and
/// pin it). `--tls-pin-clear` removes a stored pin.
///
/// `--auth-method` overrides bzr's auto-detection of header
/// vs. query-param API-key transport. Most servers don't need
/// this -- bzr probes on first use and caches the working
/// method.
///
/// Examples:
///
/// bzr config set-server prod --url <https://bz.example.com> \
/// --api-key-env BZR_API_KEY
/// bzr config set-server staging --url <https://stage.example.com> \
/// --api-key-env STAGE_KEY --tls-pin-now
/// bzr config set-server self-hosted --url <https://bz.local> \
/// --api-key-env BZR_API_KEY \
/// --tls-ca-cert /etc/pki/tls/local-ca.pem
///
/// See bzr-config-set-default(1) to pick which server `--server`
/// resolves to by default and bzr-config-set-keyring(1) for OS
/// keychain credential storage.
#[command(verbatim_doc_comment)]
SetServer {
/// Server alias name
name: String,
/// Server URL
#[arg(long)]
url: String,
/// API key, stored inline in the config file.
///
/// Mutually exclusive with `--api-key-env`; one of the two
/// is required (or use `bzr config set-keyring` to keep
/// the secret in the OS keychain instead). Inline keys can
/// leak via shell history, process args, or backup copies
/// of `config.toml` -- prefer `--api-key-env` or the
/// keyring for anything beyond a throwaway test setup.
#[arg(
long,
conflicts_with = "api_key_env",
required_unless_present = "api_key_env"
)]
api_key: Option<String>,
/// Name of an environment variable that holds the API key.
///
/// Mutually exclusive with `--api-key`. The variable is
/// resolved at command time, not at `set-server` time, so
/// rotating the key only requires updating the env var
/// (or the secret store backing it). Variable names are
/// stored verbatim in the config file; the secret itself
/// is not.
#[arg(long, conflicts_with = "api_key", required_unless_present = "api_key")]
api_key_env: Option<String>,
/// Login email used for fallback auth on older Bugzilla servers.
///
/// Required only when bzr's auto-detected auth method
/// (header API-key) is unavailable and the server falls
/// back to query-parameter auth, which uses
/// `email`+`api_key` as a credential pair. Most modern
/// Bugzilla servers don't need this.
#[arg(long)]
email: Option<String>,
/// Override bzr's auto-detected API-key transport.
///
/// Accepted values: `header` (use the
/// `X-BUGZILLA-API-KEY` HTTP header) or `query-param` (use
/// the `?api_key=...` query parameter). bzr probes both on
/// first use and caches the working method per server;
/// override only when the cached value is wrong (e.g. the
/// server changed configuration).
#[arg(long)]
auth_method: Option<AuthMethod>,
/// Accept invalid TLS certificates -- self-signed, expired, wrong host.
///
/// Disables every TLS validation check for this server.
/// Use only against a server you control or in a trusted
/// development environment; the server's responses cannot
/// be authenticated. Mutually exclusive with
/// `--tls-ca-cert`, `--tls-pin-sha256`, and
/// `--tls-pin-now`. Prefer one of those for self-signed or
/// pinned-cert deployments.
#[arg(
long,
conflicts_with_all = ["tls_ca_cert", "tls_pin_sha256", "tls_pin_now"],
)]
tls_insecure: bool,
/// Path to a PEM-encoded CA certificate file for this server.
///
/// Adds the given CA to the trust store for this server
/// without affecting other servers or the system trust
/// store. Useful for self-hosted Bugzilla instances behind
/// a private CA. Mutually exclusive with `--tls-insecure`,
/// `--tls-pin-sha256`, and `--tls-pin-now`.
#[arg(
long,
conflicts_with_all = ["tls_insecure", "tls_pin_sha256", "tls_pin_now"],
)]
tls_ca_cert: Option<String>,
/// Pin a certificate fingerprint in `sha256//<base64>` format.
///
/// The exact format used by curl's `--pinnedpubkey`. Once
/// pinned, every subsequent connection to this server
/// must present a leaf certificate whose SHA-256 hash
/// matches; mismatches exit with code 13. Mutually
/// exclusive with `--tls-insecure`, `--tls-ca-cert`,
/// `--tls-pin-now`, and `--tls-pin-clear`. Use
/// `--tls-pin-now` to capture the current cert
/// automatically instead of computing the fingerprint by
/// hand.
#[arg(
long,
conflicts_with_all = ["tls_insecure", "tls_ca_cert", "tls_pin_now", "tls_pin_clear"],
)]
tls_pin_sha256: Option<String>,
/// Connect to the server and pin its current certificate.
///
/// Issues a one-shot TLS connection, captures the leaf
/// certificate's SHA-256 fingerprint, and stores it as the
/// pin (TOFU -- trust on first use). Subsequent connections
/// require the same fingerprint. Mutually exclusive with
/// `--tls-insecure`, `--tls-ca-cert`, `--tls-pin-sha256`,
/// and `--tls-pin-clear`.
#[arg(
long,
conflicts_with_all = ["tls_insecure", "tls_ca_cert", "tls_pin_sha256", "tls_pin_clear"],
)]
tls_pin_now: bool,
/// Remove a stored certificate pin from this server.
///
/// Reverts the server to default TLS validation against
/// the OS trust store. Mutually exclusive with
/// `--tls-pin-sha256` and `--tls-pin-now` -- use one of
/// those to install a new pin in the same call as clearing
/// the old one is not supported.
#[arg(
long,
conflicts_with_all = ["tls_pin_sha256", "tls_pin_now"],
)]
tls_pin_clear: bool,
},
/// Pick which server is used when `--server` is not specified.
///
/// The default server is used by every command that doesn't
/// pass an explicit `--server <name>`. The named server must
/// already exist in the config.
///
/// Examples:
///
/// bzr config set-default prod
/// bzr config set-default staging
///
/// See bzr-config-set-server(1) to add a server before making
/// it default and bzr-config-show(1) to verify the current
/// default.
#[command(verbatim_doc_comment)]
SetDefault {
/// Server alias name
name: String,
},
/// Print the current configuration.
///
/// Lists every configured server with its URL, default-flag
/// status, and credential indirection (env-var name, keyring
/// entry, or `"<inline>"` for inline keys). Inline API keys are
/// redacted -- the secret never appears in this output. With
/// `--json`, the same data is emitted as a JSON object suitable
/// for scripting.
///
/// Use this to confirm config-file location, the resolved
/// default server, and which credential channel each server
/// uses.
///
/// Examples:
///
/// bzr config show
/// bzr config show --json | jq '.servers[] | .url'
///
/// See bzr-config-set-server(1) to add or modify entries.
#[command(verbatim_doc_comment)]
Show,
/// Store an API key for a server in the OS keychain.
///
/// Prompts on stdin for the API key (input is hidden). Stores
/// the key in the platform's native credential store (Keychain
/// on macOS, Secret Service / GNOME Keyring on Linux, Windows
/// Credential Manager on Windows) under the service name `bzr`
/// and account `<server-name>`, both of which can be overridden
/// with `--service` and `--account`.
///
/// After this completes, the server's stored `api_key` /
/// `api_key_env` field is replaced with a keyring marker, and
/// the secret is read from the keychain on each invocation.
///
/// Examples:
///
/// bzr config set-keyring prod
/// bzr config set-keyring shared --service bzr-team --account ci
///
/// Exit codes: 0 on success, 12 on keyring access errors
/// (locked keychain, daemon not running, permission denied).
///
/// See bzr-config-unset-keyring(1) to remove a stored key,
/// bzr-config-migrate-to-keyring(1) to move an existing inline
/// or env key into the keychain, and the project README's
/// "Credential storage" section for platform setup notes.
#[command(verbatim_doc_comment)]
SetKeyring {
/// Server alias name (must already exist).
name: String,
/// Override the keyring service name (defaults to `bzr`).
///
/// The service name groups related credentials in the OS
/// keychain. Override when sharing credentials across
/// multiple bzr installs or when the default `bzr`
/// collides with another tool's entries.
#[arg(long)]
service: Option<String>,
/// Override the keyring account name (defaults to the server name).
///
/// The account name identifies an individual credential
/// within the service. Override when storing multiple
/// keys for the same server (e.g. personal vs. CI).
#[arg(long)]
account: Option<String>,
},
/// Remove a server's API key from the OS keychain.
///
/// Deletes the keychain entry for `<server-name>` (or the
/// service/account configured by `set-keyring`) and clears the
/// server's keyring credential reference. The server entry is
/// preserved with no API key source; re-run `set-server` or
/// `set-keyring` afterward to re-credential it.
///
/// Examples:
///
/// bzr config unset-keyring prod
///
/// Exit codes: 0 on success, 12 on keyring access errors.
///
/// See bzr-config-set-keyring(1) for the inverse operation.
#[command(verbatim_doc_comment)]
UnsetKeyring {
/// Server alias name
name: String,
},
/// Copy an existing inline or env-var API key into the OS keychain.
///
/// Reads the server's currently configured key (whether stored
/// inline as `api_key` or read from the env var named by
/// `api_key_env`) and stores it in the OS keychain. `--yes` is
/// required to confirm the non-interactive migration. Inline
/// sources are rewritten to use the keychain; env-var sources
/// leave `config.toml` unchanged so shared env vars are not
/// removed implicitly. `--service` and `--account` override the
/// default keychain naming
/// (`bzr` / `<server-name>`).
///
/// If the env-var path is in use and the variable is unset at
/// migration time, the command fails with exit code 7 (input
/// validation).
///
/// Examples:
///
/// bzr config migrate-to-keyring prod
/// bzr config migrate-to-keyring staging --yes
///
/// See bzr-config-set-keyring(1) for storing a fresh key
/// (without reading from the existing config) and
/// bzr-config-unset-keyring(1) for the inverse direction.
#[command(verbatim_doc_comment)]
MigrateToKeyring {
/// Server alias name.
name: String,
/// Override the keyring service name (defaults to `bzr`).
#[arg(long)]
service: Option<String>,
/// Override the keyring account name (defaults to the server name).
#[arg(long)]
account: Option<String>,
/// Skip the confirmation prompt before migrating.
///
/// Without this flag, the command prints the source of
/// the existing key (inline vs. env var) and waits for a
/// `y` on stdin before writing to the keychain. Useful
/// for scripted migrations across many servers.
#[arg(long)]
yes: bool,
},
}