bootstrap: add --default-access-key and --default-bucket flags

This commit is contained in:
Alex Auvolat
2026-02-11 10:01:53 +01:00
parent 53ace58e44
commit 95aa3bc795
2 changed files with 125 additions and 5 deletions
+10 -1
View File
@@ -85,9 +85,18 @@ pub enum Command {
#[derive(StructOpt, Debug)]
pub struct ServerOpt {
/// Automatically configure a single-node layout in the cluster
/// Automatically configure a single-node layout in the cluster.
/// Garage will refuse to run if the cluster already has other nodes.
#[structopt(long = "single-node")]
pub(crate) single_node: bool,
/// Configure a default S3 API key using environment variables `GARAGE_DEFAULT_ACCESS_KEY` and
/// `GARAGE_DEFAULT_SECRET_KEY`. Requires `--single-node`.
#[structopt(long = "default-access-key")]
pub(crate) default_access_key: bool,
/// Configure a default bucket using environment variable `GARAGE_DEFAULT_BUCKET`.
/// Implies `--default-access-key`. Requires `--single-node`.
#[structopt(long = "default-bucket")]
pub(crate) default_bucket: bool,
}
// -------------------------
+115 -4
View File
@@ -50,6 +50,9 @@ pub async fn run_server(
info!("Initializing Garage main data store...");
let garage = Garage::new(config.clone())?;
// Handle --single-node, --default-bucket and --default-access-key
initial_config(&garage, opt).await?;
info!("Initializing background runner...");
let watch_cancel = watch_shutdown_signal();
let (background, await_background_done) = BackgroundRunner::new(watch_cancel.clone());
@@ -73,10 +76,6 @@ pub async fn run_server(
info!("Launching internal Garage cluster communications...");
let run_system = tokio::spawn(garage.system.clone().run(watch_cancel.clone()));
// ---- Run initial configuration logic ----
initial_config(&garage, opt).await?;
// ---- Launch public-facing API servers ----
let mut servers = vec![];
@@ -175,9 +174,23 @@ pub async fn run_server(
}
async fn initial_config(garage: &Arc<Garage>, opt: ServerOpt) -> Result<(), Error> {
use garage_model::bucket_alias_table::is_valid_bucket_name;
use garage_model::bucket_table::Bucket;
use garage_model::key_table::*;
use garage_model::permission::BucketKeyPerm;
use garage_rpc::layout::*;
use garage_rpc::replication_mode::ReplicationFactor;
use garage_table::*;
use garage_util::time::now_msec;
if opt.single_node {
if garage.replication_factor != ReplicationFactor::new(1).unwrap() {
return Err(Error::Message(
"Single-node mode requires replication_factor = 1 in the configuration file."
.into(),
));
}
let layout_version = garage.system.cluster_layout().inner().current().version;
if layout_version > 1 {
@@ -229,6 +242,104 @@ async fn initial_config(garage: &Arc<Garage>, opt: ServerOpt) -> Result<(), Erro
}
}
if (opt.default_bucket || opt.default_access_key) && !opt.single_node {
return Err(Error::Message(
"Flags --default-access-key and --default-bucket can only be used in single-node mode."
.into(),
));
}
if opt.default_access_key || opt.default_bucket {
let rdenv = |name: &str| {
std::env::var(name)
.map_err(|_| Error::Message(format!("Environment variable `{}` is not set", name)))
};
// Create default access key if it does not exist
let key_id = rdenv("GARAGE_DEFAULT_ACCESS_KEY")?;
let secret_key = rdenv("GARAGE_DEFAULT_SECRET_KEY")?;
let existing_key = garage.key_table.get(&EmptyKey, &key_id).await?;
let key = match existing_key {
Some(key) => {
match key.state.as_option() {
None => return Err(Error::Message(format!("Access key {} was deleted in the cluster, cannot add it back", key_id))),
Some(st) if st.secret_key != secret_key => return Err(Error::Message(format!("Access key {} is associated with a secret key different than the one given in GARAGE_DEFAULT_SECRET_KEY", key_id))),
_ => (),
}
key
}
None => {
info!("Creating default access key `{}`", key_id);
let mut key = Key::import(&key_id, &secret_key, "default access key")
.map_err(|e| Error::Message(format!("Invalid default access key: {}", e)))?;
key.state
.as_option_mut()
.unwrap()
.allow_create_bucket
.update(true);
garage.key_table.insert(&key).await?;
key
}
};
if opt.default_bucket {
// Create default bucket if it does not exist
let bucket_name = rdenv("GARAGE_DEFAULT_BUCKET")?;
if !is_valid_bucket_name(&bucket_name, garage.config.allow_punycode) {
return Err(Error::Message(
"Invalid default bucket name, see S3 specification for allowed bucket names."
.into(),
));
}
let helper = garage.locked_helper().await;
let bucket = match helper.bucket().resolve_global_bucket_fast(&bucket_name)? {
Some(bucket) => bucket,
None => {
info!("Creating default bucket `{}`", bucket_name);
let bucket = Bucket::new();
garage.bucket_table.insert(&bucket).await?;
helper
.set_global_bucket_alias(bucket.id, &bucket_name)
.await
.map_err(|e| {
Error::Message(format!("Cannot create default bucket: {}", e))
})?;
bucket
}
};
helper
.set_bucket_key_permissions(
bucket.id,
&key.key_id,
BucketKeyPerm {
timestamp: now_msec(),
allow_read: true,
allow_write: true,
allow_owner: true,
},
)
.await
.map_err(|e| {
Error::Message(format!(
"Cannot configure permissions on default bucket: {}",
e
))
})?;
}
}
Ok(())
}