bootstrap: add --single-node flag that creates a single-node layout

This commit is contained in:
Alex Auvolat
2026-02-11 09:30:35 +01:00
parent c22c4ff2e5
commit 53ace58e44
3 changed files with 83 additions and 4 deletions
+12 -1
View File
@@ -8,7 +8,7 @@ use crate::cli::local::convert_db;
pub enum Command {
/// Run Garage server
#[structopt(name = "server", version = garage_version())]
Server,
Server(ServerOpt),
/// Get network status
#[structopt(name = "status", version = garage_version())]
@@ -79,6 +79,17 @@ pub enum Command {
},
}
// ---------------------------
// ---- garage server ... ----
// ---------------------------
#[derive(StructOpt, Debug)]
pub struct ServerOpt {
/// Automatically configure a single-node layout in the cluster
#[structopt(long = "single-node")]
pub(crate) single_node: bool,
}
// -------------------------
// ---- garage node ... ----
// -------------------------
+2 -2
View File
@@ -158,7 +158,7 @@ fn main() {
async fn run(opt: Opt) -> Result<(), Error> {
match opt.cmd {
Command::Server => server::run_server(opt.config_file, opt.secrets).await,
Command::Server(sopt) => server::run_server(opt.config_file, opt.secrets, sopt).await,
Command::OfflineRepair(repair_opt) => {
cli::local::repair::offline_repair(opt.config_file, opt.secrets, repair_opt).await
}
@@ -188,7 +188,7 @@ async fn run(opt: Opt) -> Result<(), Error> {
fn init_logging(opt: &Opt) {
if std::env::var("RUST_LOG").is_err() {
let default_log = match &opt.cmd {
Command::Server => "netapp=info,garage=info",
Command::Server(_) => "netapp=info,garage=info",
_ => "netapp=warn,garage=warn",
};
+69 -1
View File
@@ -1,4 +1,5 @@
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::watch;
@@ -16,6 +17,7 @@ use garage_api_k2v::api_server::K2VApiServer;
use crate::secrets::{fill_secrets, Secrets};
use crate::tracing_setup::init_tracing;
use crate::ServerOpt;
async fn wait_from(mut chan: watch::Receiver<bool>) {
while !*chan.borrow() {
@@ -25,7 +27,11 @@ async fn wait_from(mut chan: watch::Receiver<bool>) {
}
}
pub async fn run_server(config_file: PathBuf, secrets: Secrets) -> Result<(), Error> {
pub async fn run_server(
config_file: PathBuf,
secrets: Secrets,
opt: ServerOpt,
) -> Result<(), Error> {
info!("Loading configuration...");
let config = fill_secrets(read_config(config_file)?, secrets)?;
@@ -67,6 +73,10 @@ pub async fn run_server(config_file: PathBuf, secrets: Secrets) -> Result<(), Er
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![];
@@ -164,6 +174,64 @@ pub async fn run_server(config_file: PathBuf, secrets: Secrets) -> Result<(), Er
Ok(())
}
async fn initial_config(garage: &Arc<Garage>, opt: ServerOpt) -> Result<(), Error> {
use garage_rpc::layout::*;
if opt.single_node {
let layout_version = garage.system.cluster_layout().inner().current().version;
if layout_version > 1 {
return Err(Error::Message("Refusing to run in single-node mode: layout version is already superior to 1. Remove the --single-node flag to run the server in full mode.".into()));
}
if layout_version == 0 {
// Setup initial layout
let mut layout = garage.system.cluster_layout().inner().clone();
let our_id = garage.system.id;
// Check no other nodes are present in the system
let nodes = garage.system.get_known_nodes();
if nodes.iter().any(|x| x.id != our_id) {
return Err(Error::Message("Refusing to run in single-node mode: more nodes are already present in the cluster.".into()));
}
// Automatically determine this node's capacity
let capacity = garage
.system
.local_status()
.data_disk_avail
.map(|(_avail, total)| total)
.unwrap_or(1024 * 1024 * 1024); // Default to 1GB
assert!(layout.current().roles.is_empty());
layout.staging.get_mut().roles.clear();
layout.staging.get_mut().roles.update_in_place(
our_id,
NodeRoleV(Some(NodeRole {
zone: "dc1".to_string(),
capacity: Some(capacity),
tags: vec!["default".to_string()],
})),
);
let (layout, msg) = layout.apply_staged_changes(1)?;
info!(
"Created initial layout for single-node configuration:\n{}",
msg.join("\n")
);
garage
.system
.layout_manager
.update_cluster_layout(&layout)
.await?;
}
}
Ok(())
}
#[cfg(unix)]
fn watch_shutdown_signal() -> watch::Receiver<bool> {
use tokio::signal::unix::*;