rustic_rs/commands/
init.rs

1//! `init` subcommand
2
3use abscissa_core::{status_err, Command, Runnable, Shutdown};
4use anyhow::{bail, Result};
5use dialoguer::Password;
6
7use crate::{repository::CliRepo, Application, RUSTIC_APP};
8
9use rustic_core::{ConfigOptions, KeyOptions, OpenStatus, Repository};
10
11/// `init` subcommand
12#[derive(clap::Parser, Command, Debug)]
13pub(crate) struct InitCmd {
14    /// Key options
15    #[clap(flatten, next_help_heading = "Key options")]
16    key_opts: KeyOptions,
17
18    /// Config options
19    #[clap(flatten, next_help_heading = "Config options")]
20    config_opts: ConfigOptions,
21}
22
23impl Runnable for InitCmd {
24    fn run(&self) {
25        if let Err(err) = RUSTIC_APP
26            .config()
27            .repository
28            .run(|repo| self.inner_run(repo))
29        {
30            status_err!("{}", err);
31            RUSTIC_APP.shutdown(Shutdown::Crash);
32        };
33    }
34}
35
36impl InitCmd {
37    fn inner_run(&self, repo: CliRepo) -> Result<()> {
38        let config = RUSTIC_APP.config();
39
40        // Note: This is again checked in repo.init_with_password(), however we want to inform
41        // users before they are prompted to enter a password
42        if repo.config_id()?.is_some() {
43            bail!("Config file already exists. Aborting.");
44        }
45
46        // Handle dry-run mode
47        if config.global.dry_run {
48            bail!(
49                "cannot initialize repository {} in dry-run mode!",
50                repo.name
51            );
52        }
53
54        let _ = init(repo.0, &self.key_opts, &self.config_opts)?;
55        Ok(())
56    }
57}
58
59/// Initialize repository
60///
61/// # Arguments
62///
63/// * `repo` - Repository to initialize
64/// * `key_opts` - Key options
65/// * `config_opts` - Config options
66///
67/// # Errors
68///
69///  * [`RepositoryErrorKind::OpeningPasswordFileFailed`] - If opening the password file failed
70/// * [`RepositoryErrorKind::ReadingPasswordFromReaderFailed`] - If reading the password failed
71/// * [`RepositoryErrorKind::FromSplitError`] - If splitting the password command failed
72/// * [`RepositoryErrorKind::PasswordCommandExecutionFailed`] - If executing the password command failed
73/// * [`RepositoryErrorKind::ReadingPasswordFromCommandFailed`] - If reading the password from the command failed
74///
75/// # Returns
76///
77/// Returns the initialized repository
78///
79/// [`RepositoryErrorKind::OpeningPasswordFileFailed`]: rustic_core::error::RepositoryErrorKind::OpeningPasswordFileFailed
80/// [`RepositoryErrorKind::ReadingPasswordFromReaderFailed`]: rustic_core::error::RepositoryErrorKind::ReadingPasswordFromReaderFailed
81/// [`RepositoryErrorKind::FromSplitError`]: rustic_core::error::RepositoryErrorKind::FromSplitError
82/// [`RepositoryErrorKind::PasswordCommandExecutionFailed`]: rustic_core::error::RepositoryErrorKind::PasswordCommandExecutionFailed
83/// [`RepositoryErrorKind::ReadingPasswordFromCommandFailed`]: rustic_core::error::RepositoryErrorKind::ReadingPasswordFromCommandFailed
84pub(crate) fn init<P, S>(
85    repo: Repository<P, S>,
86    key_opts: &KeyOptions,
87    config_opts: &ConfigOptions,
88) -> Result<Repository<P, OpenStatus>> {
89    let pass = init_password(&repo)?;
90    Ok(repo.init_with_password(&pass, key_opts, config_opts)?)
91}
92
93pub(crate) fn init_password<P, S>(repo: &Repository<P, S>) -> Result<String> {
94    let pass = repo.password()?.unwrap_or_else(|| {
95        match Password::new()
96            .with_prompt("enter password for new key")
97            .allow_empty_password(true)
98            .with_confirmation("confirm password", "passwords do not match")
99            .interact()
100        {
101            Ok(it) => it,
102            Err(err) => {
103                status_err!("{}", err);
104                RUSTIC_APP.shutdown(Shutdown::Crash);
105            }
106        }
107    });
108
109    Ok(pass)
110}