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.");
|
eprintln!("Sync complete.");
|
||||||
Ok(())
|
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)]
|
#[derive(serde::Serialize)]
|
||||||
struct ParamsFile {
|
struct ParamsFile {
|
||||||
|
|||||||
Reference in New Issue
Block a user