use std::{path::Path, process::Command};
use log::debug;
use rand::{RngExt, distr::Alphanumeric, rng};
use yescrypt::{PasswordHasher, Yescrypt};
use crate::{Error, get_command};
pub const DEFAULT_SHELL: &str = "/usr/bin/bash";
pub fn create_users(
users: &[&str],
home_base_dir: Option<&Path>,
shell: Option<&Path>,
) -> Result<(), Error> {
let useradd_command = get_command("useradd")?;
let usermod_command = get_command("usermod")?;
debug!("Creating users: {}", users.join(", "));
for user in users {
debug!("Creating user: {user}");
let mut command = Command::new(&useradd_command);
let command = command
.arg("--create-home")
.arg("--user-group")
.arg("--shell")
.arg(shell.unwrap_or(Path::new(DEFAULT_SHELL)));
let command = if let Some(path) = home_base_dir.as_ref() {
command.arg("--base-dir").arg(path)
} else {
command
};
let command = command.arg(user);
let command_output = command.output().map_err(|source| Error::CommandExec {
command: format!("{command:?}"),
source,
})?;
if !command_output.status.success() {
return Err(crate::Error::CommandNonZero {
command: format!("{command:?}"),
exit_status: command_output.status,
stderr: String::from_utf8_lossy(&command_output.stderr).into_owned(),
});
}
let random_passphrase: String = rng()
.sample_iter(&Alphanumeric)
.take(30)
.map(char::from)
.collect();
let yescrypt = Yescrypt::default();
let passphrase_hash = yescrypt
.hash_password(random_passphrase.as_bytes())
.map_err(|source| Error::PasswordHash {
context: format!("creating a passphrase hash for user {user}"),
source,
})?;
let mut command = Command::new(&usermod_command);
command.arg("--password");
command.arg(passphrase_hash.as_str());
command.arg(user);
let command_output = command.output().map_err(|source| Error::CommandExec {
command: format!("{command:?}"),
source,
})?;
if !command_output.status.success() {
return Err(Error::CommandNonZero {
command: format!("{command:?}"),
exit_status: command_output.status,
stderr: String::from_utf8_lossy(&command_output.stderr).into_owned(),
});
}
}
Ok(())
}