use anyhow::{Context, Result};
use std::os::windows::process::CommandExt as _;
use tracing::{debug, info, warn};
use winreg::enums::*;
use winreg::RegKey;
const CREATE_NO_WINDOW: u32 = 0x08000000;
const RUN_KEY_PATH: &str = r"Software\Microsoft\Windows\CurrentVersion\Run";
const USER_ENV_KEY_PATH: &str = "Environment";
const FREECYCLE_REG_NAME: &str = "FreeCycle";
const OLLAMA_REG_NAMES: &[&str] = &["Ollama", "ollama", "OllamaSetup"];
pub fn register_freecycle_autostart() -> Result<()> {
let exe_path = std::env::current_exe()
.context("Failed to get current executable path")?
.to_string_lossy()
.to_string();
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
let run_key = hkcu
.open_subkey_with_flags(RUN_KEY_PATH, KEY_SET_VALUE | KEY_READ)
.context("Failed to open Run registry key")?;
if let Ok(existing) = run_key.get_value::<String, _>(FREECYCLE_REG_NAME) {
if existing == exe_path {
debug!("FreeCycle auto-start already registered with current path");
return Ok(());
}
}
run_key
.set_value(FREECYCLE_REG_NAME, &exe_path)
.context("Failed to set FreeCycle auto-start registry value")?;
info!("Registered FreeCycle auto-start: {}", exe_path);
Ok(())
}
pub fn sync_autostart(enabled: bool) {
if enabled {
if let Err(e) = register_freecycle_autostart() {
warn!("Failed to register FreeCycle auto-start: {}", e);
}
} else {
if let Err(e) = unregister_freecycle_autostart() {
warn!("Failed to unregister FreeCycle auto-start: {}", e);
}
}
}
pub fn unregister_freecycle_autostart() -> Result<()> {
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
let run_key = hkcu
.open_subkey_with_flags(RUN_KEY_PATH, KEY_SET_VALUE)
.context("Failed to open Run registry key")?;
match run_key.delete_value(FREECYCLE_REG_NAME) {
Ok(()) => {
info!("Removed FreeCycle auto-start registry entry");
Ok(())
}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
debug!("FreeCycle auto-start entry not found (already removed)");
Ok(())
}
Err(e) => Err(e).context("Failed to delete FreeCycle auto-start registry value"),
}
}
pub fn disable_ollama_autostart() -> Result<()> {
disable_ollama_registry_run(HKEY_CURRENT_USER, "HKCU")?;
if let Err(e) = disable_ollama_registry_run(HKEY_LOCAL_MACHINE, "HKLM") {
debug!("Could not check HKLM Run key (may require admin): {}", e);
}
disable_ollama_scheduled_tasks();
Ok(())
}
fn disable_ollama_registry_run(hkey: winreg::HKEY, label: &str) -> Result<()> {
let root = RegKey::predef(hkey);
let run_key = match root.open_subkey_with_flags(RUN_KEY_PATH, KEY_SET_VALUE | KEY_READ) {
Ok(key) => key,
Err(e) => {
debug!("Could not open {} Run key: {}", label, e);
return Ok(());
}
};
for name in OLLAMA_REG_NAMES {
match run_key.delete_value(name) {
Ok(()) => {
info!("Disabled Ollama auto-start: {}\\{}", label, name);
}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
debug!("Ollama auto-start entry '{}' not found in {}", name, label);
}
Err(e) => {
warn!(
"Failed to remove Ollama auto-start '{}' from {}: {}",
name, label, e
);
}
}
}
Ok(())
}
pub fn enforce_ollama_localhost_binding(host: &str, port: u16) -> Result<()> {
let host_value = format!("{}:{}", host, port);
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
let env_key = hkcu
.open_subkey_with_flags(USER_ENV_KEY_PATH, KEY_SET_VALUE)
.context("Failed to open HKCU\\Environment registry key")?;
env_key
.set_value("OLLAMA_HOST", &host_value)
.context("Failed to set OLLAMA_HOST in user environment registry")?;
info!("Set HKCU\\Environment\\OLLAMA_HOST = {}", host_value);
broadcast_environment_change();
Ok(())
}
fn broadcast_environment_change() {
use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
use windows_sys::Win32::UI::WindowsAndMessaging::{
SendMessageTimeoutW, SMTO_ABORTIFHUNG, WM_SETTINGCHANGE,
};
let env_wide: Vec<u16> = OsStr::new("Environment")
.encode_wide()
.chain(std::iter::once(0))
.collect();
let mut result: usize = 0;
unsafe {
SendMessageTimeoutW(
0xFFFF as *mut std::ffi::c_void, WM_SETTINGCHANGE,
0,
env_wide.as_ptr() as isize,
SMTO_ABORTIFHUNG,
5000,
&mut result,
);
}
debug!("Broadcast WM_SETTINGCHANGE to propagate OLLAMA_HOST environment change");
}
fn disable_ollama_scheduled_tasks() {
let task_names = ["Ollama", "OllamaUpdate"];
for task_name in &task_names {
let result = std::process::Command::new("schtasks")
.args(["/Change", "/TN", task_name, "/DISABLE"])
.creation_flags(CREATE_NO_WINDOW)
.output();
match result {
Ok(output) if output.status.success() => {
info!("Disabled Ollama scheduled task: {}", task_name);
}
Ok(_) => {
debug!(
"Ollama scheduled task '{}' not found or already disabled",
task_name
);
}
Err(e) => {
debug!("Could not check scheduled task '{}': {}", task_name, e);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_registry_path_is_valid() {
assert!(RUN_KEY_PATH.contains("Run"));
}
#[test]
fn test_ollama_reg_names_not_empty() {
assert!(!OLLAMA_REG_NAMES.is_empty());
}
}