ok
This commit is contained in:
Generated
+137
@@ -266,6 +266,29 @@ dependencies = [
|
|||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bon"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "97493a391b4b18ee918675fb8663e53646fd09321c58b46afa04e8ce2499c869"
|
||||||
|
dependencies = [
|
||||||
|
"bon-macros",
|
||||||
|
"rustversion",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bon-macros"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2a2af3eac944c12cdf4423eab70d310da0a8e5851a18ffb192c0a5e3f7ae1663"
|
||||||
|
dependencies = [
|
||||||
|
"darling",
|
||||||
|
"ident_case",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "brotli"
|
name = "brotli"
|
||||||
version = "8.0.1"
|
version = "8.0.1"
|
||||||
@@ -358,6 +381,41 @@ dependencies = [
|
|||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling"
|
||||||
|
version = "0.20.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core",
|
||||||
|
"darling_macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_core"
|
||||||
|
version = "0.20.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
|
||||||
|
dependencies = [
|
||||||
|
"fnv",
|
||||||
|
"ident_case",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"strsim",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_macro"
|
||||||
|
version = "0.20.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "data-encoding"
|
name = "data-encoding"
|
||||||
version = "2.9.0"
|
version = "2.9.0"
|
||||||
@@ -415,6 +473,12 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.35"
|
version = "0.8.35"
|
||||||
@@ -544,6 +608,12 @@ version = "0.15.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
|
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "0.2.12"
|
version = "0.2.12"
|
||||||
@@ -664,6 +734,12 @@ dependencies = [
|
|||||||
"zerovec",
|
"zerovec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ident_case"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
@@ -712,6 +788,15 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.15"
|
version = "1.0.15"
|
||||||
@@ -734,6 +819,12 @@ version = "0.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388"
|
checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.174"
|
version = "0.2.174"
|
||||||
@@ -1035,6 +1126,12 @@ version = "0.1.25"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f"
|
checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.20"
|
version = "1.0.20"
|
||||||
@@ -1052,9 +1149,12 @@ name = "sentryx-server"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-web",
|
"actix-web",
|
||||||
|
"lazy_static",
|
||||||
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sysinfo",
|
"sysinfo",
|
||||||
|
"systemctl",
|
||||||
"tungstenite",
|
"tungstenite",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1156,6 +1256,31 @@ version = "1.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum"
|
||||||
|
version = "0.26.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum_macros"
|
||||||
|
version = "0.26.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rustversion",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.104"
|
version = "2.0.104"
|
||||||
@@ -1192,6 +1317,18 @@ dependencies = [
|
|||||||
"windows",
|
"windows",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "systemctl"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1c8ce0ab2b5159ff3bd1ad03ac4f84d406438f1c42f1e83c0698b1c8041365ee"
|
||||||
|
dependencies = [
|
||||||
|
"bon",
|
||||||
|
"itertools",
|
||||||
|
"strum",
|
||||||
|
"strum_macros",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "2.0.12"
|
version = "2.0.12"
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = "4.11.0"
|
actix-web = "4.11.0"
|
||||||
|
lazy_static = "1.5.0"
|
||||||
|
regex = "1.11.1"
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
serde_json = "1.0.140"
|
serde_json = "1.0.140"
|
||||||
sysinfo = "0.36.0"
|
sysinfo = "0.36.0"
|
||||||
|
systemctl = "0.5.0"
|
||||||
tungstenite = "0.27.0"
|
tungstenite = "0.27.0"
|
||||||
|
|||||||
@@ -1,267 +0,0 @@
|
|||||||
use std::env;
|
|
||||||
use std::fs;
|
|
||||||
use std::fs::remove_file;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct App {
|
|
||||||
pub repo: String,
|
|
||||||
pub branch: String,
|
|
||||||
pub install_command: String,
|
|
||||||
pub run_command: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl App {
|
|
||||||
/// Converts URL to "user/repo"
|
|
||||||
pub fn id(&self) -> Option<String> {
|
|
||||||
let url = self.repo.trim_end_matches(".git");
|
|
||||||
|
|
||||||
if url.starts_with("git@") {
|
|
||||||
// Format: [email protected]:usr/repo
|
|
||||||
url.split_once(':').map(|(_, path)| path.to_string())
|
|
||||||
} else if url.starts_with("https://") || url.starts_with("http://") {
|
|
||||||
// Format: https://github.com/usr/repo
|
|
||||||
url.split_once("github.com/")
|
|
||||||
.map(|(_, path)| path.to_string())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts URL to "user-repo"
|
|
||||||
pub fn id_system(&self) -> Option<String> {
|
|
||||||
let url = self.repo.trim_end_matches(".git");
|
|
||||||
|
|
||||||
let repo_path = if url.starts_with("git@") {
|
|
||||||
url.split_once(':').map(|(_, path)| path)
|
|
||||||
} else if url.starts_with("https://") || url.starts_with("http://") {
|
|
||||||
url.split_once("github.com/").map(|(_, path)| path)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}?;
|
|
||||||
|
|
||||||
Some(repo_path.replace('/', "-"))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn repo_folder_name(&self) -> String {
|
|
||||||
self.repo
|
|
||||||
.trim_end_matches(".git")
|
|
||||||
.rsplit('/')
|
|
||||||
.next()
|
|
||||||
.unwrap_or("unknown")
|
|
||||||
.to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn home_dir() -> PathBuf {
|
|
||||||
let home = env::var("HOME").expect("Failed to get HOME environment variable");
|
|
||||||
PathBuf::from(home)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apps_dir() -> PathBuf {
|
|
||||||
let home = env::var("HOME").expect("Failed to get HOME environment variable");
|
|
||||||
PathBuf::from(home).join("sentryx/apps")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apps_file_path() -> PathBuf {
|
|
||||||
apps_dir().join("apps.json")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_systemd() {
|
|
||||||
Command::new("systemctl")
|
|
||||||
.arg("--user")
|
|
||||||
.arg("daemon-reload")
|
|
||||||
.status()
|
|
||||||
.expect("Failed to reload systemd daemon");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init() -> Vec<App> {
|
|
||||||
let path = apps_file_path();
|
|
||||||
|
|
||||||
if !path.exists() {
|
|
||||||
let _ = fs::create_dir_all(path.parent().unwrap());
|
|
||||||
let _ = fs::write(&path, "[]");
|
|
||||||
}
|
|
||||||
|
|
||||||
let content = fs::read_to_string(&path).unwrap_or_else(|_| "[]".to_string());
|
|
||||||
serde_json::from_str(&content).unwrap_or_else(|_| vec![])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn save_apps(apps: &[App]) {
|
|
||||||
let path = apps_file_path();
|
|
||||||
let json = serde_json::to_string_pretty(apps).expect("Failed to serialize apps");
|
|
||||||
|
|
||||||
if let Some(parent) = path.parent() {
|
|
||||||
let _ = fs::create_dir_all(parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
fs::write(path, json).expect("Failed to write apps.json");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn uninstall_app(apps: &mut Vec<App>, index: usize) {
|
|
||||||
if index >= apps.len() {
|
|
||||||
eprintln!("Invalid app index: {}", index);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let app = &apps[index];
|
|
||||||
let repo_dir = apps_dir().join(app.repo_folder_name());
|
|
||||||
|
|
||||||
if repo_dir.exists() {
|
|
||||||
if let Err(e) = fs::remove_dir_all(&repo_dir) {
|
|
||||||
eprintln!("Failed to remove directory {:?}: {}", repo_dir, e);
|
|
||||||
} else {
|
|
||||||
println!("Removed app: {}", app.repo_folder_name());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let user_systemd_dir = home_dir().join(".config/systemd/user");
|
|
||||||
|
|
||||||
let service_path = user_systemd_dir.join(format!(
|
|
||||||
"{}.service",
|
|
||||||
app.id_system().expect("Unable to obtain id")
|
|
||||||
));
|
|
||||||
|
|
||||||
println!("Removing service file: {:?}", service_path);
|
|
||||||
|
|
||||||
remove_file(service_path).unwrap_or_else(|e| {
|
|
||||||
eprintln!("Failed to remove service file: {}", e);
|
|
||||||
});
|
|
||||||
|
|
||||||
update_systemd();
|
|
||||||
|
|
||||||
apps.remove(index);
|
|
||||||
save_apps(apps);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn install_app(apps: &mut Vec<App>, app: App) {
|
|
||||||
let path = apps_dir();
|
|
||||||
let repo_name = app.repo_folder_name();
|
|
||||||
|
|
||||||
// Ensure apps directory exists
|
|
||||||
if let Err(e) = fs::create_dir_all(&path) {
|
|
||||||
eprintln!("Failed to create apps directory: {}", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(e) = env::set_current_dir(&path) {
|
|
||||||
eprintln!("Failed to switch to apps directory: {}", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("Installing app: {}", app.id().expect("Unable to obtain id"));
|
|
||||||
println!("→ Cloning branch `{}` from `{}`", app.branch, app.repo);
|
|
||||||
|
|
||||||
let clone_status = Command::new("git")
|
|
||||||
.arg("clone")
|
|
||||||
.arg("-b")
|
|
||||||
.arg(&app.branch)
|
|
||||||
.arg(&app.repo)
|
|
||||||
.status();
|
|
||||||
|
|
||||||
if !matches!(clone_status, Ok(s) if s.success()) {
|
|
||||||
eprintln!("Failed to clone repository");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let repo_path = path.join(&repo_name);
|
|
||||||
if let Err(e) = env::set_current_dir(&repo_path) {
|
|
||||||
eprintln!("Failed to change into cloned repo directory: {}", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
println!(
|
|
||||||
"→ Running install command in {:?}",
|
|
||||||
env::current_dir().unwrap()
|
|
||||||
);
|
|
||||||
|
|
||||||
let install_status = Command::new("bash")
|
|
||||||
.arg("-c")
|
|
||||||
.arg(&app.install_command)
|
|
||||||
.status();
|
|
||||||
|
|
||||||
if !matches!(install_status, Ok(s) if s.success()) {
|
|
||||||
eprintln!("Install command failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
let service_contents = format!(
|
|
||||||
"[Unit]\nDescription={repo_name} (Installed with SentryX)\nAfter=network.target\n\n[Service]\nWorkingDirectory=/home/server/saturn-server\nExecStart={}\nRestart=always\nRestartSec=5\nStandardOutput=journal\nStandardError=journal\n\n[Install]\nWantedBy=multi-user.target",
|
|
||||||
app.run_command,
|
|
||||||
);
|
|
||||||
|
|
||||||
let user_systemd_dir = home_dir().join(".config/systemd/user");
|
|
||||||
|
|
||||||
fs::create_dir_all(&user_systemd_dir).expect("Failed to create user systemd dir");
|
|
||||||
|
|
||||||
let service_path = user_systemd_dir.join(format!(
|
|
||||||
"{}.service",
|
|
||||||
app.id_system().expect("Unable to obtain id")
|
|
||||||
));
|
|
||||||
|
|
||||||
println!("→ Creating service file: {service_path:?}");
|
|
||||||
|
|
||||||
if let Err(e) = fs::write(&service_path, service_contents) {
|
|
||||||
eprintln!(
|
|
||||||
"Failed to write service file, make sure you run this server with sudo permissions: {}",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
update_systemd();
|
|
||||||
|
|
||||||
// Add to app list and save
|
|
||||||
apps.push(app);
|
|
||||||
save_apps(apps);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn toggle_app_state(apps: &[App], index: usize, enable: bool) {
|
|
||||||
if let Some(app) = apps.get(index) {
|
|
||||||
let id = app.id_system().expect("Unable to obtain id");
|
|
||||||
Command::new("systemctl")
|
|
||||||
.arg("--user")
|
|
||||||
.arg(if enable { "enable" } else { "disable" })
|
|
||||||
.arg(format!("{}.service", id))
|
|
||||||
.status()
|
|
||||||
.expect("Failed to toggle app state");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start_app(apps: &[App], index: usize) {
|
|
||||||
if let Some(app) = apps.get(index) {
|
|
||||||
let id = app.id_system().expect("Unable to obtain id");
|
|
||||||
Command::new("systemctl")
|
|
||||||
.arg("--user")
|
|
||||||
.arg("start")
|
|
||||||
.arg(format!("{}.service", id))
|
|
||||||
.status()
|
|
||||||
.expect("Failed to start app");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stop_app(apps: &[App], index: usize) {
|
|
||||||
if let Some(app) = apps.get(index) {
|
|
||||||
let id = app.id_system().expect("Unable to obtain id");
|
|
||||||
Command::new("systemctl")
|
|
||||||
.arg("--user")
|
|
||||||
.arg("stop")
|
|
||||||
.arg(format!("{}.service", id))
|
|
||||||
.status()
|
|
||||||
.expect("Failed to stop app");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn restart_app(apps: &[App], index: usize) {
|
|
||||||
if let Some(app) = apps.get(index) {
|
|
||||||
let id = app.id_system().expect("Unable to obtain id");
|
|
||||||
Command::new("systemctl")
|
|
||||||
.arg("--user")
|
|
||||||
.arg("restart")
|
|
||||||
.arg(format!("{}.service", id))
|
|
||||||
.status()
|
|
||||||
.expect("Failed to restart app");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+139
-13
@@ -1,12 +1,26 @@
|
|||||||
mod app_manager;
|
|
||||||
mod server_config;
|
mod server_config;
|
||||||
use actix_web::{get, post, web, App, HttpServer, Responder, Result};
|
mod service;
|
||||||
|
use actix_web::{
|
||||||
|
get,
|
||||||
|
web::{self},
|
||||||
|
App, HttpServer, Responder, Result,
|
||||||
|
};
|
||||||
|
use serde_json::json;
|
||||||
use sysinfo::{Disks, Networks, System};
|
use sysinfo::{Disks, Networks, System};
|
||||||
|
|
||||||
use crate::server_config::ServerStats;
|
use crate::server_config::{validate_master_key, ServerStats};
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct Auth {
|
||||||
|
auth: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
async fn sys_info() -> Result<impl Responder> {
|
async fn sys_info(query: web::Query<Auth>) -> Result<impl Responder> {
|
||||||
|
if !validate_master_key(&query.auth) {
|
||||||
|
return Err(actix_web::error::ErrorUnauthorized("Invalid master key"));
|
||||||
|
}
|
||||||
|
|
||||||
let mut sys = System::new_all();
|
let mut sys = System::new_all();
|
||||||
let mut disks: Disks = Disks::new_with_refreshed_list();
|
let mut disks: Disks = Disks::new_with_refreshed_list();
|
||||||
let mut networks: Networks = Networks::new_with_refreshed_list();
|
let mut networks: Networks = Networks::new_with_refreshed_list();
|
||||||
@@ -48,17 +62,124 @@ async fn sys_info() -> Result<impl Responder> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/apps")]
|
#[get("/apps")]
|
||||||
async fn get_apps() -> Result<impl Responder> {
|
async fn get_apps(query: web::Query<Auth>) -> Result<impl Responder> {
|
||||||
let apps = app_manager::init();
|
if !validate_master_key(&query.auth) {
|
||||||
Ok(web::Json(apps))
|
return Err(actix_web::error::ErrorUnauthorized("Invalid master key"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let systemctl = systemctl::SystemCtl::default();
|
||||||
|
match systemctl.list_units(None, None, None) {
|
||||||
|
Ok(units) => Ok(web::Json(
|
||||||
|
units
|
||||||
|
.iter()
|
||||||
|
.map(|v| match systemctl.status(v) {
|
||||||
|
Ok(unit) => serde_json::to_value(service::parse_service(unit)).unwrap(),
|
||||||
|
Err(_) => json!({ "name": v, "status": "undefined" }),
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
)),
|
||||||
|
Err(e) => Err(actix_web::error::ErrorInternalServerError(format!(
|
||||||
|
"Failed to list units: {}",
|
||||||
|
e
|
||||||
|
))),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/app/{app_index}")]
|
#[get("/apps/{app}")]
|
||||||
async fn get_app(id: web::Path<usize>) -> Result<impl Responder> {
|
async fn get_app(id: web::Path<String>, query: web::Query<Auth>) -> Result<impl Responder> {
|
||||||
let apps = app_manager::init();
|
if !validate_master_key(&query.auth) {
|
||||||
match apps.get(*id) {
|
return Err(actix_web::error::ErrorUnauthorized("Invalid master key"));
|
||||||
Some(app) => Ok(web::Json(app.clone())),
|
}
|
||||||
None => Err(actix_web::error::ErrorNotFound("App not found")),
|
|
||||||
|
let systemctl = systemctl::SystemCtl::default();
|
||||||
|
match systemctl.status(&*id) {
|
||||||
|
Ok(unit) => Ok(web::Json(
|
||||||
|
serde_json::to_value(service::parse_service(unit)).unwrap(),
|
||||||
|
)),
|
||||||
|
Err(e) => Err(actix_web::error::ErrorInternalServerError(format!(
|
||||||
|
"Failed to get unit: {}",
|
||||||
|
e
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/enable/{app}")]
|
||||||
|
async fn enable_app(id: web::Path<String>, query: web::Query<Auth>) -> Result<impl Responder> {
|
||||||
|
if !validate_master_key(&query.auth) {
|
||||||
|
return Err(actix_web::error::ErrorUnauthorized("Invalid master key"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let systemctl = systemctl::SystemCtl::default();
|
||||||
|
match systemctl.enable(&*id) {
|
||||||
|
Ok(status) => Ok(web::Json(json!({ "success": status.success() }))),
|
||||||
|
Err(e) => Err(actix_web::error::ErrorInternalServerError(format!(
|
||||||
|
"Failed to enable unit: {}",
|
||||||
|
e
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/disable/{app}")]
|
||||||
|
async fn disable_app(id: web::Path<String>, query: web::Query<Auth>) -> Result<impl Responder> {
|
||||||
|
if !validate_master_key(&query.auth) {
|
||||||
|
return Err(actix_web::error::ErrorUnauthorized("Invalid master key"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let systemctl = systemctl::SystemCtl::default();
|
||||||
|
match systemctl.disable(&*id) {
|
||||||
|
Ok(status) => Ok(web::Json(json!({ "success": status.success() }))),
|
||||||
|
Err(e) => Err(actix_web::error::ErrorInternalServerError(format!(
|
||||||
|
"Failed to disable unit: {}",
|
||||||
|
e
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/start/{app}")]
|
||||||
|
async fn start_app(id: web::Path<String>, query: web::Query<Auth>) -> Result<impl Responder> {
|
||||||
|
if !validate_master_key(&query.auth) {
|
||||||
|
return Err(actix_web::error::ErrorUnauthorized("Invalid master key"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let systemctl = systemctl::SystemCtl::default();
|
||||||
|
match systemctl.start(&*id) {
|
||||||
|
Ok(status) => Ok(web::Json(json!({ "success": status.success() }))),
|
||||||
|
Err(e) => Err(actix_web::error::ErrorInternalServerError(format!(
|
||||||
|
"Failed to start unit: {}",
|
||||||
|
e
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/stop/{app}")]
|
||||||
|
async fn stop_app(id: web::Path<String>, query: web::Query<Auth>) -> Result<impl Responder> {
|
||||||
|
if !validate_master_key(&query.auth) {
|
||||||
|
return Err(actix_web::error::ErrorUnauthorized("Invalid master key"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let systemctl = systemctl::SystemCtl::default();
|
||||||
|
match systemctl.stop(&*id) {
|
||||||
|
Ok(status) => Ok(web::Json(json!({ "success": status.success() }))),
|
||||||
|
Err(e) => Err(actix_web::error::ErrorInternalServerError(format!(
|
||||||
|
"Failed to stop unit: {}",
|
||||||
|
e
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/restart/{app}")]
|
||||||
|
async fn restart_app(id: web::Path<String>, query: web::Query<Auth>) -> Result<impl Responder> {
|
||||||
|
if !validate_master_key(&query.auth) {
|
||||||
|
return Err(actix_web::error::ErrorUnauthorized("Invalid master key"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let systemctl = systemctl::SystemCtl::default();
|
||||||
|
match systemctl.restart(&*id) {
|
||||||
|
Ok(status) => Ok(web::Json(json!({ "success": status.success() }))),
|
||||||
|
Err(e) => Err(actix_web::error::ErrorInternalServerError(format!(
|
||||||
|
"Failed to restart unit: {}",
|
||||||
|
e
|
||||||
|
))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,6 +190,11 @@ async fn main() -> std::io::Result<()> {
|
|||||||
.service(sys_info)
|
.service(sys_info)
|
||||||
.service(get_apps)
|
.service(get_apps)
|
||||||
.service(get_app)
|
.service(get_app)
|
||||||
|
.service(enable_app)
|
||||||
|
.service(disable_app)
|
||||||
|
.service(start_app)
|
||||||
|
.service(stop_app)
|
||||||
|
.service(restart_app)
|
||||||
})
|
})
|
||||||
.bind(("127.0.0.1", 8080))?
|
.bind(("127.0.0.1", 8080))?
|
||||||
.run()
|
.run()
|
||||||
|
|||||||
@@ -22,3 +22,8 @@ pub struct ServerStats {
|
|||||||
pub disk: u8,
|
pub disk: u8,
|
||||||
pub network: u64,
|
pub network: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn validate_master_key(master_key: &str) -> bool {
|
||||||
|
let config = ServerConfig::new();
|
||||||
|
master_key == config.master_key
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
use lazy_static::lazy_static;
|
||||||
|
use regex::Regex;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct Service {
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub loaded_status: Option<String>,
|
||||||
|
pub unit_file_path: Option<String>,
|
||||||
|
pub enabled: Option<String>,
|
||||||
|
pub preset: Option<String>,
|
||||||
|
pub active: Option<String>,
|
||||||
|
pub active_state: Option<String>,
|
||||||
|
pub trigger: Option<String>,
|
||||||
|
pub triggers: Option<String>,
|
||||||
|
pub docs: Option<String>,
|
||||||
|
// Other fields can be added as needed
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref SERVICE_RE: Regex = Regex::new(
|
||||||
|
r#"(?x)
|
||||||
|
^●\s(?P<name>\S+)\s-\s(?P<description>.+?)\n
|
||||||
|
\s+Loaded:\s(?P<loaded_status>\S+)\s\((?P<unit_file_path>[^;]+)(?:;\s(?P<enabled>\S+))?(?:;\s+preset:\s(?P<preset>\S+))?\)\n
|
||||||
|
\s+Active:\s(?P<active>\S+)\s\((?P<active_state>[^)]+)\).*\n
|
||||||
|
(?:\s+Trigger:\s(?P<trigger>.+)\n)?
|
||||||
|
(?:\s+Triggers:\s●\s(?P<triggers>[^\n]+)\n)?
|
||||||
|
(?:\s+Docs:\s(?P<docs>(?:.+\n(?:\s{7,}.+\n)*))?)?
|
||||||
|
"#
|
||||||
|
).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_service(input: String) -> Service {
|
||||||
|
if let Some(cap) = SERVICE_RE.captures(&input) {
|
||||||
|
Service {
|
||||||
|
name: cap.name("name").map(|m| m.as_str().to_string()),
|
||||||
|
description: cap.name("description").map(|m| m.as_str().to_string()),
|
||||||
|
loaded_status: cap.name("loaded_status").map(|m| m.as_str().to_string()),
|
||||||
|
unit_file_path: cap.name("unit_file_path").map(|m| m.as_str().to_string()),
|
||||||
|
enabled: cap.name("enabled").map(|m| m.as_str().to_string()),
|
||||||
|
preset: cap.name("preset").map(|m| m.as_str().to_string()),
|
||||||
|
active: cap.name("active").map(|m| m.as_str().to_string()),
|
||||||
|
active_state: cap.name("active_state").map(|m| m.as_str().to_string()),
|
||||||
|
trigger: cap.name("trigger").map(|m| m.as_str().to_string()),
|
||||||
|
triggers: cap.name("triggers").map(|m| m.as_str().to_string()),
|
||||||
|
docs: cap.name("docs").map(|m| m.as_str().trim().to_string()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Return an empty Service or handle error as needed
|
||||||
|
Service {
|
||||||
|
name: None,
|
||||||
|
description: None,
|
||||||
|
loaded_status: None,
|
||||||
|
unit_file_path: None,
|
||||||
|
enabled: None,
|
||||||
|
preset: None,
|
||||||
|
active: None,
|
||||||
|
active_state: None,
|
||||||
|
trigger: None,
|
||||||
|
triggers: None,
|
||||||
|
docs: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
const ws = new WebSocket("ws://localhost:5273");
|
|
||||||
|
|
||||||
/*
|
|
||||||
pub enum Method {
|
|
||||||
Install(App),
|
|
||||||
SetEnabled(String, bool),
|
|
||||||
Edit(String, App),
|
|
||||||
Uninstall(String),
|
|
||||||
Toggle(String, bool),
|
|
||||||
Start(String),
|
|
||||||
Stop(String),
|
|
||||||
Restart(String),
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
ws.onmessage = (msg) => console.log("Received:", msg.data);
|
|
||||||
ws.onopen = () => {
|
|
||||||
ws.send("my-key");
|
|
||||||
|
|
||||||
// ws.send(
|
|
||||||
// JSON.stringify({
|
|
||||||
// Install: {
|
|
||||||
// repo: "[email protected]:osui-rs/osui.git",
|
|
||||||
// branch: "master",
|
|
||||||
// install_command: "",
|
|
||||||
// run_command: "cargo run",
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// );
|
|
||||||
|
|
||||||
// ws.send(
|
|
||||||
// JSON.stringify({
|
|
||||||
// SetEnabled: ["osui-rs/osui", true]
|
|
||||||
// })
|
|
||||||
// );
|
|
||||||
|
|
||||||
ws.send(
|
|
||||||
JSON.stringify({
|
|
||||||
Start: 0
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// ws.send(
|
|
||||||
// JSON.stringify({
|
|
||||||
// Uninstall: "[email protected]:osui-rs/osui.git"
|
|
||||||
// })
|
|
||||||
// );
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user