Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(env): Make rustic more portable #1059

Closed
wants to merge 11 commits into from
37 changes: 33 additions & 4 deletions config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,40 @@ options. Therefore `commandline arguments` have the highest precedence.

## Profiles

To use different configurations for different repositories, you can use
profiles.

For example, you can create a profile called `myconfig` and use it with the `-P`
option, e.g. `rustic -P myconfig`. The configuration file for the profile
`myconfig` should be named `myconfig.toml`. Examples for different configuration
files can be found here in the [/config/](/config) directory.

### Profile Locations

#### \*nix

Configuration files can be placed in the user's local config directory, e.g.
`~/.config/rustic/` or in the global config dir, e.g. `/etc/rustic/`. You can
use different config files, e.g. `myconfig.toml` and use the `-P` option to
specify the profile name, e.g. `rustic -P myconfig`. Examples for different
configuration files can be found here in the [/config/](/config) directory.
`~/.config/rustic/` or in the global config dir, e.g. `/etc/rustic/`.

#### Windows

On Windows, the configuration file can be placed in the user's local config
directory, e.g. `C:\Users\username\AppData\Roaming\rustic\` or in the global
config dir, e.g. `C:\ProgramData\rustic\config`. The global config directory is
usually not created by the installer, so you may have to create it yourself. You
can also use your User Profile directory, e.g. `C:\Users\username\` and place
the configuration file in the `.rustic` or `.config\rustic` directory.

#### MacOS

On MacOS, the configuration file can be placed in the user's local config
directory, e.g. `~/Library/Application Support/rustic/`.

#### Custom Directory

If you prefer to use your custom `rustic` directory, you can set `RUSTIC_HOME`
environment variable to point to your custom directory. In it you can place your
configuration file in the `config` subdirectory.

## Services

Expand Down
66 changes: 52 additions & 14 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,20 +175,61 @@ pub struct GlobalOptions {
///
/// A vector of [`PathBuf`]s to the config files
fn get_config_paths(filename: &str) -> Vec<PathBuf> {
[
// we need this mut here, because we want to add data to the Vec
// depending on the OS. Our CI is running on Unix, and doesn't have knowledge of
// us mutating that vector
#[allow(unused_mut)]
let mut paths = vec![
get_home_config_path(),
Copy link
Member

@aawsome aawsome Feb 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not simply add two entries

std::env::var_os("USERPROFILE").map(|path| {
PathBuf::from(path).join(r".config\rustic")),
std::env::var_os("USERPROFILE").map(|path| {
PathBuf::from(path).join(r".rustic")),

here? If this seems to heavy, create 2 functions which return these results.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I don't understand. Instead of what should we do that, you marked get_home_config_path(),?

Copy link
Contributor Author

@simonsan simonsan Feb 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean like that:

    let mut paths = vec![
        get_home_config_path(),
        ProjectDirs::from("", "", "rustic")
            .map(|project_dirs| project_dirs.config_dir().to_path_buf()),
        get_global_config_path(),
        Some(PathBuf::from(".")),
        #[cfg(target_os = "windows")]
        std::env::var_os("USERPROFILE").map(|path| PathBuf::from(path).join(r".config\rustic")),
        #[cfg(target_os = "windows")]
        std::env::var_os("USERPROFILE").map(|path| PathBuf::from(path).join(".rustic")),
    ];

Copy link
Contributor Author

@simonsan simonsan Feb 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that was the idea, I found it a bit cleaner, although we need to have all that lint deactivated and mutable variable, to have that in its own function. Just because, we can have access to that, to just get the config paths singly, when we need them.

For example, this:

fn get_config_paths(filename: &str) -> Vec<PathBuf> {
    vec![
        std::env::var_os("RUSTIC_HOME").map(|home_dir| PathBuf::from(home_dir).join("config")),
        ProjectDirs::from("", "", "rustic")
            .map(|project_dirs| project_dirs.config_dir().to_path_buf()),
        get_global_config_path(),
        Some(PathBuf::from(".")),
        #[cfg(target_os = "windows")]
        std::env::var_os("USERPROFILE").map(|path| PathBuf::from(path).join(r".config\rustic")),
        #[cfg(target_os = "windows")]
        std::env::var_os("USERPROFILE").map(|path| PathBuf::from(path).join(".rustic")),
    ]
    .into_iter()
    .filter_map(|path| path.map(|p| p.join(filename)))
    .collect::<Vec<_>>()
}

would work, but would not let us have RUSTIC_HOME access in a function, if we just need it for something else, e.g. opening a pdf in that folder, when we would want to open documentation or anything else in the home folder. Also I feel it makes it a bit 'chaotic' somehow, at least from reading it, but this may be personal preference.

Hence I think extracting these things into their own functions is better long-term.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, then simply use get_userprofile_config_rustic() and get_userprofile_rustic() or something like this.

Copy link
Contributor Author

@simonsan simonsan Feb 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about it, all those directories are a bit different:

RUSTIC_HOME: we use RUSTIC_HOME/config here for the configs, RUSTIC_HOME/logs I reserved on scoop for log files
USERPROFILE\.config\rustic: we use a root directory here for the configs
USERPROFILE\.rustic: same here, we use a root directory

I think consistency is important in that regard, so people can for example use their rustic-package unpack it, change some configs and start. Which would mean, that we would need config/ as a subdirectory in each of these, although it doesn't make a lot of sense in
USERPROFILE\.config\rustic to have another subdir for config. But then, we also can't use this directory as a kind of HOME directory, where for example also log files would be found and other resources. 🤔

ProjectDirs::from("", "", "rustic")
.map(|project_dirs| project_dirs.config_dir().to_path_buf()),
get_global_config_path(),
Some(PathBuf::from(".")),
]
.into_iter()
.filter_map(|path| {
path.map(|mut p| {
p.push(filename);
p
})
];

#[cfg(target_os = "windows")]
{
if let Some(win_compatibility_paths) = get_windows_portability_config_directories() {
paths.extend(win_compatibility_paths);
};
}

paths
.into_iter()
.filter_map(|path| path.map(|p| p.join(filename)))
.collect::<Vec<_>>()
}

/// Get the path to the home config directory.
///
/// # Returns
///
/// The path to the home config directory.
///
/// # Note
///
/// If the environment variable `RUSTIC_HOME` is not set, `None` is returned.
fn get_home_config_path() -> Option<PathBuf> {
std::env::var_os("RUSTIC_HOME").map(|home_dir| PathBuf::from(home_dir).join("config"))
}

/// Get the paths to the user profile config directories on Windows.
///
/// # Returns
///
/// A collection of possible paths to the user profile config directory on Windows.
///
/// # Note
///
/// If the environment variable `USERPROFILE` is not set, `None` is returned.
#[cfg(target_os = "windows")]
fn get_windows_portability_config_directories() -> Option<Vec<Option<PathBuf>>> {
std::env::var_os("USERPROFILE").map(|path| {
vec![
Some(PathBuf::from(path.clone()).join(r".config\rustic")),
Some(PathBuf::from(path).join(".rustic")),
]
})
.collect()
}

/// Get the path to the global config directory on Windows.
Expand All @@ -199,11 +240,8 @@ fn get_config_paths(filename: &str) -> Vec<PathBuf> {
/// If the environment variable `PROGRAMDATA` is not set, `None` is returned.
#[cfg(target_os = "windows")]
fn get_global_config_path() -> Option<PathBuf> {
std::env::var_os("PROGRAMDATA").map(|program_data| {
let mut path = PathBuf::from(program_data);
path.push(r"rustic\config");
path
})
std::env::var_os("PROGRAMDATA")
.map(|program_data| PathBuf::from(program_data).join(r"rustic\config"))
}

/// Get the path to the global config directory on ios and wasm targets.
Expand Down
Loading