feat(core): add ollama lifecycle management for cron sync

Add src/core/ollama_mgmt.rs module that handles Ollama detection, startup,
and health checking. This enables cron-based sync to automatically start
Ollama when it's installed but not running, ensuring embeddings are always
available during unattended sync runs.

Integration points:
- sync handler (--lock mode): calls ensure_ollama() before embedding phase
- cron status: displays Ollama health (installed/running/not-installed)
- robot JSON: includes OllamaStatusBrief in cron status response

The module handles local vs remote Ollama URLs, IPv6, process detection
via lsof, and graceful startup with configurable wait timeouts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
teernisse
2026-03-12 17:06:57 -04:00
parent 75469af514
commit 16bd33e8c0
4 changed files with 569 additions and 1 deletions

View File

@@ -9,6 +9,7 @@ use crate::core::cron::{
};
use crate::core::db::create_connection;
use crate::core::error::Result;
use crate::core::ollama_mgmt::{OllamaStatusBrief, ollama_status_brief};
use crate::core::paths::get_db_path;
use crate::core::time::ms_to_iso;
@@ -143,12 +144,20 @@ pub fn run_cron_status(config: &Config) -> Result<CronStatusInfo> {
// Query last sync run from DB
let last_sync = get_last_sync_time(config).unwrap_or_default();
Ok(CronStatusInfo { status, last_sync })
// Quick ollama health check
let ollama = ollama_status_brief(&config.embedding.base_url);
Ok(CronStatusInfo {
status,
last_sync,
ollama,
})
}
pub struct CronStatusInfo {
pub status: CronStatusResult,
pub last_sync: Option<LastSyncInfo>,
pub ollama: OllamaStatusBrief,
}
pub struct LastSyncInfo {
@@ -236,6 +245,32 @@ pub fn print_cron_status(info: &CronStatusInfo) {
last.status
);
}
// Ollama status
if info.ollama.installed {
if info.ollama.running {
println!(
" {} running (auto-started by cron if needed)",
Theme::dim().render("ollama:")
);
} else {
println!(
" {} {}",
Theme::warning().render("ollama:"),
Theme::warning()
.render("installed but not running (will attempt start on next sync)")
);
}
} else {
println!(
" {} {}",
Theme::error().render("ollama:"),
Theme::error().render("not installed — embeddings unavailable")
);
if let Some(ref hint) = info.ollama.install_hint {
println!(" {hint}");
}
}
println!();
}
@@ -264,6 +299,7 @@ struct CronStatusData {
last_sync_at: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
last_sync_status: Option<String>,
ollama: OllamaStatusBrief,
}
pub fn print_cron_status_json(info: &CronStatusInfo, elapsed_ms: u64) {
@@ -283,6 +319,7 @@ pub fn print_cron_status_json(info: &CronStatusInfo, elapsed_ms: u64) {
cron_entry: info.status.cron_entry.clone(),
last_sync_at: info.last_sync.as_ref().map(|s| s.started_at_iso.clone()),
last_sync_status: info.last_sync.as_ref().map(|s| s.status.clone()),
ollama: info.ollama.clone(),
},
meta: RobotMeta::new(elapsed_ms),
};