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
This commit is contained in:
@@ -143,6 +143,9 @@ pub enum AgentError {
|
|||||||
#[error("Agent loop terminated: {0}")]
|
#[error("Agent loop terminated: {0}")]
|
||||||
Terminated(String),
|
Terminated(String),
|
||||||
|
|
||||||
|
#[error("IO error: {0}")]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
|
||||||
#[error("Internal error: {0}")]
|
#[error("Internal error: {0}")]
|
||||||
Internal(String),
|
Internal(String),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
pub mod types;
|
pub mod types;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
pub mod service;
|
||||||
|
|
||||||
pub use types::*;
|
pub use types::*;
|
||||||
pub use config::*;
|
pub use config::*;
|
||||||
|
|||||||
390
crates/gyandoot-core/src/service.rs
Normal file
390
crates/gyandoot-core/src/service.rs
Normal file
@@ -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<ServiceStatus> {
|
||||||
|
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<ServiceStatus> {
|
||||||
|
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<ServiceStatus> {
|
||||||
|
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<ServiceStatus> {
|
||||||
|
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<ServiceStatus> {
|
||||||
|
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<ServiceStatus> {
|
||||||
|
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<String, String> {
|
||||||
|
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()),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -333,6 +333,40 @@ pub fn run_setup_wizard() -> anyhow::Result<Option<WizardResult>> {
|
|||||||
&format!("Configuration saved to {}", config_path.display()),
|
&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 {
|
Ok(Some(WizardResult {
|
||||||
provider: choice,
|
provider: choice,
|
||||||
config,
|
config,
|
||||||
|
|||||||
113
install.sh
113
install.sh
@@ -79,90 +79,12 @@ install_camofox_browser() {
|
|||||||
fi
|
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
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>Label</key>
|
|
||||||
<string>com.camoufox.browser</string>
|
|
||||||
<key>ProgramArguments</key>
|
|
||||||
<array>
|
|
||||||
<string>/usr/local/bin/npx</string>
|
|
||||||
<string>camoufox-browser</string>
|
|
||||||
<string>start</string>
|
|
||||||
<string>--port</string>
|
|
||||||
<string>9377</string>
|
|
||||||
</array>
|
|
||||||
<key>RunAtLoad</key>
|
|
||||||
<true/>
|
|
||||||
<key>KeepAlive</key>
|
|
||||||
<true/>
|
|
||||||
<key>StandardOutPath</key>
|
|
||||||
<string>${home_dir}/.gyandoot/camoufox.log</string>
|
|
||||||
<key>StandardErrorPath</key>
|
|
||||||
<string>${home_dir}/.gyandoot/camoufox.log</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
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() {
|
build_release() {
|
||||||
local root_dir
|
local root_dir
|
||||||
root_dir="$(cd "$(dirname "$0")" && pwd)"
|
root_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||||
info "Building GyanDoot v2.0 in release mode..."
|
info "Building GyanDoot v2.0 in release mode..."
|
||||||
cd "$root_dir"
|
cd "$root_dir"
|
||||||
if [ ! -f "$root_dir/Cargo.toml" ]; then
|
if [ ! -f "$root_dir/Cargo.toml" ]; then
|
||||||
# Fallback: find Cargo.toml in parent chain (in case script is in sub-dir)
|
|
||||||
local probe
|
local probe
|
||||||
probe="$(pwd)"
|
probe="$(pwd)"
|
||||||
while [ "$probe" != "/" ]; do
|
while [ "$probe" != "/" ]; do
|
||||||
@@ -210,6 +132,7 @@ print_post_install() {
|
|||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
echo " gyandoot --help Show all commands"
|
echo " gyandoot --help Show all commands"
|
||||||
echo " gyandoot init Create config"
|
echo " gyandoot init Create config"
|
||||||
|
echo " gyandoot setup Interactive setup wizard"
|
||||||
echo " gyandoot run Start interactive agent"
|
echo " gyandoot run Start interactive agent"
|
||||||
echo ""
|
echo ""
|
||||||
echo " Browser Tools (anti-detection browsing):"
|
echo " Browser Tools (anti-detection browsing):"
|
||||||
@@ -217,13 +140,28 @@ print_post_install() {
|
|||||||
echo " gyandoot browser search <query>"
|
echo " gyandoot browser search <query>"
|
||||||
echo " gyandoot browser browse <url>"
|
echo " gyandoot browser browse <url>"
|
||||||
echo ""
|
echo ""
|
||||||
echo " Start camofox-browser:"
|
echo " Service Management:"
|
||||||
echo " sudo systemctl start camofox-browser"
|
echo " gyandoot service install Auto-start on boot"
|
||||||
echo " # Or manually: cd ~/camofox-browser && npm start"
|
echo " gyandoot service remove Remove system service"
|
||||||
|
echo " gyandoot service status Check service status"
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
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() {
|
main() {
|
||||||
local os arch
|
local os arch
|
||||||
os=$(detect_os)
|
os=$(detect_os)
|
||||||
@@ -237,8 +175,17 @@ main() {
|
|||||||
build_release
|
build_release
|
||||||
install_binary
|
install_binary
|
||||||
|
|
||||||
# Optional: don't fail if service setup doesn't work
|
# Optional: install GyanDoot system service (auto-start on boot)
|
||||||
setup_camofox_service 2>/dev/null || warn "Could not setup camofox service (manual start works)"
|
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
|
print_post_install
|
||||||
}
|
}
|
||||||
|
|||||||
52
resources/launchd/com.gyandoot.agent.plist
Normal file
52
resources/launchd/com.gyandoot.agent.plist
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>Label</key>
|
||||||
|
<string>com.gyandoot.agent</string>
|
||||||
|
|
||||||
|
<key>ProgramArguments</key>
|
||||||
|
<array>
|
||||||
|
<string>%BINARY%</string>
|
||||||
|
<string>serve</string>
|
||||||
|
<string>--host</string>
|
||||||
|
<string>127.0.0.1</string>
|
||||||
|
<string>--port</string>
|
||||||
|
<string>8777</string>
|
||||||
|
</array>
|
||||||
|
|
||||||
|
<key>RunAtLoad</key>
|
||||||
|
<true/>
|
||||||
|
|
||||||
|
<key>KeepAlive</key>
|
||||||
|
<dict>
|
||||||
|
<key>SuccessfulExit</key>
|
||||||
|
<false/>
|
||||||
|
<key>Crashed</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
|
||||||
|
<key>WatchPaths</key>
|
||||||
|
<array>
|
||||||
|
<string>%HOME%/.config/gyandoot/config.toml</string>
|
||||||
|
</array>
|
||||||
|
|
||||||
|
<key>StandardOutPath</key>
|
||||||
|
<string>%HOME%/.gyandoot/service.log</string>
|
||||||
|
<key>StandardErrorPath</key>
|
||||||
|
<string>%HOME%/.gyandoot/service.log</string>
|
||||||
|
|
||||||
|
<key>EnvironmentVariables</key>
|
||||||
|
<dict>
|
||||||
|
<key>GYANDOOT_CONFIG</key>
|
||||||
|
<string>%HOME%/.config/gyandoot/config.toml</string>
|
||||||
|
<key>GYANDOOT_LOG</key>
|
||||||
|
<string>info</string>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>/usr/local/bin:/usr/bin:/bin:%HOME%/.local/bin</string>
|
||||||
|
</dict>
|
||||||
|
|
||||||
|
<key>WorkingDirectory</key>
|
||||||
|
<string>%HOME%/.gyandoot</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
31
resources/systemd/gyandoot.service
Normal file
31
resources/systemd/gyandoot.service
Normal file
@@ -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
|
||||||
74
src/main.rs
74
src/main.rs
@@ -75,6 +75,12 @@ enum Commands {
|
|||||||
/// Launch the TUI interface
|
/// Launch the TUI interface
|
||||||
#[command(name = "tui")]
|
#[command(name = "tui")]
|
||||||
Tui,
|
Tui,
|
||||||
|
/// Manage GyanDoot system service (install, remove, status)
|
||||||
|
#[command(name = "service")]
|
||||||
|
Service {
|
||||||
|
#[command(subcommand)]
|
||||||
|
action: ServiceAction,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Subcommand, Debug)]
|
#[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]
|
#[tokio::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
@@ -161,6 +177,9 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
Some(Commands::Tui) => {
|
Some(Commands::Tui) => {
|
||||||
cmd_tui().await?;
|
cmd_tui().await?;
|
||||||
}
|
}
|
||||||
|
Some(Commands::Service { action }) => {
|
||||||
|
cmd_service(action).await?;
|
||||||
|
}
|
||||||
None => {
|
None => {
|
||||||
// Default: run interactive CLI mode
|
// Default: run interactive CLI mode
|
||||||
cmd_run(&cli.profile, "cli", None).await?;
|
cmd_run(&cli.profile, "cli", None).await?;
|
||||||
@@ -536,3 +555,58 @@ async fn cmd_tui() -> anyhow::Result<()> {
|
|||||||
gyandoot_tui::tui::run_tui()?;
|
gyandoot_tui::tui::run_tui()?;
|
||||||
Ok(())
|
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(())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user