From 53ace58e44e9840ee0d8a357803b078f3e9cf587 Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Wed, 11 Feb 2026 09:30:35 +0100 Subject: [PATCH 1/5] bootstrap: add `--single-node` flag that creates a single-node layout --- src/garage/cli/structs.rs | 13 +++++++- src/garage/main.rs | 4 +-- src/garage/server.rs | 70 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 83 insertions(+), 4 deletions(-) diff --git a/src/garage/cli/structs.rs b/src/garage/cli/structs.rs index 7fd2defa..4bb6dc0a 100644 --- a/src/garage/cli/structs.rs +++ b/src/garage/cli/structs.rs @@ -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 ... ---- // ------------------------- diff --git a/src/garage/main.rs b/src/garage/main.rs index 6a8c9412..0ab64086 100644 --- a/src/garage/main.rs +++ b/src/garage/main.rs @@ -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", }; diff --git a/src/garage/server.rs b/src/garage/server.rs index f9cd5631..7f80b205 100644 --- a/src/garage/server.rs +++ b/src/garage/server.rs @@ -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) { while !*chan.borrow() { @@ -25,7 +27,11 @@ async fn wait_from(mut chan: watch::Receiver) { } } -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, 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 { use tokio::signal::unix::*; From 95aa3bc795eeecac7dffaf67abac0a0de2aaf0e1 Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Wed, 11 Feb 2026 10:01:53 +0100 Subject: [PATCH 2/5] bootstrap: add `--default-access-key` and `--default-bucket` flags --- src/garage/cli/structs.rs | 11 +++- src/garage/server.rs | 119 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 125 insertions(+), 5 deletions(-) diff --git a/src/garage/cli/structs.rs b/src/garage/cli/structs.rs index 4bb6dc0a..9073991d 100644 --- a/src/garage/cli/structs.rs +++ b/src/garage/cli/structs.rs @@ -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, } // ------------------------- diff --git a/src/garage/server.rs b/src/garage/server.rs index 7f80b205..93c29562 100644 --- a/src/garage/server.rs +++ b/src/garage/server.rs @@ -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, 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, 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(()) } From 00cbc5c31df770fe1b046e5fd7c0b251cc25ab07 Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Wed, 11 Feb 2026 10:13:59 +0100 Subject: [PATCH 3/5] relax requirements on imported access keys to allow easier transition from other S3 storage providers (fix #1262) --- src/model/key_table.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/model/key_table.rs b/src/model/key_table.rs index ded9832d..6eeae171 100644 --- a/src/model/key_table.rs +++ b/src/model/key_table.rs @@ -155,12 +155,23 @@ impl Key { /// Import a key from it's parts pub fn import(key_id: &str, secret_key: &str, name: &str) -> Result { - if key_id.len() != 26 || &key_id[..2] != "GK" || hex::decode(&key_id[2..]).is_err() { - return Err("The specified key ID is not a valid Garage key ID (starts with `GK`, followed by 12 hex-encoded bytes)"); + if key_id.len() < 8 { + return Err("Key identifiers should be at least 8 characters long"); } - if secret_key.len() != 64 || hex::decode(secret_key).is_err() { - return Err("The specified secret key is not a valid Garage secret key (composed of 32 hex-encoded bytes)"); + if !key_id + .chars() + .all(|c| c.is_ascii_alphanumeric() || "-_.".contains(c)) + { + return Err("Key identifiers should be composed only of ASCII alphanumeric characters and characters '-', '_' and '.'"); + } + + if secret_key.len() < 16 { + return Err("Secret keys should be at least 16 characters long"); + } + + if !secret_key.chars().all(|c| c.is_ascii_graphic()) { + return Err("Secret keys should be composed only of graphic ASCII characters (U+0021 to U+007E)"); } Ok(Self { From f7ec4b13385d267738a1f3b4490a9f371b45e91a Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Wed, 11 Feb 2026 10:49:46 +0100 Subject: [PATCH 4/5] update quick start guide --- doc/book/cookbook/real-world.md | 2 +- doc/book/quick-start/_index.md | 347 +++++++++++++++++++------------- 2 files changed, 211 insertions(+), 138 deletions(-) diff --git a/doc/book/cookbook/real-world.md b/doc/book/cookbook/real-world.md index 681346cb..4a491193 100644 --- a/doc/book/cookbook/real-world.md +++ b/doc/book/cookbook/real-world.md @@ -103,7 +103,7 @@ to check [the most recent versions on the Docker Hub](https://hub.docker.com/r/d For example: ``` -sudo docker pull dxflrs/garage:v2.2.0 +docker pull dxflrs/garage:v2.2.0 ``` ## Deploying and configuring Garage diff --git a/doc/book/quick-start/_index.md b/doc/book/quick-start/_index.md index e9394327..d5f7be46 100644 --- a/doc/book/quick-start/_index.md +++ b/doc/book/quick-start/_index.md @@ -43,12 +43,10 @@ or if you want a build customized for your system, you can [build Garage from source](@/documentation/cookbook/from-source.md). If none of these option work for you, you can also run Garage in a Docker -container. When using Docker, the commands used in this guide will not work -anymore. We recommend reading the tutorial on [configuring a -multi-node cluster](@/documentation/cookbook/real-world.md) to learn about -using Garage as a Docker container. For simplicity, a minimal command to launch -Garage using Docker is provided in this quick start guide as well. - +container. For simplicity, a minimal command to launch Garage using Docker is +provided in this quick start guide. We recommend reading the tutorial on +[configuring a multi-node cluster](@/documentation/cookbook/real-world.md) to +learn about the full Docker workflow for Garage. ## Configuring and starting Garage @@ -82,9 +80,6 @@ bind_addr = "[::]:3902" root_domain = ".web.garage.localhost" index = "index.html" -[k2v_api] -api_bind_addr = "[::]:3904" - [admin] api_bind_addr = "[::]:3903" admin_token = "$(openssl rand -base64 32)" @@ -95,10 +90,13 @@ EOF See the [Configuration file format](https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/) for complete options and values. -Now that your configuration file has been created, you may save it to the directory of your choice. -By default, Garage looks for **`/etc/garage.toml`.** -You can also store it somewhere else, but you will have to specify `-c path/to/garage.toml` -at each invocation of the `garage` binary (for example: `garage -c ./garage.toml server`, `garage -c ./garage.toml status`). +By default, Garage looks for its configuration file in **`/etc/garage.toml`.** +Since we have written our configuration file in the working directory, we will have to set +the following environment variable: + +```bash +export GARAGE_CONFIG_FILE=$(pwd)/garage.toml +``` As you can see, the `rpc_secret` is a 32 bytes hexadecimal string. You can regenerate it with `openssl rand -hex 32`. @@ -111,15 +109,36 @@ Garage server will not be persistent. Change these to locations on your local di your data to be persisted properly. +### Configuring initial access credentials + +Since `v2.3.0`, Garage can automatically create a default access key and a default storage bucket, +based on values provided in environment variables. + +To use this feature, export the following environment variables: + +```bash +export GARAGE_DEFAULT_ACCESS_KEY="GK$(openssl rand -hex 16)" +export GARAGE_DEFAULT_SECRET_KEY="$(openssl rand -hex 32)" +export GARAGE_DEFAULT_BUCKET="default-bucket" +``` + +The example above creates a random access key ID and associated secret key. +You can also provide an access key ID and secret key of your own. + ### Launching the Garage server Use the following command to launch the Garage server: -``` -garage -c path/to/garage.toml server +```bash +garage server --single-node --default-bucket ``` -If you have placed the `garage.toml` file in `/etc` (its default location), you can simply run `garage server`. +The `--single-node` flag instructs Garage to automatically configure a single-node cluster without data replication. +The `--default-bucket` flag instructs Garage to create a default access key and a default bucket using the environment variables we defined above. +Both flags are optional and can be omitted, in which case you will have to follow manual configuration steps described below. + +**For older versions of Garage (before v2.3.0):** automatic configuration using `--single-node` and `--default-bucket` is not available, +you must follow the manual configuration steps. Alternatively, if you cannot or do not wish to run the Garage binary directly, you may use Docker to run Garage in a container using the following command: @@ -127,21 +146,58 @@ you may use Docker to run Garage in a container using the following command: ```bash docker run \ -d \ - --name garaged \ + --name garage-container \ -p 3900:3900 -p 3901:3901 -p 3902:3902 -p 3903:3903 \ - -v /path/to/garage.toml:/etc/garage.toml \ - -v /tmp/meta:/tmp/meta \ - -v /tmp/data:/tmp/data \ + -v $(pwd)/garage.toml:/etc/garage.toml \ + -e GARAGE_DEFAULT_ACCESS_KEY \ + -e GARAGE_DEFAULT_SECRET_KEY \ + -e GARAGE_DEFAULT_BUCKET \ dxflrs/garage:v2.2.0 + /garage server --single-node --default-bucket ``` -Under Linux, you can substitute `--network host` for `-p 3900:3900 -p 3901:3901 -p 3902:3902 -p 3903:3903` +Note that this command will NOT create persistent volumes for Garage's data, so +your cluster will be wiped if the container terminates. To persist Garage's +data, you must manually add volumes for the `data` and `metadata` directories +and configure their correct paths in your `garage.toml` files (see [configuring +a multi-node cluster](@/documentation/cookbook/real-world.md)). -#### Troubleshooting +Under Linux, you can substitute `--network host` for `-p 3900:3900 -p 3901:3901 -p 3902:3902 -p 3903:3903`. + +### Checking that Garage runs correctly + +The `garage` utility is also used as a CLI tool to administrate your Garage +deployment. It needs read access to your configuration file and to the metadata directory +to obtain connection parameters to contact the local Garage node. + +Use the following command to show the status of your cluster: + +``` +garage status +``` + +If you are running Garage in a Docker container, you can use the following command instead: + +```bash +docker exec garage-container /garage status +``` + +This should show something like this: + +``` +==== HEALTHY NODES ==== +ID Hostname Address Tags Zone Capacity DataAvail Version +563e1ac825ee3323 linuxbox 127.0.0.1:3901 [default] dc1 19.9 GiB 19.5 GiB (97.6%) v2.3.0 +``` + +### Troubleshooting Ensure your configuration file, `metadata_dir` and `data_dir` are readable by the user running the `garage` server or Docker. -You can tune Garage's verbosity by setting the `RUST_LOG=` environment variable. \ +When running the `garage` CLI, ensure that the path to your configuration file is correctly specified (see below), +and that it can read it and read from your metadata directory. + +You can tune Garage's verbosity by setting the `RUST_LOG=` environment variable. Available log levels are (from less verbose to more verbose): `error`, `warn`, `info` *(default)*, `debug` and `trace`. ```bash @@ -154,36 +210,135 @@ Log level `info` is the default value and is recommended for most use cases. Log level `debug` can help you check why your S3 API calls are not working. -### Checking that Garage runs correctly -The `garage` utility is also used as a CLI tool to configure your Garage deployment. -It uses values from the TOML configuration file to find the Garage daemon running on the -local node, therefore if your configuration file is not at `/etc/garage.toml` you will -again have to specify `-c path/to/garage.toml` at each invocation. +## Uploading and downloading from Garage -If you are running Garage in a Docker container, you can set `alias garage="docker exec -ti /garage"` -to use the Garage binary inside your container. +This section will show how to download and upload files on Garage using a third-party tool named `awscli`. -If the `garage` CLI is able to correctly detect the parameters of your local Garage node, -the following command should be enough to show the status of your cluster: -``` -garage status +### Install and configure `awscli` + +If you have python on your system, you can install it with: + +```bash +python -m pip install --user awscli ``` -This should show something like this: +Now that `awscli` is installed, you must configure it to talk to your Garage +instance using the credentials defined above. Here is a simple way to create +a configuration file in `~/.awsrc` using a single command that will save the +secrets from your environment: + +```bash +cat > ~/.awsrc <=1.29.0` or `>=2.13.0`, otherwise you +need to specify `--endpoint-url` explicitly on each `awscli` invocation. + +Now, each time you want to use `awscli` on this target, run: + +```bash +source ~/.awsrc +``` + +*You can create multiple files with different names if you +have multiple Garage clusters or different keys. +Switching from one cluster to another is as simple as +sourcing the right file.* + +### Example usage of `awscli` + +```bash +# list buckets +aws s3 ls + +# list objects of a bucket +aws s3 ls s3://default-bucket + +# copy from your filesystem to garage +aws s3 cp /proc/cpuinfo s3://default-bucket/cpuinfo.txt + +# copy from garage to your filesystem +aws s3 cp s3://default-bucket/cpuinfo.txt /tmp/cpuinfo.txt +``` + +Note that you can use `awscli` for more advanced operations like +creating a bucket, pre-signing a request or managing your website. +[Read the full documentation to know more](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3/index.html). + +Some features are however not implemented like ACL or policy. +Check [our S3 compatibility list](@/documentation/reference-manual/s3-compatibility.md). + +### Other tools for interacting with Garage + +The following tools can also be used to send and receive files from/to Garage: + +- [minio-client](@/documentation/connect/cli.md#minio-client) +- [s3cmd](@/documentation/connect/cli.md#s3cmd) +- [rclone](@/documentation/connect/cli.md#rclone) +- [Cyberduck](@/documentation/connect/cli.md#cyberduck) +- [WinSCP](@/documentation/connect/cli.md#winscp) + +An exhaustive list is maintained in the ["Integrations" > "Browsing tools" section](@/documentation/connect/_index.md). + + + +## Manual configuration + +This section provides instructions that are equivalent to using the +`--single-node` and `--default-bucket` flags for automatic configuration. If +you are using an older version of Garage (before v2.3.0), you must follow +these instructions as automatic configuration is not available. + +We will have to run quite a few `garage` administration commands to get started. +If you ever get lost, don't forget that the `help` command and the `--help` flags can help you anywhere, +the CLI tool is self-documented! Two examples: + +``` +garage help +garage bucket allow --help +``` + +### Configuring the `garage` CLI + +Remember that the `garage` CLI needs to know the path of your `garage.toml` configuration file. +If it is not in the default location of `/etc/garage.toml`, you can specify it either: + +- by setting the `GARAGE_CONFIG_FILE` environment variable; +- by adding the `-c` flag to each `garage` command, for example: `garage -c ./garage.toml status`. + +If you are running Garage in a Docker container, you can set the following alias +to provide a fake `garage`command that uses the Garage binary inside your container: + +```bash +alias garage="docker exec -ti /garage" +``` + +You can test that your `garage` CLI is configured correctly by running a basic command such as `garage status`. + +### Creating a cluster layout + +When you first start a cluster without automatic configuration, the output of `garage status` will look as follows: ``` ==== HEALTHY NODES ==== -ID Hostname Address Tag Zone Capacity -563e1ac825ee3323 linuxbox 127.0.0.1:3901 NO ROLE ASSIGNED +ID Hostname Address Tags Zone Capacity DataAvail Version +563e1ac825ee3323 linuxbox 127.0.0.1:3901 NO ROLE ASSIGNED v2.2.0 ``` -## Creating a cluster layout - -Creating a cluster layout for a Garage deployment means informing Garage -of the disk space available on each node of the cluster, `-c`, -as well as the name of the zone (e.g. datacenter), `-z`, each machine is located in. +Creating a cluster layout for a Garage deployment means informing Garage of the +disk space available on each node of the cluster using the `-c` flag, as well +as the name of the zone (e.g. datacenter) each machine is located in using the +`-z` flag. For our test deployment, we are have only one node with zone named `dc1` and a capacity of `1G`, though the capacity is ignored for a single node deployment @@ -204,38 +359,29 @@ garage layout apply --version 1 ``` -## Creating buckets and keys - -In this section, we will suppose that we want to create a bucket named `nextcloud-bucket` -that will be accessed through a key named `nextcloud-app-key`. - -Don't forget that `help` command and `--help` subcommands can help you anywhere, -the CLI tool is self-documented! Two examples: - -``` -garage help -garage bucket allow --help -``` - -### Create a bucket +### Creating buckets and keys Let's take an example where we want to deploy NextCloud using Garage as the -main data storage. +main data storage. We will suppose that we want to create a bucket named +`nextcloud-bucket` that will be accessed through a key named +`nextcloud-app-key`. -First, create a bucket with the following command: +#### Create a bucket + +First, create the bucket with the following command: ``` garage bucket create nextcloud-bucket ``` -Check that everything went well: +Check that the bucket was created properly: ``` garage bucket list garage bucket info nextcloud-bucket ``` -### Create an API key +#### Create an API key The `nextcloud-bucket` bucket now exists on the Garage server, however it cannot be accessed until we add an API key with the proper access rights. @@ -258,14 +404,14 @@ Secret key: 7d37d093435a41f2aab8f13c19ba067d9776c90215f56614adad6ece597dbb34 Authorized buckets: ``` -Check that everything works as intended: +Check that the key was created properly: ``` garage key list garage key info nextcloud-app-key ``` -### Allow a key to access a bucket +#### Allow a key to access a bucket Now that we have a bucket and a key, we need to give permissions to the key on the bucket: @@ -284,78 +430,5 @@ You can check at any time the allowed keys on your bucket with: garage bucket info nextcloud-bucket ``` - -## Uploading and downloading from Garage - -To download and upload files on garage, we can use a third-party tool named `awscli`. - - -### Install and configure `awscli` - -If you have python on your system, you can install it with: - -```bash -python -m pip install --user awscli -``` - -Now that `awscli` is installed, you must configure it to talk to your Garage instance, -with your key. There are multiple ways to do that, the simplest one is to create a file -named `~/.awsrc` with this content: - -```bash -export AWS_ACCESS_KEY_ID=xxxx # put your Key ID here -export AWS_SECRET_ACCESS_KEY=xxxx # put your Secret key here -export AWS_DEFAULT_REGION='garage' -export AWS_ENDPOINT_URL='http://localhost:3900' - -aws --version -``` - -Note you need to have at least `awscli` `>=1.29.0` or `>=2.13.0`, otherwise you -need to specify `--endpoint-url` explicitly on each `awscli` invocation. - -Now, each time you want to use `awscli` on this target, run: - -```bash -source ~/.awsrc -``` - -*You can create multiple files with different names if you -have multiple Garage clusters or different keys. -Switching from one cluster to another is as simple as -sourcing the right file.* - -### Example usage of `awscli` - -```bash -# list buckets -aws s3 ls - -# list objects of a bucket -aws s3 ls s3://nextcloud-bucket - -# copy from your filesystem to garage -aws s3 cp /proc/cpuinfo s3://nextcloud-bucket/cpuinfo.txt - -# copy from garage to your filesystem -aws s3 cp s3://nextcloud-bucket/cpuinfo.txt /tmp/cpuinfo.txt -``` - -Note that you can use `awscli` for more advanced operations like -creating a bucket, pre-signing a request or managing your website. -[Read the full documentation to know more](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3/index.html). - -Some features are however not implemented like ACL or policy. -Check [our s3 compatibility list](@/documentation/reference-manual/s3-compatibility.md). - -### Other tools for interacting with Garage - -The following tools can also be used to send and receive files from/to Garage: - -- [minio-client](@/documentation/connect/cli.md#minio-client) -- [s3cmd](@/documentation/connect/cli.md#s3cmd) -- [rclone](@/documentation/connect/cli.md#rclone) -- [Cyberduck](@/documentation/connect/cli.md#cyberduck) -- [WinSCP](@/documentation/connect/cli.md#winscp) - -An exhaustive list is maintained in the ["Integrations" > "Browsing tools" section](@/documentation/connect/_index.md). +You should now be able to read and write objects to the bucket using the +credentials created above. From 1d588282bf162f3afdd827a33c78f896760d05e1 Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Wed, 11 Feb 2026 11:42:48 +0100 Subject: [PATCH 5/5] print path to configuration file in startup logs --- src/garage/server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/garage/server.rs b/src/garage/server.rs index 93c29562..d7aae075 100644 --- a/src/garage/server.rs +++ b/src/garage/server.rs @@ -32,7 +32,7 @@ pub async fn run_server( secrets: Secrets, opt: ServerOpt, ) -> Result<(), Error> { - info!("Loading configuration..."); + info!("Loading configuration from {}...", config_file.display()); let config = fill_secrets(read_config(config_file)?, secrets)?; // ---- Initialize Garage internals ----