From b2650a569c72b3d64c5df7ea6d456f8e2957ff5e Mon Sep 17 00:00:00 2001 From: Tara Menon Date: Thu, 11 Jun 2026 23:23:49 +0530 Subject: [PATCH] feat: system service management (gyandoot service install|remove|status) - Create systemd + launchd service templates in resources/ - Add gyandoot-core/src/service.rs module with full lifecycle - systemd: user service, daemon-reload, enable, start, stop, disable - launchd: macOS plist, load/unload, start/stop (launchctl) - Add 'gyandoot service install|remove|status' CLI subcommand - Update setup wizard: prompts 'Install system service? [y/N]' - Update install.sh: prompts user for service install after binary - Browser auto-starts at runtime (browser_manager), no separate service needed - Camoufox installed via npm, auto-started by GyanDoot agent loop --- crates/gyandoot-core/src/error.rs | 3 + crates/gyandoot-core/src/lib.rs | 1 + crates/gyandoot-core/src/service.rs | 390 +++++++++++++++++++++ crates/gyandoot-tui/src/setup.rs | 34 ++ install.sh | 113 ++---- resources/launchd/com.gyandoot.agent.plist | 52 +++ resources/systemd/gyandoot.service | 31 ++ src/main.rs | 74 ++++ 8 files changed, 615 insertions(+), 83 deletions(-) create mode 100644 crates/gyandoot-core/src/service.rs create mode 100644 resources/launchd/com.gyandoot.agent.plist create mode 100644 resources/systemd/gyandoot.service diff --git a/crates/gyandoot-core/src/error.rs b/crates/gyandoot-core/src/error.rs index fb81504..9fc7275 100644 --- a/crates/gyandoot-core/src/error.rs +++ b/crates/gyandoot-core/src/error.rs @@ -143,6 +143,9 @@ pub enum AgentError { #[error("Agent loop terminated: {0}")] Terminated(String), + #[error("IO error: {0}")] + Io(#[from] std::io::Error), + #[error("Internal error: {0}")] Internal(String), } diff --git a/crates/gyandoot-core/src/lib.rs b/crates/gyandoot-core/src/lib.rs index 601dcdb..c629eb6 100644 --- a/crates/gyandoot-core/src/lib.rs +++ b/crates/gyandoot-core/src/lib.rs @@ -7,6 +7,7 @@ pub mod types; pub mod config; pub mod error; +pub mod service; pub use types::*; pub use config::*; diff --git a/crates/gyandoot-core/src/service.rs b/crates/gyandoot-core/src/service.rs new file mode 100644 index 0000000..eb9bb57 --- /dev/null +++ b/crates/gyandoot-core/src/service.rs @@ -0,0 +1,390 @@ +//! Service management for GyanDoot — install, remove, and check system services. +//! +//! Supports: +//! - **Linux**: systemd user services (`systemctl --user`) +//! - **macOS**: launchd user agents (`launchctl`) +//! +//! Templates live in the resources/ directory of the repo and are rendered +//! at install time with the actual binary path and home directory. + +use std::path::{Path, PathBuf}; +use tracing::{info, warn}; + +use crate::{AgentError, AgentResult}; + +/// Supported init systems. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum InitSystem { + Systemd, + Launchd, + Unknown, +} + +impl InitSystem { + /// Detect the current init system from the OS. + pub fn detect() -> Self { + if cfg!(target_os = "linux") { + // Check if systemd is available + if std::process::Command::new("systemctl") + .arg("--version") + .output() + .is_ok() + { + Self::Systemd + } else { + Self::Unknown + } + } else if cfg!(target_os = "macos") { + Self::Launchd + } else { + Self::Unknown + } + } + + /// Human-readable name. + pub fn name(&self) -> &str { + match self { + Self::Systemd => "systemd", + Self::Launchd => "launchd", + Self::Unknown => "unknown", + } + } +} + +/// Result of a service status check. +#[derive(Debug, Clone)] +pub struct ServiceStatus { + pub name: String, + pub init_system: String, + pub installed: bool, + pub running: bool, + pub enabled: bool, + pub details: String, +} + +/// Install GyanDoot as a system service. +/// +/// Detects the init system, renders the appropriate template, installs it, +/// and starts the service. +pub fn install_service() -> AgentResult { + let init = InitSystem::detect(); + info!("Detected init system: {}", init.name()); + + match init { + InitSystem::Systemd => install_systemd(), + InitSystem::Launchd => install_launchd(), + InitSystem::Unknown => Err(AgentError::Io(std::io::Error::new( + std::io::ErrorKind::Unsupported, + format!( + "No supported init system detected. Supported: systemd (Linux), launchd (macOS).\n\ + You can still run GyanDoot manually: `gyandoot run` or `gyandoot serve`" + ), + ))), + } +} + +/// Remove the GyanDoot system service (stop + disable + delete). +pub fn remove_service() -> AgentResult<()> { + let init = InitSystem::detect(); + info!("Detected init system: {}", init.name()); + + match init { + InitSystem::Systemd => remove_systemd(), + InitSystem::Launchd => remove_launchd(), + InitSystem::Unknown => Err(AgentError::Io(std::io::Error::new( + std::io::ErrorKind::Unsupported, + "No supported init system detected.", + ))), + } +} + +/// Check the status of the GyanDoot service. +pub fn check_service() -> AgentResult { + let init = InitSystem::detect(); + info!("Detected init system: {}", init.name()); + + match init { + InitSystem::Systemd => check_systemd(), + InitSystem::Launchd => check_launchd(), + InitSystem::Unknown => Ok(ServiceStatus { + name: "gyandoot".to_string(), + init_system: "unknown".to_string(), + installed: false, + running: false, + enabled: false, + details: "Unknown init system. Supported: systemd (Linux), launchd (macOS).".to_string(), + }), + } +} + +// ── systemd ────────────────────────────────────────────────────────────── + +/// Get the systemd user service directory. +fn systemd_user_dir() -> PathBuf { + if let Ok(xdg) = std::env::var("XDG_CONFIG_HOME") { + PathBuf::from(xdg).join("systemd").join("user") + } else if let Some(home) = dirs::home_dir() { + home.join(".config").join("systemd").join("user") + } else { + PathBuf::from("/tmp/gyandoot-systemd-user") + } +} + +fn install_systemd() -> AgentResult { + let home = dirs::home_dir().ok_or_else(|| { + AgentError::Io(std::io::Error::new(std::io::ErrorKind::NotFound, "Cannot determine home directory")) + })?; + let binary = std::env::current_exe().unwrap_or_else(|_| PathBuf::from("gyandoot")); + let user = std::env::var("USER").unwrap_or_else(|_| "root".to_string()); + let group = std::env::var("USER").unwrap_or_else(|_| "root".to_string()); + + let service_dir = systemd_user_dir(); + std::fs::create_dir_all(&service_dir).map_err(|e| { + AgentError::Io(std::io::Error::new( + std::io::ErrorKind::Other, + format!("Cannot create systemd user dir {}: {}", service_dir.display(), e), + )) + })?; + + let service_path = service_dir.join("gyandoot.service"); + + // Render template + let template = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../resources/systemd/gyandoot.service" + )); + + let rendered = template + .replace("%BINARY%", &binary.to_string_lossy()) + .replace("%USER%", &user) + .replace("%GROUP%", &group) + .replace("%HOME%", &home.to_string_lossy()); + + std::fs::write(&service_path, &rendered).map_err(|e| { + AgentError::Io(std::io::Error::new( + std::io::ErrorKind::Other, + format!("Cannot write service file {}: {}", service_path.display(), e), + )) + })?; + + info!("Wrote systemd service to {}", service_path.display()); + + // Reload systemd user daemon + run_cmd("systemctl", &["--user", "daemon-reload"])?; + + // Enable and start + run_cmd("systemctl", &["--user", "enable", "gyandoot.service"])?; + run_cmd("systemctl", &["--user", "start", "gyandoot.service"])?; + + info!("GyanDoot systemd service installed and started"); + + check_systemd() +} + +fn remove_systemd() -> AgentResult<()> { + // Stop first + let _ = run_cmd("systemctl", &["--user", "stop", "gyandoot.service"]); + + // Disable + let _ = run_cmd("systemctl", &["--user", "disable", "gyandoot.service"]); + + // Remove file + let service_path = systemd_user_dir().join("gyandoot.service"); + if service_path.exists() { + std::fs::remove_file(&service_path).map_err(|e| { + AgentError::Io(std::io::Error::new( + std::io::ErrorKind::Other, + format!("Cannot remove {}: {}", service_path.display(), e), + )) + })?; + info!("Removed systemd service file: {}", service_path.display()); + } + + // Reload + let _ = run_cmd("systemctl", &["--user", "daemon-reload"]); + + Ok(()) +} + +fn check_systemd() -> AgentResult { + let mut details = String::new(); + + // Check if unit file exists + let service_path = systemd_user_dir().join("gyandoot.service"); + let installed = service_path.exists(); + + let mut running = false; + let mut enabled = false; + + if installed { + // Check if running + if let Ok(out) = run_cmd_for_output("systemctl", &["--user", "is-active", "gyandoot.service"]) { + running = out.trim() == "active"; + details.push_str(&format!("Active: {}", out.trim())); + } + + if let Ok(out) = run_cmd_for_output("systemctl", &["--user", "is-enabled", "gyandoot.service"]) { + enabled = out.trim() == "enabled"; + details.push_str(&format!("\nEnabled: {}", out.trim())); + } + } else { + details = "Not installed".to_string(); + } + + Ok(ServiceStatus { + name: "gyandoot".to_string(), + init_system: "systemd".to_string(), + installed, + running, + enabled, + details, + }) +} + +// ── launchd (macOS) ──────────────────────────────────────────────────── + +fn launchd_plist_path() -> PathBuf { + if let Some(home) = dirs::home_dir() { + home.join("Library").join("LaunchAgents").join("com.gyandoot.agent.plist") + } else { + PathBuf::from("/tmp/com.gyandoot.agent.plist") + } +} + +fn install_launchd() -> AgentResult { + let home = dirs::home_dir().ok_or_else(|| { + AgentError::Io(std::io::Error::new(std::io::ErrorKind::NotFound, "Cannot determine home directory")) + })?; + let binary = std::env::current_exe().unwrap_or_else(|_| PathBuf::from("gyandoot")); + + let launch_agents_dir = home.join("Library").join("LaunchAgents"); + std::fs::create_dir_all(&launch_agents_dir).map_err(|e| { + AgentError::Io(std::io::Error::new( + std::io::ErrorKind::Other, + format!("Cannot create LaunchAgents dir {}: {}", launch_agents_dir.display(), e), + )) + })?; + + let plist_path = launchd_plist_path(); + + // Render template + let template = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../resources/launchd/com.gyandoot.agent.plist" + )); + + let rendered = template + .replace("%BINARY%", &binary.to_string_lossy()) + .replace("%HOME%", &home.to_string_lossy()); + + std::fs::write(&plist_path, &rendered).map_err(|e| { + AgentError::Io(std::io::Error::new( + std::io::ErrorKind::Other, + format!("Cannot write plist {}: {}", plist_path.display(), e), + )) + })?; + + info!("Wrote launchd plist to {}", plist_path.display()); + + // Unload first (in case it was already loaded) + let _ = run_cmd("launchctl", &["unload", &plist_path.to_string_lossy()]); + + // Load the new plist + run_cmd("launchctl", &["load", &plist_path.to_string_lossy()])?; + + // Start + run_cmd("launchctl", &["start", "com.gyandoot.agent"])?; + + info!("GyanDoot launchd agent installed and started"); + + check_launchd() +} + +fn remove_launchd() -> AgentResult<()> { + let plist_path = launchd_plist_path(); + + // Stop + let _ = run_cmd("launchctl", &["stop", "com.gyandoot.agent"]); + + // Unload + if plist_path.exists() { + let _ = run_cmd("launchctl", &["unload", &plist_path.to_string_lossy()]); + std::fs::remove_file(&plist_path).map_err(|e| { + AgentError::Io(std::io::Error::new( + std::io::ErrorKind::Other, + format!("Cannot remove plist {}: {}", plist_path.display(), e), + )) + })?; + info!("Removed launchd plist: {}", plist_path.display()); + } + + Ok(()) +} + +fn check_launchd() -> AgentResult { + let plist_path = launchd_plist_path(); + let installed = plist_path.exists(); + + let mut running = false; + let mut details = String::new(); + + if installed { + // Check if loaded + if let Ok(out) = run_cmd_for_output("launchctl", &["list", "com.gyandoot.agent"]) { + running = !out.contains("PID = 0,") && !out.is_empty(); + details = out; + } + } else { + details = "Not installed".to_string(); + } + + Ok(ServiceStatus { + name: "com.gyandoot.agent".to_string(), + init_system: "launchd".to_string(), + installed, + running, + enabled: installed, // launchd agents are "enabled" if loaded + details, + }) +} + +// ── Helpers ───────────────────────────────────────────────────────────── + +/// Run a command and return stdout as a String. +fn run_cmd_for_output(cmd: &str, args: &[&str]) -> Result { + let output = std::process::Command::new(cmd) + .args(args) + .output() + .map_err(|e| format!("Failed to execute {}: {}", cmd, e))?; + + let stdout = String::from_utf8_lossy(&output.stdout).to_string(); + let stderr = String::from_utf8_lossy(&output.stderr).to_string(); + + if output.status.success() { + Ok(stdout) + } else { + Err(format!("{} failed: {}", cmd, stderr.trim())) + } +} + +/// Run a command and check for success. +fn run_cmd(cmd: &str, args: &[&str]) -> AgentResult<()> { + let status = std::process::Command::new(cmd) + .args(args) + .status() + .map_err(|e| { + AgentError::Io(std::io::Error::new( + std::io::ErrorKind::Other, + format!("Failed to execute {}: {}", cmd, e), + )) + })?; + + if status.success() { + Ok(()) + } else { + Err(AgentError::Io(std::io::Error::new( + std::io::ErrorKind::Other, + format!("{} exited with status {:?}", cmd, status.code()), + ))) + } +} \ No newline at end of file diff --git a/crates/gyandoot-tui/src/setup.rs b/crates/gyandoot-tui/src/setup.rs index 35cbe76..5834c00 100644 --- a/crates/gyandoot-tui/src/setup.rs +++ b/crates/gyandoot-tui/src/setup.rs @@ -333,6 +333,40 @@ pub fn run_setup_wizard() -> anyhow::Result> { &format!("Configuration saved to {}", config_path.display()), ); + // Step 7: Optional service install + println!(); + terminal::section_header("System Service"); + println!(" GyanDoot can run as a system service and auto-start on boot."); + println!(" This keeps the gateway server running in the background."); + let install_service = terminal::confirm_prompt( + "Install GyanDoot system service (auto-start on boot)?", + Some(false), + ); + + if install_service { + terminal::show_status( + terminal::StatusKind::Info, + "Installing system service...", + ); + match gyandoot_core::service::install_service() { + Ok(status) => { + terminal::show_status( + terminal::StatusKind::Success, + &format!("Service installed! (init: {})", status.init_system), + ); + } + Err(e) => { + terminal::show_status( + terminal::StatusKind::Warning, + &format!("Could not install service: {}", e), + ); + println!(" You can install later with: gyandoot service install"); + } + } + } else { + println!(" You can install later with: gyandoot service install"); + } + Ok(Some(WizardResult { provider: choice, config, diff --git a/install.sh b/install.sh index 9b78c15..ddc1396 100755 --- a/install.sh +++ b/install.sh @@ -79,90 +79,12 @@ install_camofox_browser() { fi } -setup_camofox_service() { - local os - os=$(detect_os) - - info "Setting up camofox-browser to auto-start..." - case "$os" in - linux) - local service_file="/etc/systemd/system/camofox-browser.service" - if [ ! -f "$service_file" ]; then - cat > /tmp/camofox-browser.service << 'SYSTEMD' -[Unit] -Description=Camofox Browser — Anti-detection browser for AI agents -After=network.target - -[Service] -Type=simple -ExecStart=npx camoufox-browser start --port 9377 -Restart=on-failure -RestartSec=5 -Environment=NODE_ENV=production - -[Install] -WantedBy=multi-user.target -SYSTEMD - sudo mv /tmp/camofox-browser.service "$service_file" - sudo systemctl daemon-reload - sudo systemctl enable camofox-browser - sudo systemctl start camofox-browser - ok "Camoufox auto-start service installed and started" - else - sudo systemctl start camofox-browser 2>/dev/null || true - ok "Camoufox service already exists" - fi - ;; - darwin) - # macOS: create a launchd plist for auto-start - local plist="$HOME/Library/LaunchAgents/com.camoufox.browser.plist" - if [ ! -f "$plist" ]; then - mkdir -p "$HOME/Library/LaunchAgents" - local home_dir="$HOME" - cat > "$plist" << LAUNCHD - - - - - Label - com.camoufox.browser - ProgramArguments - - /usr/local/bin/npx - camoufox-browser - start - --port - 9377 - - RunAtLoad - - KeepAlive - - StandardOutPath - ${home_dir}/.gyandoot/camoufox.log - StandardErrorPath - ${home_dir}/.gyandoot/camoufox.log - - -LAUNCHD - launchctl load "$plist" - ok "Camoufox auto-start configured (launchd)" - else - launchctl load "$plist" 2>/dev/null || true - ok "Camoufox launchd plist already exists" - fi - ;; - esac - ok "Camofox-browser auto-start setup complete" -} - build_release() { local root_dir root_dir="$(cd "$(dirname "$0")" && pwd)" info "Building GyanDoot v2.0 in release mode..." cd "$root_dir" if [ ! -f "$root_dir/Cargo.toml" ]; then - # Fallback: find Cargo.toml in parent chain (in case script is in sub-dir) local probe probe="$(pwd)" while [ "$probe" != "/" ]; do @@ -210,6 +132,7 @@ print_post_install() { echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo " gyandoot --help Show all commands" echo " gyandoot init Create config" + echo " gyandoot setup Interactive setup wizard" echo " gyandoot run Start interactive agent" echo "" echo " Browser Tools (anti-detection browsing):" @@ -217,13 +140,28 @@ print_post_install() { echo " gyandoot browser search " echo " gyandoot browser browse " echo "" - echo " Start camofox-browser:" - echo " sudo systemctl start camofox-browser" - echo " # Or manually: cd ~/camofox-browser && npm start" + echo " Service Management:" + echo " gyandoot service install Auto-start on boot" + echo " gyandoot service remove Remove system service" + echo " gyandoot service status Check service status" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" } +prompt_service_install() { + echo "" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo " Optional: Auto-start GyanDoot on Boot?" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo " GyanDoot can run as a system service and" + echo " start automatically when your computer boots." + echo " This keeps the gateway server running in the background." + echo "" + echo " (You can also do this later with: gyandoot service install)" + echo "" + read -r -p " Install system service? [y/N]: " response +} + main() { local os arch os=$(detect_os) @@ -237,8 +175,17 @@ main() { build_release install_binary - # Optional: don't fail if service setup doesn't work - setup_camofox_service 2>/dev/null || warn "Could not setup camofox service (manual start works)" + # Optional: install GyanDoot system service (auto-start on boot) + prompt_service_install + case "$response" in + [yY]|[yY][eE][sS]) + info "Installing GyanDoot system service..." + gyandoot service install 2>&1 || warn "Service install failed (run 'gyandoot service install' later)" + ;; + *) + info "Skipping service install (use 'gyandoot service install' later)" + ;; + esac print_post_install } diff --git a/resources/launchd/com.gyandoot.agent.plist b/resources/launchd/com.gyandoot.agent.plist new file mode 100644 index 0000000..c03c8cf --- /dev/null +++ b/resources/launchd/com.gyandoot.agent.plist @@ -0,0 +1,52 @@ + + + + + Label + com.gyandoot.agent + + ProgramArguments + + %BINARY% + serve + --host + 127.0.0.1 + --port + 8777 + + + RunAtLoad + + + KeepAlive + + SuccessfulExit + + Crashed + + + + WatchPaths + + %HOME%/.config/gyandoot/config.toml + + + StandardOutPath + %HOME%/.gyandoot/service.log + StandardErrorPath + %HOME%/.gyandoot/service.log + + EnvironmentVariables + + GYANDOOT_CONFIG + %HOME%/.config/gyandoot/config.toml + GYANDOOT_LOG + info + PATH + /usr/local/bin:/usr/bin:/bin:%HOME%/.local/bin + + + WorkingDirectory + %HOME%/.gyandoot + + \ No newline at end of file diff --git a/resources/systemd/gyandoot.service b/resources/systemd/gyandoot.service new file mode 100644 index 0000000..f4293c0 --- /dev/null +++ b/resources/systemd/gyandoot.service @@ -0,0 +1,31 @@ +[Unit] +Description=GyanDoot v2.0 — AI Agent Platform +Documentation=https://git.gyannetra.in/gnpl_admin/gyandoot-v2 +After=network.target network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=%USER% +Group=%GROUP% +ExecStart=%BINARY% serve --host 127.0.0.1 --port 8777 +Restart=on-failure +RestartSec=5 +TimeoutStopSec=10 +KillMode=mixed +StandardOutput=append:%HOME%/.gyandoot/service.log +StandardError=append:%HOME%/.gyandoot/service.log + +# Environment +Environment=GYANDOOT_CONFIG=%HOME%/.config/gyandoot/config.toml +Environment=GYANDOOT_LOG=info + +# Security hardening +NoNewPrivileges=true +PrivateTmp=true +ProtectHome=read-only +ProtectSystem=strict +ReadWritePaths=%HOME%/.gyandoot %HOME%/.config/gyandoot + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index a22858a..c587a83 100644 --- a/src/main.rs +++ b/src/main.rs @@ -75,6 +75,12 @@ enum Commands { /// Launch the TUI interface #[command(name = "tui")] Tui, + /// Manage GyanDoot system service (install, remove, status) + #[command(name = "service")] + Service { + #[command(subcommand)] + action: ServiceAction, + }, } #[derive(Subcommand, Debug)] @@ -116,6 +122,16 @@ enum BrowserAction { }, } +#[derive(Subcommand, Debug)] +enum ServiceAction { + /// Install GyanDoot as a system service (auto-start on boot) + Install, + /// Remove the GyanDoot system service + Remove, + /// Check GyanDoot service status + Status, +} + #[tokio::main] async fn main() -> anyhow::Result<()> { let cli = Cli::parse(); @@ -161,6 +177,9 @@ async fn main() -> anyhow::Result<()> { Some(Commands::Tui) => { cmd_tui().await?; } + Some(Commands::Service { action }) => { + cmd_service(action).await?; + } None => { // Default: run interactive CLI mode cmd_run(&cli.profile, "cli", None).await?; @@ -535,4 +554,59 @@ async fn cmd_tui() -> anyhow::Result<()> { tracing::info!("Launching TUI"); gyandoot_tui::tui::run_tui()?; Ok(()) +} + +/// Handle service management subcommands +async fn cmd_service(action: ServiceAction) -> anyhow::Result<()> { + use gyandoot_core::service; + + match action { + ServiceAction::Install => { + println!("📦 Installing GyanDoot system service..."); + match service::install_service() { + Ok(status) => { + println!("✅ GyanDoot service installed successfully!"); + println!(" Init system: {}", status.init_system); + println!(" Status: {}", status.details); + println!("\n GyanDoot will now start automatically on boot."); + } + Err(e) => { + eprintln!("❌ Failed to install service: {}", e); + eprintln!(" You can still run: gyandoot run"); + } + } + } + ServiceAction::Remove => { + println!("🗑️ Removing GyanDoot system service..."); + match service::remove_service() { + Ok(()) => { + println!("✅ GyanDoot service removed."); + } + Err(e) => { + eprintln!("❌ Failed to remove service: {}", e); + } + } + } + ServiceAction::Status => { + match service::check_service() { + Ok(status) => { + println!("📊 GyanDoot Service Status"); + println!(" Name: {}", status.name); + println!(" Init System: {}", status.init_system); + println!(" Installed: {}", if status.installed { "✅ Yes" } else { "❌ No" }); + println!(" Running: {}", if status.running { "✅ Yes" } else { "❌ No" }); + println!(" Auto-start: {}", if status.enabled { "✅ Enabled" } else { "❌ Disabled" }); + println!(" Details: {}", status.details); + + if !status.installed { + println!("\n 💡 Install with: gyandoot service install"); + } + } + Err(e) => { + eprintln!("❌ Failed to check service status: {}", e); + } + } + } + } + Ok(()) } \ No newline at end of file