Skip to main content

cdk_mintd/
cli.rs

1use std::path::PathBuf;
2
3use clap::{Args, Parser, Subcommand};
4
5#[derive(Debug, Parser)]
6#[command(about = "A cashu mint written in rust", author = env!("CARGO_PKG_AUTHORS"), version = env!("CARGO_PKG_VERSION"))]
7pub struct CLIArgs {
8    #[arg(
9        short,
10        long,
11        help = "Use the <directory> as the location of the database",
12        required = false
13    )]
14    pub work_dir: Option<PathBuf>,
15    #[cfg(feature = "sqlcipher")]
16    #[arg(
17        short,
18        long,
19        global = true,
20        help = "Database password for SQLCipher (required when opening an encrypted database)"
21    )]
22    pub password: Option<String>,
23    #[arg(
24        short,
25        long,
26        global = true,
27        help = "Legacy startup flag; use `config init` or `config apply` instead",
28        required = false
29    )]
30    pub config: Option<PathBuf>,
31    #[arg(
32        long,
33        global = true,
34        help = "Legacy seed file; accepted only by `config migrate`",
35        required = false
36    )]
37    pub seed_file: Option<PathBuf>,
38    #[arg(
39        long,
40        help = "Enable logging output",
41        required = false,
42        action = clap::ArgAction::SetTrue,
43        default_value = "true"
44    )]
45    pub enable_logging: bool,
46    #[command(subcommand)]
47    pub command: Option<Commands>,
48}
49
50/// Commands exposed by the `cdk-mintd` binary.
51#[derive(Debug, Subcommand)]
52pub enum Commands {
53    /// Manage the database-backed mintd configuration.
54    Config(ConfigArgs),
55}
56
57/// Arguments for database-backed configuration management.
58#[derive(Debug, Args)]
59pub struct ConfigArgs {
60    #[command(subcommand)]
61    pub command: ConfigCommands,
62}
63
64/// Database-backed configuration operations.
65#[derive(Debug, Subcommand)]
66pub enum ConfigCommands {
67    /// Convert a legacy TOML plus environment overrides into an import document.
68    Migrate(MigrateConfigArgs),
69    /// Initialize an unconfigured database from a TOML document.
70    Init(InitConfigArgs),
71    /// Validate a TOML document without changing the database.
72    Validate(ConfigFileArgs),
73    /// Replace the configuration used by the next mintd start.
74    Apply(ApplyConfigArgs),
75    /// Restore the last configuration known to have been applied.
76    Rollback,
77    /// Print the stored configuration document.
78    Show,
79    /// Export the stored configuration document.
80    Export(ExportConfigArgs),
81}
82
83/// Arguments for initializing database-backed configuration.
84#[derive(Debug, Args)]
85pub struct InitConfigArgs {
86    /// TOML document to import.
87    #[arg(long)]
88    pub file: PathBuf,
89    /// Initialize a database that has never served a mint.
90    #[arg(
91        long,
92        required_unless_present = "existing_mint",
93        conflicts_with = "existing_mint"
94    )]
95    pub new_mint: bool,
96    /// Import configuration into a database containing an existing mint.
97    #[arg(
98        long,
99        required_unless_present = "new_mint",
100        conflicts_with = "new_mint"
101    )]
102    pub existing_mint: bool,
103    /// Permit an existing mint to create a BDK wallet when no wallet database exists.
104    #[arg(long, requires = "existing_mint", conflicts_with = "new_mint")]
105    pub allow_new_bdk_wallet: bool,
106}
107
108/// Arguments for migrating a legacy configuration.
109#[derive(Debug, Args)]
110pub struct MigrateConfigArgs {
111    /// Legacy TOML document to read.
112    #[arg(long)]
113    pub file: PathBuf,
114    /// Migrated TOML document to write.
115    #[arg(long)]
116    pub output: PathBuf,
117    /// Directory for literal secrets extracted from the legacy TOML.
118    #[arg(long)]
119    pub secrets_dir: Option<PathBuf>,
120    /// Overwrite generated output and secret files.
121    #[arg(long)]
122    pub force: bool,
123}
124
125/// Arguments containing a configuration document path.
126#[derive(Debug, Args)]
127pub struct ConfigFileArgs {
128    /// TOML document to read or write.
129    #[arg(long)]
130    pub file: PathBuf,
131}
132
133/// Arguments for exporting the stored configuration.
134#[derive(Debug, Args)]
135pub struct ExportConfigArgs {
136    /// TOML document to write.
137    #[arg(long)]
138    pub file: PathBuf,
139    /// Overwrite the destination if it already exists.
140    #[arg(long)]
141    pub force: bool,
142}
143
144/// Arguments for replacing the stored configuration.
145#[derive(Debug, Args)]
146pub struct ApplyConfigArgs {
147    /// TOML document to validate and store.
148    #[arg(long)]
149    pub file: PathBuf,
150    /// Validate the document and persisted constraints without writing it.
151    #[arg(long)]
152    pub validate_only: bool,
153    /// Permit creation of a BDK wallet when no wallet database exists.
154    #[arg(long)]
155    pub allow_new_bdk_wallet: bool,
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161
162    #[test]
163    fn parses_configuration_commands() {
164        CLIArgs::try_parse_from([
165            "cdk-mintd",
166            "config",
167            "init",
168            "--new-mint",
169            "--file",
170            "/tmp/mint.toml",
171        ])
172        .expect("new-mint initialization should parse");
173        CLIArgs::try_parse_from([
174            "cdk-mintd",
175            "config",
176            "init",
177            "--existing-mint",
178            "--file",
179            "/tmp/mint.toml",
180        ])
181        .expect("existing-mint initialization should parse");
182        CLIArgs::try_parse_from([
183            "cdk-mintd",
184            "config",
185            "validate",
186            "--file",
187            "/tmp/mint.toml",
188        ])
189        .expect("configuration validation should parse");
190
191        let args =
192            CLIArgs::try_parse_from(["cdk-mintd", "config", "export", "--file", "/tmp/mint.toml"])
193                .expect("configuration export should parse");
194        assert!(matches!(
195            args.command,
196            Some(Commands::Config(ConfigArgs {
197                command: ConfigCommands::Export(ExportConfigArgs { force: false, .. }),
198            }))
199        ));
200
201        let args = CLIArgs::try_parse_from([
202            "cdk-mintd",
203            "config",
204            "export",
205            "--file",
206            "/tmp/mint.toml",
207            "--force",
208        ])
209        .expect("configuration export should parse");
210        assert!(matches!(
211            args.command,
212            Some(Commands::Config(ConfigArgs {
213                command: ConfigCommands::Export(ExportConfigArgs { force: true, .. }),
214            }))
215        ));
216
217        CLIArgs::try_parse_from([
218            "cdk-mintd",
219            "config",
220            "apply",
221            "--file",
222            "/tmp/mint.toml",
223            "--validate-only",
224        ])
225        .expect("configuration apply should parse");
226        CLIArgs::try_parse_from(["cdk-mintd", "config", "show"])
227            .expect("configuration show should parse");
228        CLIArgs::try_parse_from(["cdk-mintd", "config", "rollback"])
229            .expect("configuration rollback should parse");
230
231        let args = CLIArgs::try_parse_from([
232            "cdk-mintd",
233            "config",
234            "migrate",
235            "--file",
236            "/tmp/legacy.toml",
237            "--output",
238            "/tmp/migrated.toml",
239            "--secrets-dir",
240            "/tmp/mint-secrets",
241        ])
242        .expect("configuration migration should parse");
243        assert!(matches!(
244            args.command,
245            Some(Commands::Config(ConfigArgs {
246                command: ConfigCommands::Migrate(MigrateConfigArgs { force: false, .. }),
247            }))
248        ));
249
250        let args = CLIArgs::try_parse_from([
251            "cdk-mintd",
252            "--seed-file",
253            "/tmp/seed.txt",
254            "config",
255            "migrate",
256            "--file",
257            "/tmp/legacy.toml",
258            "--output",
259            "/tmp/migrated.toml",
260        ])
261        .expect("legacy seed-file migration should parse");
262        assert_eq!(args.seed_file, Some(PathBuf::from("/tmp/seed.txt")));
263    }
264
265    #[test]
266    fn initialization_requires_exactly_one_mint_mode() {
267        assert!(CLIArgs::try_parse_from([
268            "cdk-mintd",
269            "config",
270            "init",
271            "--file",
272            "/tmp/mint.toml",
273        ])
274        .is_err());
275        assert!(CLIArgs::try_parse_from([
276            "cdk-mintd",
277            "config",
278            "init",
279            "--new-mint",
280            "--existing-mint",
281            "--file",
282            "/tmp/mint.toml",
283        ])
284        .is_err());
285        assert!(CLIArgs::try_parse_from([
286            "cdk-mintd",
287            "config",
288            "init",
289            "--new-mint",
290            "--allow-new-bdk-wallet",
291            "--file",
292            "/tmp/mint.toml",
293        ])
294        .is_err());
295
296        let args = CLIArgs::try_parse_from([
297            "cdk-mintd",
298            "config",
299            "init",
300            "--existing-mint",
301            "--allow-new-bdk-wallet",
302            "--file",
303            "/tmp/mint.toml",
304        ])
305        .expect("existing mint may explicitly allow a new BDK wallet");
306        assert!(matches!(
307            args.command,
308            Some(Commands::Config(ConfigArgs {
309                command: ConfigCommands::Init(InitConfigArgs {
310                    allow_new_bdk_wallet: true,
311                    ..
312                }),
313            }))
314        ));
315    }
316
317    #[test]
318    fn no_subcommand_still_parses_daemon_startup() {
319        let args = CLIArgs::try_parse_from(["cdk-mintd"]).expect("daemon arguments should parse");
320        assert!(args.command.is_none());
321    }
322}