diff --git a/client/src/cli.rs b/client/src/cli.rs index ee86783..e96b1b6 100644 --- a/client/src/cli.rs +++ b/client/src/cli.rs @@ -9,6 +9,7 @@ use enum_as_inner::EnumAsInner; use crate::command::cache::{self, Cache}; use crate::command::get_closure::{self, GetClosure}; +use crate::command::key::{self, Key}; use crate::command::login::{self, Login}; use crate::command::push::{self, Push}; use crate::command::r#use::{self, Use}; @@ -30,6 +31,7 @@ pub enum Command { Push(Push), Cache(Cache), WatchStore(WatchStore), + Key(Key), #[clap(hide = true)] GetClosure(GetClosure), @@ -57,6 +59,7 @@ pub async fn run() -> Result<()> { Command::Cache(_) => cache::run(opts).await, Command::WatchStore(_) => watch_store::run(opts).await, Command::GetClosure(_) => get_closure::run(opts).await, + Command::Key(_) => key::run(opts).await, } } diff --git a/client/src/command/cache.rs b/client/src/command/cache.rs index af01378..af24d8c 100644 --- a/client/src/command/cache.rs +++ b/client/src/command/cache.rs @@ -7,8 +7,11 @@ use crate::api::ApiClient; use crate::cache::CacheRef; use crate::cli::Opts; use crate::config::Config; -use attic::api::v1::cache_config::{ - CacheConfig, CreateCacheRequest, KeypairConfig, RetentionPeriodConfig, +use attic::{ + api::v1::cache_config::{ + CacheConfig, CreateCacheRequest, KeypairConfig, RetentionPeriodConfig, + }, + signing::NixKeypair, }; /// Manage caches on an Attic server. @@ -72,6 +75,12 @@ struct Create { default_value = "cache.nixos.org-1" )] upstream_cache_key_names: Vec, + + /// The signing keypair to use for the cache. + /// + /// If not specified, a new keypair will be generated. + #[clap(long)] + keypair_path: Option, } /// Configure a cache. @@ -91,6 +100,14 @@ struct Configure { #[clap(long)] regenerate_keypair: bool, + /// Set a keypair for the cache. + /// + /// The server-side signing key will be set to the + /// specified keypair. This is useful for setting up + /// a cache with a pre-existing keypair. + #[clap(long, conflicts_with = "regenerate_keypair")] + keypair_path: Option, + /// Make the cache public. /// /// Use `--private` to make it private. @@ -179,9 +196,15 @@ async fn create_cache(sub: Create) -> Result<()> { let (server_name, server, cache) = config.resolve_cache(&sub.cache)?; let api = ApiClient::from_server_config(server.clone())?; + let keypair = if let Some(keypair_path) = &sub.keypair_path { + let contents = std::fs::read_to_string(keypair_path)?; + KeypairConfig::Keypair(NixKeypair::from_str(&contents)?) + } else { + KeypairConfig::Generate + }; + let request = CreateCacheRequest { - // TODO: Make this configurable? - keypair: KeypairConfig::Generate, + keypair, is_public: sub.public, priority: sub.priority, store_dir: sub.store_dir, @@ -230,6 +253,10 @@ async fn configure_cache(sub: Configure) -> Result<()> { if sub.regenerate_keypair { patch.keypair = Some(KeypairConfig::Generate); + } else if let Some(keypair_path) = &sub.keypair_path { + let contents = std::fs::read_to_string(keypair_path)?; + let keypair = KeypairConfig::Keypair(NixKeypair::from_str(&contents)?); + patch.keypair = Some(keypair); } patch.store_dir = sub.store_dir; diff --git a/client/src/command/key.rs b/client/src/command/key.rs new file mode 100644 index 0000000..807d8a7 --- /dev/null +++ b/client/src/command/key.rs @@ -0,0 +1,42 @@ +use anyhow::Result; +use clap::{Parser, Subcommand}; + +use crate::cli::Opts; +use attic::signing::NixKeypair; + +/// Manage signing keys. +#[derive(Debug, Parser)] +pub struct Key { + #[clap(subcommand)] + command: KeyCommand, +} + +#[derive(Debug, Subcommand)] +enum KeyCommand { + Generate(Generate), +} + +/// Generate a key. +#[derive(Debug, Clone, Parser)] +pub struct Generate { + /// Name of the key (must not contain colons). + name: String, +} + +pub async fn run(opts: Opts) -> Result<()> { + let sub = opts.command.as_key().unwrap(); + match &sub.command { + KeyCommand::Generate(sub) => generate_key(sub).await, + } +} + +async fn generate_key(sub: &Generate) -> Result<()> { + let keypair = NixKeypair::generate(&sub.name)?; + + println!("🔑 Generated keypair \"{}\"", sub.name); + println!(); + println!(" Private key: {}", keypair.export_keypair()); + println!(" Public key: {}", keypair.export_public_key()); + + Ok(()) +} diff --git a/client/src/command/mod.rs b/client/src/command/mod.rs index cca423f..26b105a 100644 --- a/client/src/command/mod.rs +++ b/client/src/command/mod.rs @@ -1,5 +1,6 @@ pub mod cache; pub mod get_closure; +pub mod key; pub mod login; pub mod push; pub mod r#use;