Don't load user's shell env on app startup (#4681)
Co-authored-by: Michael Neale <michael.neale@gmail.com>
This commit is contained in:
Generated
+1
@@ -2816,6 +2816,7 @@ dependencies = [
|
|||||||
"docx-rs",
|
"docx-rs",
|
||||||
"etcetera",
|
"etcetera",
|
||||||
"glob",
|
"glob",
|
||||||
|
"goose",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
"hyper 1.6.0",
|
"hyper 1.6.0",
|
||||||
"ignore",
|
"ignore",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ description.workspace = true
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
goose = { path = "../goose" }
|
||||||
mcp-core = { path = "../mcp-core" }
|
mcp-core = { path = "../mcp-core" }
|
||||||
mcp-server = { path = "../mcp-server" }
|
mcp-server = { path = "../mcp-server" }
|
||||||
rmcp = { version = "0.6.0", features = ["server", "client", "transport-io", "macros"] }
|
rmcp = { version = "0.6.0", features = ["server", "client", "transport-io", "macros"] }
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use std::{env, process::Stdio};
|
use goose::config::get_config_dir;
|
||||||
|
use std::{env, ffi::OsString, process::Stdio};
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
#[allow(unused_imports)] // False positive: trait is used for process_group method
|
#[allow(unused_imports)] // False positive: trait is used for process_group method
|
||||||
@@ -8,30 +9,23 @@ use std::os::unix::process::CommandExt;
|
|||||||
pub struct ShellConfig {
|
pub struct ShellConfig {
|
||||||
pub executable: String,
|
pub executable: String,
|
||||||
pub args: Vec<String>,
|
pub args: Vec<String>,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub envs: Vec<(OsString, OsString)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ShellConfig {
|
impl Default for ShellConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
if cfg!(windows) {
|
#[cfg(windows)]
|
||||||
// Detect the default shell on Windows
|
{
|
||||||
#[cfg(windows)]
|
Self::detect_windows_shell()
|
||||||
{
|
}
|
||||||
Self::detect_windows_shell()
|
#[cfg(not(windows))]
|
||||||
}
|
{
|
||||||
#[cfg(not(windows))]
|
let bash_env = get_config_dir().join(".bash_env").into_os_string();
|
||||||
{
|
|
||||||
// This branch should never be taken on non-Windows
|
|
||||||
// but we need it for compilation
|
|
||||||
Self {
|
|
||||||
executable: "cmd".to_string(),
|
|
||||||
args: vec!["/c".to_string()],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Use bash on Unix/macOS (keep existing behavior)
|
|
||||||
Self {
|
Self {
|
||||||
executable: "bash".to_string(),
|
executable: "bash".to_string(),
|
||||||
args: vec!["-c".to_string()],
|
args: vec!["-c".to_string()],
|
||||||
|
envs: vec![(OsString::from("BASH_ENV"), bash_env)],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,6 +44,7 @@ impl ShellConfig {
|
|||||||
"-NonInteractive".to_string(),
|
"-NonInteractive".to_string(),
|
||||||
"-Command".to_string(),
|
"-Command".to_string(),
|
||||||
],
|
],
|
||||||
|
envs: vec![],
|
||||||
}
|
}
|
||||||
} else if let Ok(ps_path) = which::which("powershell") {
|
} else if let Ok(ps_path) = which::which("powershell") {
|
||||||
// Windows PowerShell 5.1
|
// Windows PowerShell 5.1
|
||||||
@@ -60,12 +55,14 @@ impl ShellConfig {
|
|||||||
"-NonInteractive".to_string(),
|
"-NonInteractive".to_string(),
|
||||||
"-Command".to_string(),
|
"-Command".to_string(),
|
||||||
],
|
],
|
||||||
|
envs: vec![],
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fall back to cmd.exe
|
// Fall back to cmd.exe
|
||||||
Self {
|
Self {
|
||||||
executable: "cmd".to_string(),
|
executable: "cmd".to_string(),
|
||||||
args: vec!["/c".to_string()],
|
args: vec!["/c".to_string()],
|
||||||
|
envs: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,14 +116,18 @@ enum SecretStorage {
|
|||||||
// Global instance
|
// Global instance
|
||||||
static GLOBAL_CONFIG: OnceCell<Config> = OnceCell::new();
|
static GLOBAL_CONFIG: OnceCell<Config> = OnceCell::new();
|
||||||
|
|
||||||
|
pub fn get_config_dir() -> PathBuf {
|
||||||
|
choose_app_strategy(APP_STRATEGY.clone())
|
||||||
|
.expect("goose requires a home dir")
|
||||||
|
.config_dir()
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
// choose_app_strategy().config_dir()
|
// choose_app_strategy().config_dir()
|
||||||
// - macOS/Linux: ~/.config/goose/
|
// - macOS/Linux: ~/.config/goose/
|
||||||
// - Windows: ~\AppData\Roaming\Block\goose\config\
|
// - Windows: ~\AppData\Roaming\Block\goose\config\
|
||||||
let config_dir = choose_app_strategy(APP_STRATEGY.clone())
|
let config_dir = get_config_dir();
|
||||||
.expect("goose requires a home dir")
|
|
||||||
.config_dir();
|
|
||||||
|
|
||||||
std::fs::create_dir_all(&config_dir).expect("Failed to create config directory");
|
std::fs::create_dir_all(&config_dir).expect("Failed to create config directory");
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ pub mod signup_openrouter;
|
|||||||
pub mod signup_tetrate;
|
pub mod signup_tetrate;
|
||||||
|
|
||||||
pub use crate::agents::ExtensionConfig;
|
pub use crate::agents::ExtensionConfig;
|
||||||
pub use base::{Config, ConfigError, APP_STRATEGY};
|
pub use base::{get_config_dir, Config, ConfigError, APP_STRATEGY};
|
||||||
pub use custom_providers::CustomProviderConfig;
|
pub use custom_providers::CustomProviderConfig;
|
||||||
pub use experiments::ExperimentManager;
|
pub use experiments::ExperimentManager;
|
||||||
pub use extensions::{ExtensionConfigManager, ExtensionEntry};
|
pub use extensions::{ExtensionConfigManager, ExtensionEntry};
|
||||||
|
|||||||
+23
-33
@@ -25,7 +25,6 @@ import { spawn } from 'child_process';
|
|||||||
import 'dotenv/config';
|
import 'dotenv/config';
|
||||||
import { startGoosed } from './goosed';
|
import { startGoosed } from './goosed';
|
||||||
import { expandTilde, getBinaryPath } from './utils/pathUtils';
|
import { expandTilde, getBinaryPath } from './utils/pathUtils';
|
||||||
import { loadShellEnv } from './utils/loadEnv';
|
|
||||||
import log from './utils/logger';
|
import log from './utils/logger';
|
||||||
import { ensureWinShims } from './utils/winShims';
|
import { ensureWinShims } from './utils/winShims';
|
||||||
import { addRecentDir, loadRecentDirs } from './utils/recentDirs';
|
import { addRecentDir, loadRecentDirs } from './utils/recentDirs';
|
||||||
@@ -448,46 +447,37 @@ const parseArgs = () => {
|
|||||||
return { dirPath };
|
return { dirPath };
|
||||||
};
|
};
|
||||||
|
|
||||||
const getGooseProvider = () => {
|
interface BundledConfig {
|
||||||
loadShellEnv(app.isPackaged);
|
defaultProvider?: string;
|
||||||
|
defaultModel?: string;
|
||||||
|
predefinedModels?: string;
|
||||||
|
baseUrlShare?: string;
|
||||||
|
version?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getBundledConfig = (): BundledConfig => {
|
||||||
//{env-macro-start}//
|
//{env-macro-start}//
|
||||||
//needed when goose is bundled for a specific provider
|
//needed when goose is bundled for a specific provider
|
||||||
//{env-macro-end}//
|
//{env-macro-end}//
|
||||||
return [
|
return {
|
||||||
process.env.GOOSE_DEFAULT_PROVIDER,
|
defaultProvider: process.env.GOOSE_DEFAULT_PROVIDER,
|
||||||
process.env.GOOSE_DEFAULT_MODEL,
|
defaultModel: process.env.GOOSE_DEFAULT_MODEL,
|
||||||
process.env.GOOSE_PREDEFINED_MODELS,
|
predefinedModels: process.env.GOOSE_PREDEFINED_MODELS,
|
||||||
];
|
baseUrlShare: process.env.GOOSE_BASE_URL_SHARE,
|
||||||
|
version: process.env.GOOSE_VERSION,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSharingUrl = () => {
|
const { defaultProvider, defaultModel, predefinedModels, baseUrlShare, version } =
|
||||||
// checks app env for sharing url
|
getBundledConfig();
|
||||||
loadShellEnv(app.isPackaged); // will try to take it from the zshrc file
|
|
||||||
// if GOOSE_BASE_URL_SHARE is found, we will set process.env.GOOSE_BASE_URL_SHARE, otherwise we return what it is set
|
|
||||||
// to in the env at bundle time
|
|
||||||
return process.env.GOOSE_BASE_URL_SHARE;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getVersion = () => {
|
|
||||||
// checks app env for sharing url
|
|
||||||
loadShellEnv(app.isPackaged); // will try to take it from the zshrc file
|
|
||||||
// to in the env at bundle time
|
|
||||||
return process.env.GOOSE_VERSION;
|
|
||||||
};
|
|
||||||
|
|
||||||
const [provider, model, predefinedModels] = getGooseProvider();
|
|
||||||
|
|
||||||
const sharingUrl = getSharingUrl();
|
|
||||||
|
|
||||||
const gooseVersion = getVersion();
|
|
||||||
|
|
||||||
const SERVER_SECRET = process.env.GOOSE_EXTERNAL_BACKEND
|
const SERVER_SECRET = process.env.GOOSE_EXTERNAL_BACKEND
|
||||||
? 'test'
|
? 'test'
|
||||||
: crypto.randomBytes(32).toString('hex');
|
: crypto.randomBytes(32).toString('hex');
|
||||||
|
|
||||||
let appConfig = {
|
let appConfig = {
|
||||||
GOOSE_DEFAULT_PROVIDER: provider,
|
GOOSE_DEFAULT_PROVIDER: defaultProvider,
|
||||||
GOOSE_DEFAULT_MODEL: model,
|
GOOSE_DEFAULT_MODEL: defaultModel,
|
||||||
GOOSE_PREDEFINED_MODELS: predefinedModels,
|
GOOSE_PREDEFINED_MODELS: predefinedModels,
|
||||||
GOOSE_API_HOST: 'http://127.0.0.1',
|
GOOSE_API_HOST: 'http://127.0.0.1',
|
||||||
GOOSE_PORT: 0,
|
GOOSE_PORT: 0,
|
||||||
@@ -603,8 +593,8 @@ const createChat = async (
|
|||||||
GOOSE_PORT: port,
|
GOOSE_PORT: port,
|
||||||
GOOSE_WORKING_DIR: working_dir,
|
GOOSE_WORKING_DIR: working_dir,
|
||||||
REQUEST_DIR: dir,
|
REQUEST_DIR: dir,
|
||||||
GOOSE_BASE_URL_SHARE: sharingUrl,
|
GOOSE_BASE_URL_SHARE: baseUrlShare,
|
||||||
GOOSE_VERSION: gooseVersion,
|
GOOSE_VERSION: version,
|
||||||
recipe: recipe,
|
recipe: recipe,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
@@ -1927,7 +1917,7 @@ async function appMain() {
|
|||||||
if (aboutGooseMenuItem.submenu) {
|
if (aboutGooseMenuItem.submenu) {
|
||||||
aboutGooseMenuItem.submenu.append(
|
aboutGooseMenuItem.submenu.append(
|
||||||
new MenuItem({
|
new MenuItem({
|
||||||
label: `Version ${gooseVersion || app.getVersion()}`,
|
label: `Version ${version || app.getVersion()}`,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
import { execSync } from 'child_process';
|
|
||||||
import log from './logger';
|
|
||||||
|
|
||||||
export function loadShellEnv(isProduction: boolean = false): void {
|
|
||||||
// Only proceed if running on macOS and in production mode
|
|
||||||
if (process.platform !== 'darwin' || !isProduction) {
|
|
||||||
log.info(
|
|
||||||
`Skipping zsh environment loading: ${
|
|
||||||
process.platform !== 'darwin' ? 'Not running on macOS' : 'Not in production mode'
|
|
||||||
}`
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
log.info('LOADING ENV');
|
|
||||||
|
|
||||||
const shell = process.env.SHELL || '/bin/bash'; // Detect user's shell
|
|
||||||
|
|
||||||
const envStr = execSync(`${shell} -l -i -c 'env'`, {
|
|
||||||
encoding: 'utf-8',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Parse and set environment variables
|
|
||||||
envStr.split('\n').forEach((line) => {
|
|
||||||
const matches = line.match(/^([^=]+)=(.*)$/);
|
|
||||||
if (matches) {
|
|
||||||
const [, key, value] = matches;
|
|
||||||
log.info(`Setting ${key}`);
|
|
||||||
process.env[key] = value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
log.info('Successfully loaded zsh environment variables');
|
|
||||||
} catch (error) {
|
|
||||||
log.error('Failed to load zsh environment variables:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user