Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion helix-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@ path = "src/main.rs"
[features]
normal = ["helix-db/server"]
ingestion = ["helix-db/full"]
default = ["normal"]
default = ["normal"]
1 change: 1 addition & 0 deletions helix-cli/src/commands/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ pub async fn run(deployment_type: CloudDeploymentTypeCommand) -> Result<()> {
let local_config = LocalInstanceConfig {
port: None, // Let the system assign a port
build_mode: BuildMode::Debug,
env_file: None,
db_config: DbConfig::default(),
};

Expand Down
2 changes: 1 addition & 1 deletion helix-cli/src/commands/integrations/fly.rs
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ impl<'a> FlyManager<'a> {
docker.push(image_name, FLY_REGISTRY_URL)?;

// Get environment variables first to ensure they live long enough
let env_vars = docker.environment_variables(instance_name);
let env_vars = docker.environment_variables(instance_name)?;

let mut deploy_args = vec![
"deploy",
Expand Down
1 change: 1 addition & 0 deletions helix-cli/src/commands/migrate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ fn create_v2_config(ctx: &MigrationContext) -> Result<()> {
let local_config = LocalInstanceConfig {
port: Some(ctx.port),
build_mode: BuildMode::Debug,
env_file: None,
db_config,
};

Expand Down
3 changes: 3 additions & 0 deletions helix-cli/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ pub struct LocalInstanceConfig {
pub port: Option<u16>,
#[serde(default = "default_dev_build_mode")]
pub build_mode: BuildMode,
#[serde(default)]
pub env_file: Option<PathBuf>,
#[serde(flatten)]
pub db_config: DbConfig,
}
Expand Down Expand Up @@ -378,6 +380,7 @@ impl HelixConfig {
LocalInstanceConfig {
port: Some(6969),
build_mode: BuildMode::Debug,
env_file: None,
db_config: DbConfig::default(),
},
);
Expand Down
57 changes: 34 additions & 23 deletions helix-cli/src/docker.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::config::{BuildMode, InstanceInfo};
use crate::env_utils::load_env_variables;
use crate::project::ProjectContext;
use crate::utils::print_status;
use eyre::{Result, eyre};
Expand Down Expand Up @@ -44,25 +45,32 @@ impl<'a> DockerManager<'a> {
}

/// Get environment variables for an instance
pub(crate) fn environment_variables(&self, instance_name: &str) -> Vec<String> {
vec![
{
let port = self
.project
.config
.get_instance(instance_name)
.unwrap()
.port()
.unwrap_or(6969);
format!("HELIX_PORT={port}")
},
pub(crate) fn environment_variables(&self, instance_name: &str) -> Result<Vec<String>> {
let instance = self.project.config.get_instance(instance_name)?;

// Start with Helix built-in variables (highest priority)
let mut env_vars = vec![
format!("HELIX_PORT={}", instance.port().unwrap_or(6969)),
format!("HELIX_DATA_DIR={HELIX_DATA_DIR}"),
format!("HELIX_INSTANCE={instance_name}"),
{
let project_name = &self.project.config.project.name;
format!("HELIX_PROJECT={project_name}")
},
]
format!("HELIX_PROJECT={}", self.project.config.project.name),
];

// Load user-defined environment variables if this is a local instance
if let InstanceInfo::Local(local_config) = instance
&& let Some(env_file) = &local_config.env_file
{
let user_vars = load_env_variables(Some(env_file.as_path()), &self.project.root)?;

// Add user variables that don't conflict with Helix built-ins
for (key, value) in user_vars {
if !key.starts_with("HELIX_") {
env_vars.push(format!("{key}={value}"));
}
}
}

Ok(env_vars)
}

/// Get the container name for an instance
Expand Down Expand Up @@ -220,6 +228,14 @@ CMD ["helix-container"]
let container_name = self.container_name(instance_name);
let network_name = self.network_name(instance_name);

// Get environment variables including user-defined ones
let env_vars = self.environment_variables(instance_name)?;
let env_section = env_vars
.iter()
.map(|var| format!(" - {var}"))
.collect::<Vec<_>>()
.join("\n");

let compose = format!(
r#"# Generated docker-compose.yml for Helix instance: {instance_name}
services:
Expand All @@ -235,10 +251,7 @@ services:
volumes:
- ../.volumes/{instance_name}:/data
environment:
- HELIX_PORT={port}
- HELIX_DATA_DIR={data_dir}
- HELIX_INSTANCE={instance_name}
- HELIX_PROJECT={project_name}
{env_section}
restart: unless-stopped
networks:
- {network_name}
Expand All @@ -250,8 +263,6 @@ networks:
platform = instance_config
.docker_build_target()
.map_or("".to_string(), |p| format!("platforms:\n - {p}")),
project_name = self.project.config.project.name,
data_dir = HELIX_DATA_DIR,
);

Ok(compose)
Expand Down
70 changes: 70 additions & 0 deletions helix-cli/src/env_utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use eyre::Result;
use std::collections::HashMap;
use std::path::Path;

/// Load environment variables from .env file and shell environment
///
/// Precedence order (highest to lowest):
/// 1. Shell environment variables
/// 2. .env file variables
pub fn load_env_variables(
env_file_path: Option<&Path>,
project_root: &Path,
) -> Result<HashMap<String, String>> {
let mut vars = HashMap::new();

// Load from .env file if specified
if let Some(env_path) = env_file_path {
let full_path = project_root.join(env_path);
if full_path.exists() {
// Parse the .env file with improved handling
let env_content = std::fs::read_to_string(&full_path)?;
for (line_num, line) in env_content.lines().enumerate() {
let trimmed = line.trim();
// Skip empty lines and comments
if trimmed.is_empty() || trimmed.starts_with('#') {
continue;
}

// Parse KEY=VALUE pairs (find first = to handle values with = in them)
if let Some(eq_pos) = trimmed.find('=') {
let key = trimmed[..eq_pos].trim();
let value = trimmed[eq_pos + 1..].trim();

// Validate key
if key.is_empty() {
eprintln!("Warning: Skipping empty key at line {} in {}",
line_num + 1, full_path.display());
continue;
}

// Remove surrounding quotes if present (both single and double)
let value = if (value.starts_with('"') && value.ends_with('"'))
|| (value.starts_with('\'') && value.ends_with('\'')) {
&value[1..value.len() - 1]
} else {
value
};
Comment on lines +42 to +47
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: quote removal logic could fail on single-character quoted strings due to potential underflow in value.len() - 1

Suggested change
let value = if (value.starts_with('"') && value.ends_with('"'))
|| (value.starts_with('\'') && value.ends_with('\'')) {
&value[1..value.len() - 1]
} else {
value
};
let value = if value.len() >= 2 && ((value.starts_with('"') && value.ends_with('"'))
|| (value.starts_with('\'') && value.ends_with('\''))) {
&value[1..value.len() - 1]
} else {
value
};
Prompt To Fix With AI
This is a comment left during a code review.
Path: helix-cli/src/env_utils.rs
Line: 42:47

Comment:
**logic:** quote removal logic could fail on single-character quoted strings due to potential underflow in `value.len() - 1`

```suggestion
                    let value = if value.len() >= 2 && ((value.starts_with('"') && value.ends_with('"'))
                        || (value.starts_with('\'') && value.ends_with('\''))) {
                        &value[1..value.len() - 1]
                    } else {
                        value
                    };
```

How can I resolve this? If you propose a fix, please make it concise.


vars.insert(key.to_string(), value.to_string());
} else if !trimmed.starts_with("export ") {
// Skip lines that start with "export " (shell syntax)
eprintln!("Warning: Skipping malformed line {} in {}: {}",
line_num + 1, full_path.display(), trimmed);
}
}
} else {
eprintln!("Warning: env_file '{}' not found, skipping", full_path.display());
}
}

// Add shell environment variables (higher priority - overwrites .env)
for (key, value) in std::env::vars() {
// Skip HELIX_ prefixed variables as they're managed internally
if !key.starts_with("HELIX_") {
vars.insert(key, value);
}
}

Ok(vars)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: missing newline at end of file

Prompt To Fix With AI
This is a comment left during a code review.
Path: helix-cli/src/env_utils.rs
Line: 70:70

Comment:
**style:** missing newline at end of file

How can I resolve this? If you propose a fix, please make it concise.

1 change: 1 addition & 0 deletions helix-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use eyre::Result;
mod commands;
mod config;
mod docker;
mod env_utils;
mod errors;
mod metrics_sender;
mod project;
Expand Down
Loading