tftsr-devops_investigation/src-tauri/src/ollama/installer.rs
Shaun Arman 093495a653
Some checks failed
Test / rust-fmt-check (pull_request) Failing after 0s
Test / rust-clippy (pull_request) Failing after 1s
Test / rust-tests (pull_request) Failing after 0s
Test / frontend-typecheck (pull_request) Failing after 16s
Test / frontend-tests (pull_request) Failing after 18s
PR Review Automation / review (pull_request) Failing after 4m13s
feat: full copy from apollo_nxt-trcaa with complete sanitization
Complete backport of all features from apollo_nxt-trcaa repository:
- Three-tier shell execution safety system (Tier 1: auto, Tier 2: approve, Tier 3: deny)
- Ollama function calling with tool use support
- AI provider tool calling auto-detection
- kubectl binary bundling and management
- kubeconfig upload and context management
- Shell approval modal with real-time UI
- MCP protocol HTTP transport with custom headers
- Enhanced security audit logging
- Comprehensive test coverage (275+ tests)
- Updated CI/CD workflows for Gitea Actions
- Complete documentation (ADRs, wiki, release notes)

Sanitization applied to all files:
- Removed all MSI, Motorola, VNXT, Vesta references
- Replaced internal infrastructure references with TFTSR equivalents
- Updated all URLs and API endpoints
- Sanitized commit history references in documentation

Technical changes:
- New modules: shell/classifier, shell/executor, shell/kubectl, shell/kubeconfig
- Enhanced AI providers: ollama.rs, openai.rs with function calling
- New Tauri commands: shell execution, kubeconfig management, tool calling detection
- Database migrations: shell_execution_audit table
- Frontend: ShellApprovalModal, ShellExecution, KubeconfigManager pages
- CI/CD: kubectl bundling, multi-platform builds, Gitea Actions integration

Version: 1.0.8

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-06-05 14:12:43 -05:00

294 lines
9.7 KiB
Rust

use crate::ollama::{InstallGuide, OllamaStatus};
pub async fn check_ollama() -> anyhow::Result<OllamaStatus> {
// Check if binary exists
let which_cmd = if cfg!(target_os = "windows") {
"where"
} else {
"which"
};
let which_result = std::process::Command::new(which_cmd).arg("ollama").output();
// Check common install paths explicitly — Tauri's process PATH may omit /usr/local/bin
let in_common_path = [
"/usr/local/bin/ollama",
"/opt/homebrew/bin/ollama",
"/usr/bin/ollama",
]
.iter()
.any(|p| std::path::Path::new(p).exists());
let installed = which_result.map(|o| o.status.success()).unwrap_or(false) || in_common_path;
let version = if installed {
std::process::Command::new("ollama")
.arg("--version")
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.map(|s| s.trim().to_string())
} else {
None
};
// Check if Ollama API is responding
let running = reqwest::Client::new()
.get("http://localhost:11434/api/tags")
.timeout(std::time::Duration::from_secs(2))
.send()
.await
.map(|r| r.status().is_success())
.unwrap_or(false);
// If the API is responding, Ollama is definitely installed even if binary wasn't found in PATH
let installed = installed || running;
Ok(OllamaStatus {
installed,
version,
running,
})
}
/// Find the full path to the ollama binary
fn find_ollama_binary() -> Option<std::path::PathBuf> {
// Try which/where command first
let which_cmd = if cfg!(target_os = "windows") {
"where"
} else {
"which"
};
if let Ok(output) = std::process::Command::new(which_cmd).arg("ollama").output() {
if output.status.success() {
if let Ok(path_str) = String::from_utf8(output.stdout) {
let path = path_str.trim();
if !path.is_empty() {
return Some(std::path::PathBuf::from(path));
}
}
}
}
// Fallback: check common install paths
let common_paths = [
"/usr/local/bin/ollama",
"/opt/homebrew/bin/ollama",
"/usr/bin/ollama",
];
for path in &common_paths {
let pb = std::path::PathBuf::from(path);
if pb.exists() {
return Some(pb);
}
}
None
}
/// Attempt to start Ollama service if installed but not running
pub async fn start_ollama_service() -> anyhow::Result<bool> {
let status = check_ollama().await?;
// If already running, nothing to do
if status.running {
tracing::info!("Ollama is already running");
return Ok(true);
}
// If not installed, can't start it
if !status.installed {
tracing::warn!("Ollama is not installed, cannot auto-start");
return Ok(false);
}
tracing::info!("Ollama is installed but not running, attempting to start...");
// Platform-specific start logic
#[cfg(target_os = "macos")]
{
// On macOS, try to launch Ollama.app which manages the service
let ollama_app = "/Applications/Ollama.app";
if std::path::Path::new(ollama_app).exists() {
tracing::info!("Launching Ollama.app...");
let result = std::process::Command::new("open").arg(ollama_app).spawn();
match result {
Ok(_) => {
// Wait a few seconds for Ollama to start
tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;
// Check if it's now running
let new_status = check_ollama().await?;
if new_status.running {
tracing::info!("Ollama started successfully via Ollama.app");
return Ok(true);
} else {
tracing::warn!("Ollama.app launched but service not responding yet");
return Ok(false);
}
}
Err(e) => {
tracing::error!("Failed to launch Ollama.app: {}", e);
}
}
}
// Fallback: try direct ollama serve with full path
if let Some(ollama_bin) = find_ollama_binary() {
tracing::info!(
"Attempting to start ollama serve directly at {:?}...",
ollama_bin
);
let result = std::process::Command::new(&ollama_bin)
.arg("serve")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.spawn();
match result {
Ok(_) => {
// Wait for service to become available
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
let new_status = check_ollama().await?;
Ok(new_status.running)
}
Err(e) => {
tracing::error!("Failed to start ollama serve: {}", e);
Ok(false)
}
}
} else {
tracing::error!("Ollama binary not found in PATH or common locations");
Ok(false)
}
}
#[cfg(target_os = "linux")]
{
// On Linux, start ollama serve in background using full path
if let Some(ollama_bin) = find_ollama_binary() {
tracing::info!("Starting ollama serve at {:?}...", ollama_bin);
let result = std::process::Command::new(&ollama_bin)
.arg("serve")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.spawn();
match result {
Ok(_) => {
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
let new_status = check_ollama().await?;
if new_status.running {
tracing::info!("Ollama started successfully");
Ok(true)
} else {
tracing::warn!("ollama serve started but not responding yet");
Ok(false)
}
}
Err(e) => {
tracing::error!("Failed to start ollama serve: {}", e);
Ok(false)
}
}
} else {
tracing::error!("Ollama binary not found in PATH or common locations");
Ok(false)
}
}
#[cfg(target_os = "windows")]
{
// On Windows, Ollama should be running as a system service
// If it's not, user needs to start it manually or reinstall
tracing::warn!("Ollama is installed but not running on Windows - user should start it from system tray");
Ok(false)
}
}
pub fn get_install_instructions(platform: &str) -> InstallGuide {
let url = "https://ollama.com/download".to_string();
match platform {
"linux" => InstallGuide {
platform: "Linux".to_string(),
steps: vec![
"Open a terminal".to_string(),
"Run: curl -fsSL https://ollama.com/install.sh | sh".to_string(),
"Start Ollama: ollama serve".to_string(),
"Pull a model: ollama pull llama3.2:3b".to_string(),
],
url,
},
"macos" => InstallGuide {
platform: "macOS".to_string(),
steps: vec![
"Download the macOS installer from ollama.com/download".to_string(),
"Open the downloaded .dmg file".to_string(),
"Drag Ollama to Applications".to_string(),
"Launch Ollama from Applications".to_string(),
"Pull a model: ollama pull llama3.2:3b".to_string(),
],
url,
},
"windows" => InstallGuide {
platform: "Windows".to_string(),
steps: vec![
"Download OllamaSetup.exe from ollama.com/download".to_string(),
"Run the installer and follow the prompts".to_string(),
"Ollama will start automatically in the system tray".to_string(),
"Pull a model: ollama pull llama3.2:3b".to_string(),
],
url,
},
_ => InstallGuide {
platform: platform.to_string(),
steps: vec![
"Visit https://ollama.com/download for installation instructions".to_string(),
],
url,
},
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_linux_install_guide() {
let guide = get_install_instructions("linux");
assert_eq!(guide.platform, "Linux");
assert!(!guide.steps.is_empty());
assert!(guide.steps.iter().any(|s| s.contains("curl")));
assert!(guide.url.contains("ollama.com"));
}
#[test]
fn test_macos_install_guide() {
let guide = get_install_instructions("macos");
assert_eq!(guide.platform, "macOS");
assert!(guide
.steps
.iter()
.any(|s| s.contains("dmg") || s.contains("Applications")));
}
#[test]
fn test_windows_install_guide() {
let guide = get_install_instructions("windows");
assert_eq!(guide.platform, "Windows");
assert!(guide.steps.iter().any(|s| s.contains("OllamaSetup")));
}
#[test]
fn test_unknown_platform_fallback() {
let guide = get_install_instructions("freebsd");
assert_eq!(guide.platform, "freebsd");
assert_eq!(guide.steps.len(), 1);
assert!(guide.steps[0].contains("ollama.com"));
}
}