diff --git a/AGENTS.md b/AGENTS.md index 3cdfcd83b..45ba1d6bb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -71,8 +71,10 @@ Co-locate unit tests with their modules and give behavior-led names. Integration ## Environment Variables -- `RUSTFS_ENABLE_SCANNER` - Enable/disable background data scanner (default: true) -- `RUSTFS_ENABLE_HEAL` - Enable/disable auto-heal functionality (default: true) +- Global configuration environment variables must use flat `RUSTFS_*` names (no module segments), such as `RUSTFS_REGION`, `RUSTFS_ADDRESS`, `RUSTFS_VOLUMES`, and `RUSTFS_LICENSE`. +- `RUSTFS_SCANNER_ENABLED` - Enable/disable background data scanner (default: true) +- `RUSTFS_HEAL_ENABLED` - Enable/disable auto-heal functionality (default: true) +- Deprecated aliases (for pre-beta compatibility) are documented in [the config module README](crates/config/README.md#environment-variable-naming-conventions) and must log warnings when used. - For KMS tests: `NO_PROXY=127.0.0.1,localhost` and clear proxy environment variables ## Commit & Pull Request Guidelines diff --git a/crates/config/README.md b/crates/config/README.md index 232d5e3ca..ea694b3a6 100644 --- a/crates/config/README.md +++ b/crates/config/README.md @@ -32,6 +32,24 @@ For comprehensive documentation, examples, and usage guides, please visit the main [RustFS repository](https://github.com/rustfs/rustfs). +## Environment Variable Naming Conventions + +RustFS uses a flat naming style for top-level configuration: environment variables are `RUSTFS_*` without nested module segments. + +Examples: +- `RUSTFS_REGION` +- `RUSTFS_ADDRESS` +- `RUSTFS_VOLUMES` +- `RUSTFS_LICENSE` + +Current guidance: +- Prefer module-specific names only when they are not top-level product configuration. +- Renamed variables must keep backward-compatible aliases until before beta. +- Alias usage must emit deprecation warnings and be treated as transitional only. +- Deprecated example: + - `RUSTFS_ENABLE_SCANNER` -> `RUSTFS_SCANNER_ENABLED` + - `RUSTFS_ENABLE_HEAL` -> `RUSTFS_HEAL_ENABLED` + ## 📄 License This project is licensed under the Apache License 2.0 - see the [LICENSE](../../LICENSE) file for details. diff --git a/crates/config/src/constants/app.rs b/crates/config/src/constants/app.rs index eb3b0735a..844dd1609 100644 --- a/crates/config/src/constants/app.rs +++ b/crates/config/src/constants/app.rs @@ -128,6 +128,9 @@ pub const RUSTFS_LICENSE_URL: &str = "https://www.apache.org/licenses/LICENSE-2. /// Example: RUSTFS_ADDRESS=":9000" pub const ENV_RUSTFS_ADDRESS: &str = "RUSTFS_ADDRESS"; +/// Environment variable for server volumes. +pub const ENV_RUSTFS_VOLUMES: &str = "RUSTFS_VOLUMES"; + /// Default port for rustfs /// This is the default port for rustfs. /// This is used to bind the server to a specific port. @@ -153,6 +156,12 @@ pub const DEFAULT_CONSOLE_ADDRESS: &str = concat!(":", DEFAULT_CONSOLE_PORT); /// Default value: us-east-1 pub const RUSTFS_REGION: &str = "us-east-1"; +/// Environment variable for server region. +pub const ENV_RUSTFS_REGION: &str = "RUSTFS_REGION"; + +/// Environment variable for server license. +pub const ENV_RUSTFS_LICENSE: &str = "RUSTFS_LICENSE"; + /// Default log filename for rustfs /// This is the default log filename for rustfs. /// It is used to store the logs of the application. diff --git a/crates/lock/src/fast_lock/disabled_manager.rs b/crates/lock/src/fast_lock/disabled_manager.rs index 35ca24193..5c1aaf7a0 100644 --- a/crates/lock/src/fast_lock/disabled_manager.rs +++ b/crates/lock/src/fast_lock/disabled_manager.rs @@ -13,7 +13,8 @@ // limitations under the License. //! Disabled lock manager that bypasses all locking operations -//! Used when RUSTFS_ENABLE_LOCKS environment variable is set to false +//! Used when the lock feature is disabled via RUSTFS_LOCK_ENABLED +//! (or deprecated RUSTFS_ENABLE_LOCKS). use std::sync::Arc; diff --git a/crates/lock/src/lib.rs b/crates/lock/src/lib.rs index 65005bb16..6562f8675 100644 --- a/crates/lock/src/lib.rs +++ b/crates/lock/src/lib.rs @@ -69,8 +69,12 @@ pub const BUILD_TIMESTAMP: &str = "unknown"; /// Maximum number of items in delete list pub const MAX_DELETE_LIST: usize = 1000; -/// Default setting for RUSTFS_ENABLE_LOCKS environment variable +/// Default setting for lock enablement. +/// Canonical variable: RUSTFS_LOCK_ENABLED +/// Deprecated compatibility alias: RUSTFS_ENABLE_LOCKS const DEFAULT_RUSTFS_LOCKS_ENABLED: bool = true; +const ENV_LOCK_ENABLED: &str = "RUSTFS_LOCK_ENABLED"; +const ENV_LOCK_ENABLED_DEPRECATED: &str = "RUSTFS_ENABLE_LOCKS"; // ============================================================================ // Global FastLock Manager @@ -97,10 +101,26 @@ impl Default for GlobalLockManager { impl GlobalLockManager { /// Create a lock manager based on environment variable configuration pub fn new() -> Self { - // Check RUSTFS_ENABLE_LOCKS environment variable - let locks_enabled = rustfs_utils::get_env_bool("RUSTFS_ENABLE_LOCKS", DEFAULT_RUSTFS_LOCKS_ENABLED); + // Check lock enablement env vars with deprecated compatibility support. + let locks_enabled = rustfs_utils::get_env_bool_with_aliases( + ENV_LOCK_ENABLED, + &[ENV_LOCK_ENABLED_DEPRECATED], + DEFAULT_RUSTFS_LOCKS_ENABLED, + ); if !locks_enabled { - tracing::info!("Lock system disabled via RUSTFS_ENABLE_LOCKS environment variable"); + let disabled_by = if std::env::var(ENV_LOCK_ENABLED).is_ok() { + ENV_LOCK_ENABLED + } else if std::env::var(ENV_LOCK_ENABLED_DEPRECATED).is_ok() { + ENV_LOCK_ENABLED_DEPRECATED + } else { + "default setting" + }; + + if disabled_by == "default setting" { + tracing::info!("Lock system disabled via default setting"); + } else { + tracing::info!("Lock system disabled via {} environment variable", disabled_by); + } return Self::Disabled(DisabledLockManager::new()); } tracing::info!("Lock system enabled"); @@ -248,7 +268,7 @@ static GLOBAL_LOCK_MANAGER: OnceLock> = OnceLock::new(); /// Get the global shared lock manager instance /// /// Returns either FastObjectLockManager or DisabledLockManager based on -/// the RUSTFS_ENABLE_LOCKS environment variable. +/// the RUSTFS_LOCK_ENABLED environment variable. pub fn get_global_lock_manager() -> Arc { GLOBAL_LOCK_MANAGER.get_or_init(|| Arc::new(GlobalLockManager::new())).clone() } diff --git a/crates/utils/src/envs.rs b/crates/utils/src/envs.rs index 5252119c9..1c19cd881 100644 --- a/crates/utils/src/envs.rs +++ b/crates/utils/src/envs.rs @@ -12,7 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::env; +use std::{ + collections::HashSet, + env, + sync::{Mutex, OnceLock}, +}; +use tracing::warn; /// Retrieve an environment variable as a specific type, with a default value if not set or parsing fails. /// 8-bit type: signed i8 @@ -68,6 +73,56 @@ pub fn get_env_opt_u8(key: &str) -> Option { env::var(key).ok().and_then(|v| v.parse().ok()) } +static WARNED_ENV_MESSAGES: OnceLock>> = OnceLock::new(); + +fn log_once(key: &str, message: impl FnOnce() -> String) { + let seen = WARNED_ENV_MESSAGES.get_or_init(|| Mutex::new(HashSet::new())); + let mut seen = match seen.lock() { + Ok(seen) => seen, + Err(poisoned) => poisoned.into_inner(), + }; + if seen.insert(key.to_string()) { + warn!("{}", message()); + } +} + +fn resolve_env_with_aliases(key: &str, deprecated: &[&str]) -> Option<(String, String)> { + if let Ok(value) = env::var(key) { + return Some((key.to_string(), value)); + } + + let (alias, value) = deprecated + .iter() + .find_map(|alias| env::var(alias).ok().map(|value| (*alias, value)))?; + let deprecated_key = format!("env_alias:{alias}->{key}"); + log_once(&deprecated_key, || { + format!("Environment variable {alias} is deprecated, use {key} instead") + }); + Some((alias.to_string(), value)) +} + +pub fn get_env_str_with_aliases(key: &str, deprecated: &[&str], default: &str) -> String { + resolve_env_with_aliases(key, deprecated).map_or_else(|| default.to_string(), |(_, value)| value) +} + +pub fn get_env_bool_with_aliases(key: &str, deprecated: &[&str], default: bool) -> bool { + let Some((used_key, value)) = resolve_env_with_aliases(key, deprecated) else { + return default; + }; + + parse_bool_str(&value).unwrap_or_else(|| { + log_once( + &format!("env_invalid_bool:{used_key}"), + || { + format!( + "Invalid bool value for {used_key}: {value}. Supported values are true/false,1/0,yes/no,on/off. Treating as unset." + ) + }, + ); + default + }) +} + /// Retrieve an environment variable as a specific type, with a default value if not set or parsing fails. /// 16-bit type: signed i16 /// @@ -338,7 +393,7 @@ pub fn get_env_opt_usize(key: &str) -> Option { /// - `String`: The environment variable value if set, otherwise the default value. /// pub fn get_env_str(key: &str, default: &str) -> String { - env::var(key).unwrap_or_else(|_| default.to_string()) + get_env_str_with_aliases(key, &[], default) } /// Retrieve an environment variable as a String, returning None if not set. @@ -363,7 +418,7 @@ pub fn get_env_opt_str(key: &str) -> Option { /// - `bool`: The parsed boolean value if successful, otherwise the default value. /// pub fn get_env_bool(key: &str, default: bool) -> bool { - env::var(key).ok().and_then(|v| parse_bool_str(&v)).unwrap_or(default) + get_env_bool_with_aliases(key, &[], default) } /// Parse a string into a boolean value. @@ -391,5 +446,11 @@ fn parse_bool_str(s: &str) -> Option { /// - `Option`: The parsed boolean value if successful, otherwise None. /// pub fn get_env_opt_bool(key: &str) -> Option { - env::var(key).ok().and_then(|v| parse_bool_str(&v)) + let (used_key, value) = resolve_env_with_aliases(key, &[])?; + parse_bool_str(&value).or_else(|| { + log_once(&format!("env_invalid_bool_optional:{used_key}"), || { + format!("Invalid bool value for {used_key}: {value}. Supported values are true/false,1/0,yes/no,on/off.") + }); + None + }) } diff --git a/rustfs/src/main.rs b/rustfs/src/main.rs index c573823cf..30c5d2213 100644 --- a/rustfs/src/main.rs +++ b/rustfs/src/main.rs @@ -66,12 +66,17 @@ use rustfs_iam::{init_iam_sys, init_oidc_sys}; use rustfs_metrics::init_metrics_system; use rustfs_obs::{init_obs, set_global_guard}; use rustfs_scanner::init_data_scanner; -use rustfs_utils::net::parse_and_resolve_address; +use rustfs_utils::{get_env_bool_with_aliases, net::parse_and_resolve_address}; use std::io::{Error, Result}; use std::sync::Arc; use tokio_util::sync::CancellationToken; use tracing::{debug, error, info, instrument, warn}; +const ENV_SCANNER_ENABLED: &str = "RUSTFS_SCANNER_ENABLED"; +const ENV_SCANNER_ENABLED_DEPRECATED: &str = "RUSTFS_ENABLE_SCANNER"; +const ENV_HEAL_ENABLED: &str = "RUSTFS_HEAL_ENABLED"; +const ENV_HEAL_ENABLED_DEPRECATED: &str = "RUSTFS_ENABLE_HEAL"; + #[cfg(all(target_os = "linux", target_env = "gnu", target_arch = "x86_64"))] #[global_allocator] static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; @@ -404,8 +409,8 @@ async fn run(config: config::Config) -> Result<()> { let _ = create_ahm_services_cancel_token(); // Check environment variables to determine if scanner and heal should be enabled - let enable_scanner = rustfs_utils::get_env_bool("RUSTFS_ENABLE_SCANNER", true); - let enable_heal = rustfs_utils::get_env_bool("RUSTFS_ENABLE_HEAL", true); + let enable_scanner = get_env_bool_with_aliases(ENV_SCANNER_ENABLED, &[ENV_SCANNER_ENABLED_DEPRECATED], true); + let enable_heal = get_env_bool_with_aliases(ENV_HEAL_ENABLED, &[ENV_HEAL_ENABLED_DEPRECATED], true); info!( target: "rustfs::main::run", @@ -501,8 +506,8 @@ async fn handle_shutdown( state_manager.update(ServiceState::Stopping); // Check environment variables to determine what services need to be stopped - let enable_scanner = rustfs_utils::get_env_bool("RUSTFS_ENABLE_SCANNER", true); - let enable_heal = rustfs_utils::get_env_bool("RUSTFS_ENABLE_HEAL", true); + let enable_scanner = get_env_bool_with_aliases(ENV_SCANNER_ENABLED, &[ENV_SCANNER_ENABLED_DEPRECATED], true); + let enable_heal = get_env_bool_with_aliases(ENV_HEAL_ENABLED, &[ENV_HEAL_ENABLED_DEPRECATED], true); // Stop background services based on what was enabled if enable_scanner || enable_heal { diff --git a/scripts/run.ps1 b/scripts/run.ps1 index d686a4502..7d6275a4a 100644 --- a/scripts/run.ps1 +++ b/scripts/run.ps1 @@ -156,8 +156,8 @@ $env:RUSTFS_NS_SCANNER_INTERVAL = "60" # $env:RUSTFS_REGION = "us-east-1" -$env:RUSTFS_ENABLE_SCANNER = "false" -$env:RUSTFS_ENABLE_HEAL = "false" +$env:RUSTFS_SCANNER_ENABLED = "false" +$env:RUSTFS_HEAL_ENABLED = "false" # Object cache configuration $env:RUSTFS_OBJECT_CACHE_ENABLE = "true" @@ -198,4 +198,4 @@ if (-not $env:MALLOC_CONF) { # To run in release mode: # cargo run --profile release --bin rustfs # To run in debug mode: -cargo run --bin rustfs \ No newline at end of file +cargo run --bin rustfs diff --git a/scripts/run.sh b/scripts/run.sh index 01179f450..735425745 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -178,9 +178,9 @@ export RUSTFS_NS_SCANNER_INTERVAL=60 # Object scanning interval in seconds #export RUSTFS_REGION="us-east-1" -export RUSTFS_ENABLE_SCANNER=true +export RUSTFS_SCANNER_ENABLED=true -export RUSTFS_ENABLE_HEAL=true +export RUSTFS_HEAL_ENABLED=true # Object cache configuration export RUSTFS_OBJECT_CACHE_ENABLE=true @@ -252,4 +252,4 @@ fi # To run in release mode, use the following line #cargo run --profile release --bin rustfs # To run in debug mode, use the following line -cargo run --bin rustfs \ No newline at end of file +cargo run --bin rustfs