Add declarative attic cache

Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
2025-09-11 11:08:22 +01:00
parent ffafc81ed1
commit 1727785180
13 changed files with 194 additions and 967 deletions

View File

@@ -0,0 +1,162 @@
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<String>,
+
+ /// The signing keypair to use for the cache.
+ ///
+ /// If not specified, a new keypair will be generated.
+ #[clap(long)]
+ keypair_path: Option<String>,
}
/// 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<String>,
+
/// 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;