xdbm/src/cmd_init.rs
2025-03-01 23:45:22 +09:00

189 lines
5.9 KiB
Rust

//! Init subcommand.
//! Initialize xdbm for the device.
use crate::backups::Backups;
use crate::storages::{STORAGESFILE, Storages};
use crate::{
DEVICESFILE, Device, add_and_commit, backups,
devices::{get_devices, write_devices},
full_status,
};
use anyhow::{Context, Ok, Result, anyhow};
use core::panic;
use git2::{Cred, RemoteCallbacks, Repository};
use inquire::Password;
use std::fs::{DirBuilder, File};
use std::io::{BufWriter, Write};
use std::path::{self, Path, PathBuf};
fn clone_repo(
repo_url: &str,
use_sshagent: bool,
ssh_key: Option<PathBuf>,
config_dir: &path::PathBuf,
) -> Result<Repository> {
// dont use credentials
if ssh_key.is_none() && !use_sshagent {
info!("No authentication will be used.");
info!("Use either ssh_key or ssh-agent to access private repository");
return Ok(Repository::clone(repo_url, config_dir)?);
}
// using credentials
let mut callbacks = RemoteCallbacks::new();
callbacks.credentials(|_url, username_from_url, _allowed_types| {
if let Some(key) = &ssh_key {
info!("Using provided ssh key to access the repository");
let passwd = match Password::new("SSH passphrase").prompt() {
std::result::Result::Ok(s) => Some(s),
Err(err) => {
error!("Failed to get ssh passphrase: {:?}", err);
None
}
};
Cred::ssh_key(
username_from_url.ok_or(git2::Error::from_str("No username found from the url"))?,
None,
key as &Path,
passwd.as_deref(),
)
} else if use_sshagent {
// use ssh agent
info!("Using ssh agent to access the repository");
Cred::ssh_key_from_agent(
username_from_url.ok_or(git2::Error::from_str("No username found from the url"))?,
)
} else {
error!("no ssh_key and use_sshagent");
panic!("This option must be unreachable.")
}
});
// fetch options
let mut fo = git2::FetchOptions::new();
fo.remote_callbacks(callbacks);
let mut builder = git2::build::RepoBuilder::new();
builder.fetch_options(fo);
Ok(builder.clone(repo_url, config_dir)?)
}
pub(crate) fn cmd_init(
device_name: String,
repo_url: Option<String>,
use_sshagent: bool,
ssh_key: Option<PathBuf>,
config_dir: &path::PathBuf,
) -> Result<()> {
if config_dir.join(DEVICESFILE).exists() {
debug!("{} already exists.", DEVICESFILE);
return Err(anyhow!("This device is already added."));
}
// validate device name
if device_name.chars().count() == 0 {
log::error!("Device name cannot by empty");
return Err(anyhow!("Device name is empty"));
}
// get repo or initialize it
let repo = match repo_url {
Some(repo_url) => {
trace!("repo: {}", repo_url);
clone_repo(&repo_url, use_sshagent, ssh_key, config_dir)?
}
None => {
trace!("No repo provided");
println!("Initializing for the first device...");
// create repository
let repo = Repository::init(config_dir)?;
// set up gitignore
{
let f = File::create(config_dir.join(".gitignore"))?;
{
let mut buf = BufWriter::new(f);
buf.write_all("devname".as_bytes())?;
}
add_and_commit(&repo, Path::new(".gitignore"), "Add devname to gitignore.")?;
full_status(&repo)?;
}
// TDOO: wrap up below into one commit?
// set up devices.yml
let devices: Vec<Device> = vec![];
write_devices(config_dir, devices)?;
add_and_commit(
&repo,
Path::new(DEVICESFILE),
&format!("Initialize {}", DEVICESFILE),
)?;
// set up storages.yml
let storages = Storages::new();
storages.write(config_dir)?;
add_and_commit(
&repo,
Path::new(STORAGESFILE),
&format!("Initialize {}", STORAGESFILE),
)?;
// set up directory for backups
DirBuilder::new().create(config_dir.join(backups::BACKUPSDIR))?;
repo
}
};
full_status(&repo)?;
// set device name
// let device = set_device_name()?;
let device = Device::new(device_name);
trace!("Device information: {:?}", device);
// save devname
let devname_path = &config_dir.join("devname");
{
let f = File::create(devname_path)
.context("Failed to create a file to store local device name")?;
let writer = BufWriter::new(f);
serde_yaml::to_writer(writer, &device.name()).unwrap();
};
full_status(&repo)?;
// Add new device to devices.yml
{
let mut devices: Vec<Device> = get_devices(config_dir)?;
trace!("devices: {:?}", devices);
if devices.iter().any(|x| x.name() == device.name()) {
error!("Device name `{}` is already used.", device.name());
error!("Clear the config directory and try again with different name");
return Err(anyhow!("device name is already used."));
}
devices.push(device.clone());
trace!("Devices: {:?}", devices);
write_devices(config_dir, devices)?;
}
full_status(&repo)?;
// commit
add_and_commit(
&repo,
Path::new(DEVICESFILE),
&format!("Add new device: {}", &device.name()),
)?;
// backups/[device].yml
{
let backups = Backups::new();
backups.write(config_dir, &device)?;
}
add_and_commit(
&repo,
&backups::backups_file(&device),
&format!("Add new backups for device: {}", &device.name()),
)?;
println!("Device added");
full_status(&repo)?;
Ok(())
}