feat(cli): device add / list / revoke rewired to hardened git
This commit is contained in:
@@ -1252,7 +1252,77 @@ fn cmd_sync() -> Result<()> {
|
||||
eprintln!("Sync complete.");
|
||||
Ok(())
|
||||
}
|
||||
fn cmd_device(_a: DeviceAction) -> Result<()> { bail!("not yet implemented"); }
|
||||
fn cmd_device(action: DeviceAction) -> Result<()> {
|
||||
use std::fs;
|
||||
use ed25519_dalek::SigningKey;
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
let root = crate::helpers::vault_dir()?;
|
||||
let devices_path = root.join(".relicario").join("devices.json");
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
struct DeviceEntry { name: String, public_key: String }
|
||||
|
||||
match action {
|
||||
DeviceAction::Add { name } => {
|
||||
let mut existing: Vec<DeviceEntry> =
|
||||
serde_json::from_slice(&fs::read(&devices_path)?).unwrap_or_default();
|
||||
if existing.iter().any(|d| d.name == name) {
|
||||
anyhow::bail!("device `{name}` already exists");
|
||||
}
|
||||
let signing = SigningKey::generate(&mut OsRng);
|
||||
let verifying = signing.verifying_key();
|
||||
let pubkey_hex = hex::encode(verifying.to_bytes());
|
||||
|
||||
existing.push(DeviceEntry { name: name.clone(), public_key: pubkey_hex.clone() });
|
||||
fs::write(&devices_path, serde_json::to_string_pretty(&existing)?)?;
|
||||
|
||||
let cfg_dir = dirs::config_dir()
|
||||
.ok_or_else(|| anyhow::anyhow!("no config dir"))?
|
||||
.join("relicario").join("devices");
|
||||
fs::create_dir_all(&cfg_dir)?;
|
||||
let key_path = cfg_dir.join(format!("{name}.key"));
|
||||
fs::write(&key_path, signing.to_bytes())?;
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
fs::set_permissions(&key_path, fs::Permissions::from_mode(0o600))?;
|
||||
}
|
||||
|
||||
let status = crate::helpers::git_command(&root,
|
||||
&["add", ".relicario/devices.json"]).status()?;
|
||||
if !status.success() { anyhow::bail!("git add failed"); }
|
||||
let status = crate::helpers::git_command(&root,
|
||||
&["commit", "-m", &format!("device: add {name}")]).status()?;
|
||||
if !status.success() { anyhow::bail!("git commit failed"); }
|
||||
eprintln!("Added device `{name}` (pubkey: {pubkey_hex})");
|
||||
}
|
||||
DeviceAction::List => {
|
||||
let existing: Vec<DeviceEntry> =
|
||||
serde_json::from_slice(&fs::read(&devices_path)?).unwrap_or_default();
|
||||
if existing.is_empty() { eprintln!("(no devices)"); return Ok(()); }
|
||||
for d in existing {
|
||||
println!("{:<20} {}", d.name, d.public_key);
|
||||
}
|
||||
}
|
||||
DeviceAction::Revoke { name } => {
|
||||
let mut existing: Vec<DeviceEntry> =
|
||||
serde_json::from_slice(&fs::read(&devices_path)?).unwrap_or_default();
|
||||
let before = existing.len();
|
||||
existing.retain(|d| d.name != name);
|
||||
if existing.len() == before { anyhow::bail!("device `{name}` not found"); }
|
||||
fs::write(&devices_path, serde_json::to_string_pretty(&existing)?)?;
|
||||
let status = crate::helpers::git_command(&root,
|
||||
&["add", ".relicario/devices.json"]).status()?;
|
||||
if !status.success() { anyhow::bail!("git add failed"); }
|
||||
let status = crate::helpers::git_command(&root,
|
||||
&["commit", "-m", &format!("device: revoke {name}")]).status()?;
|
||||
if !status.success() { anyhow::bail!("git commit failed"); }
|
||||
eprintln!("Revoked device `{name}`");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
struct ParamsFile {
|
||||
|
||||
Reference in New Issue
Block a user