diff --git a/src/app_manager.rs b/src/app_manager.rs new file mode 100644 index 0000000..9be2aae --- /dev/null +++ b/src/app_manager.rs @@ -0,0 +1,146 @@ +use std::path::PathBuf; +use std::process::Command; + +use serde::{Deserialize, Serialize}; +use std::env; +use std::fs; + +#[derive(Clone, Serialize, Deserialize)] +pub struct App { + pub repo: String, + pub branch: String, + pub install_command: String, + pub run_command: String, + pub enabled: Option, +} + +pub fn remove_app(apps: &mut Vec, index: usize) { + if let Some(app) = apps.get(index) { + let home = env::var("HOME").expect("Failed to get HOME environment variable"); + let apps_dir = PathBuf::from(home).join("sentryx/apps"); + let repo_name = app + .repo + .trim_end_matches(".git") + .rsplit('/') + .next() + .expect("Failed to extract repo name"); + fs::remove_dir_all(&apps_dir.join(repo_name)).expect("Failed to remove app directory"); + println!("Removed app: {}", app.repo); + } else { + println!("App not found: {}", index); + } + + apps.remove(index); + save_apps(apps); +} + +pub fn install_app(apps: &mut Vec, app: App) { + let a2 = app.clone(); + // Get home directory and create ~/sentryx/apps if needed + let home = env::var("HOME").expect("Failed to get HOME environment variable"); + let apps_dir = PathBuf::from(home).join("sentryx/apps"); + + if !apps_dir.exists() { + fs::create_dir_all(&apps_dir).expect("Failed to create apps directory"); + } + + // Change to apps directory + env::set_current_dir(&apps_dir).expect("Failed to change to apps directory"); + + println!("Installing app from repo: {}", app.repo); + println!("Branch: {}", app.branch); + println!("Install command: {}", app.install_command); + println!("Run command: {}", app.run_command); + + // Clone the repo + println!("Cloning repo"); + Command::new("git") + .arg("clone") + .arg("-b") + .arg(&app.branch) + .arg(&app.repo) + .status() + .expect("Failed to clone repository"); + + // Extract repo name from URL (e.g., "https://github.com/user/myapp.git" -> "myapp") + let repo_name = app + .repo + .trim_end_matches(".git") + .rsplit('/') + .next() + .expect("Failed to extract repo name"); + + // Change to cloned repo directory + env::set_current_dir(&repo_name).expect("Failed to change to cloned repo directory"); + + // You are now inside the cloned repo + println!("Changed directory to {:?}", env::current_dir().unwrap()); + + apps.push(a2.clone()); + save_apps(&apps); + + Command::new("bash") + .arg("-c") + .arg(app.install_command) + .status() + .expect("Failed to execute Bash command"); + + run_app(a2); +} + +pub fn run_app(app: App) { + // Change to the app directory + let home = env::var("HOME").expect("Failed to get HOME environment variable"); + let app_dir = PathBuf::from(home) + .join("sentryx/apps") + .join(app.repo.trim_end_matches(".git").split('/').last().unwrap()); + + env::set_current_dir(&app_dir).expect("Failed to change to app directory"); + + // Run the app + Command::new("bash") + .arg("-c") + .arg(app.run_command) + .status() + .expect("Failed to execute run command"); +} + +pub fn run_apps() -> Vec { + // Change to the app directory + let home = env::var("HOME").expect("Failed to get HOME environment variable"); + let apps_dir = PathBuf::from(home).join("sentryx/apps"); + + if !apps_dir.exists() { + fs::create_dir_all(&apps_dir).expect("Failed to create apps directory"); + fs::write(apps_dir.join("apps.json"), "[]").expect("Failed to create apps.json"); + } + + let apps_json = serde_json::from_str::>( + &fs::read_to_string(apps_dir.join("apps.json")).expect("Failed to read apps.json"), + ) + .expect("Failed to parse apps.json"); + + for app in apps_json.clone() { + if app.enabled.unwrap_or(true) { + println!("Running app: {}", app.repo); + std::thread::spawn(|| { + run_app(app); + }); + } else { + println!("Skipping disabled app: {}", app.repo); + } + } + + apps_json +} + +pub fn save_apps(apps: &[App]) { + let home = env::var("HOME").expect("Failed to get HOME environment variable"); + let apps_dir = PathBuf::from(home).join("sentryx/apps"); + + fs::write( + apps_dir.join("apps.json"), + serde_json::to_string(apps).expect("Failed to serialize apps"), + ) + .expect("Failed to write apps.json"); +} diff --git a/src/main.rs b/src/main.rs index 0274c39..565ab31 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,13 @@ +pub mod app_manager; pub mod server_config; -use std::net::TcpListener; use std::sync::Arc; use std::thread::spawn; +use std::{net::TcpListener, sync::Mutex}; use sysinfo::{Disks, Networks, System}; use tungstenite::accept; +use crate::app_manager::{install_app, App}; use crate::server_config::ServerStats; fn get_system_info(sys: &mut System, networks: &Networks, disks: &Disks) -> ServerStats { let cpus = sys.cpus(); @@ -36,19 +38,26 @@ fn get_system_info(sys: &mut System, networks: &Networks, disks: &Disks) -> Serv } fn main() { + let apps = app_manager::run_apps(); + let server = TcpListener::bind("127.0.0.1:5273").unwrap(); for stream in server.incoming() { + let mut apps = apps.clone(); + spawn(move || { - let mut websocket = accept(stream.unwrap()).unwrap(); + let websocket = Arc::new(Mutex::new(accept(stream.unwrap()).unwrap())); + let reader_socket = Arc::clone(&websocket); let config = Arc::new(server_config::ServerConfig::new()); { - let master_key = websocket.read().unwrap(); + let master_key = reader_socket.lock().unwrap().read().unwrap(); if !master_key.is_text() || master_key.to_text().unwrap() != config.master_key.clone() { websocket + .lock() + .unwrap() .send(tungstenite::Message::text("!Invalid master key")) .unwrap(); @@ -56,15 +65,32 @@ fn main() { } websocket + .lock() + .unwrap() .send(tungstenite::Message::text("Success")) .unwrap(); } + std::thread::spawn(move || loop { + let app: App = serde_json::from_str( + &reader_socket + .lock() + .unwrap() + .read() + .unwrap() + .to_text() + .unwrap(), + ) + .unwrap(); + + install_app(&mut apps, app); + }); + let mut sys = System::new_all(); let mut networks = Networks::new(); let mut disks = Disks::new(); - while websocket.can_write() { + while websocket.lock().unwrap().can_write() { sys.refresh_all(); std::thread::sleep(std::time::Duration::from_millis(200)); sys.refresh_all(); @@ -72,6 +98,8 @@ fn main() { disks.refresh(true); websocket + .lock() + .unwrap() .send(tungstenite::Message::text( serde_json::to_string(&get_system_info(&mut sys, &networks, &disks)) .unwrap(), diff --git a/test.js b/test.js index 0bfc98f..d909203 100644 --- a/test.js +++ b/test.js @@ -1,4 +1,14 @@ -const ws = new WebSocket('ws://localhost:5273'); +const ws = new WebSocket("ws://localhost:5273"); ws.onmessage = (msg) => console.log("Received:", msg.data); -ws.onopen = () => ws.send("my-key"); \ No newline at end of file +ws.onopen = () => { + ws.send("my-key"); + ws.send( + JSON.stringify({ + repo: "git@github.com:osui-rs/osui.git", + branch: "master", + install_command: "", + run_command: "cargo run", + }) + ); +};