From e162ef10a3ea6ca8242c9a58e223da50a7325b8b Mon Sep 17 00:00:00 2001 From: tomoron Date: Sat, 6 Jun 2026 17:20:15 +0200 Subject: [PATCH] add edit, ls and remove cli commands --- src/cli/add.rs | 2 +- src/cli/edit.rs | 46 +++++++++++++++++++++++++++++++- src/cli/env_settings.rs | 59 ++++++++++++++++++++++++++++++++--------- src/cli/ls.rs | 21 +++++++++++++++ src/cli/remove.rs | 30 ++++++++++++++++++++- src/main.rs | 5 +++- src/models.rs | 17 ++++++++++++ 7 files changed, 164 insertions(+), 16 deletions(-) diff --git a/src/cli/add.rs b/src/cli/add.rs index 8dfeba3..90ce649 100644 --- a/src/cli/add.rs +++ b/src/cli/add.rs @@ -46,7 +46,7 @@ pub async fn subcommand(arg: &ArgMatches, pool: DbPool) -> Result<(), String>{ create_dir(&path).await.map_err(|e| format!("failed to create the config folder, reason : {}", e))?; let mut file = File::create(path.to_string() + "env").await.map_err(|e| format!("failed to create .env file, reason {}", e))?; - file.write_all(env_settings::args_to_env(arg).as_bytes()).await.map_err(|e| format!("Failed to write to env file, reason : {}", e))?; + file.write_all(env_settings::args_to_env(arg, true).as_bytes()).await.map_err(|e| format!("Failed to write to env file, reason : {}", e))?; create_dir(path.to_string() + "server").await.map_err(|e| format!("failed to create server subfolder, reason: {}", e))?; diff --git a/src/cli/edit.rs b/src/cli/edit.rs index 47e2e2a..d0f1bfd 100644 --- a/src/cli/edit.rs +++ b/src/cli/edit.rs @@ -1,6 +1,50 @@ -use clap::Command; +use clap::{ArgMatches, Command, Arg}; +use diesel::result::Error::NotFound; + +use crate::cli::env_settings; +use crate::status::ServerStatus; + +use diesel::prelude::*; + +use crate::{Config, DbPool, schema, models}; + +use std::fs; +use std::io::Write; pub fn create_subcommand() -> Command { + let mut args = vec![Arg::new("NAME").required(true)]; + + args.extend(env_settings::get_args()); + Command::new("edit") .about("edit a server") + .args(args.as_slice()) +} + + +pub async fn subcommand(arguments: &ArgMatches, pool: DbPool) -> Result<(), String> { + use schema::servers::dsl::*; + let conn = &mut pool.get().unwrap(); + + let server = match servers.filter(name.eq(arguments.get_one::("NAME").unwrap())).select(models::Servers::as_select()).first(conn) { + Err(NotFound) => { return Err("this server does not exist".to_string()); }, + Err(e) => { return Err(format!("An error occured : {:?}", e)); }, + Ok(server) => server + }; + let path = Config::load().config_path + "/" + &server.name; + + if server.status == ServerStatus::Running { + eprintln!("warning: the server is currently running, changes will no be applied until the next start"); + } + + let prev_env = fs::read_to_string(path.to_string() + "/env").map_err(|e| format!("failed to read previous env file : {}", e))?; + + let new_env = env_settings::update_env(arguments, prev_env); + println!("\n\nnew env : {}", new_env); + + + let mut env_file = fs::OpenOptions::new().write(true).truncate(true).open(path + "/env").map_err(|e| format!("failed to reopen env file : {}" ,e))?; + env_file.write_all(new_env.as_bytes()).map_err(|e| format!("failed to write to file : {}", e))?; + + Ok(()) } diff --git a/src/cli/env_settings.rs b/src/cli/env_settings.rs index 3de6b0e..9c7e050 100644 --- a/src/cli/env_settings.rs +++ b/src/cli/env_settings.rs @@ -1,6 +1,8 @@ -use clap::{ Arg, ArgMatches, ArgAction}; +use std::collections::HashMap; -pub fn get_args() -> [Arg; 22] { +use clap::{ Arg, ArgAction, ArgMatches, parser::ValueSource}; + +pub fn get_args() -> [Arg; 21] { [ Arg::new("VERSION").short('v').long("version").help("Minecraft version of the server").default_value("LATEST"), Arg::new("MEMORY").short('m').long("memory").help("How much ram does this server have").default_value("4G"), @@ -14,7 +16,7 @@ pub fn get_args() -> [Arg; 22] { Arg::new("WHITELIST").short('w').long("whitelist").help("comma separated list of the playsrs to whitelist (disabled if not provied)"), Arg::new("OPS").short('O').long("ops").help("comma separated list of the players to give operator permissions to"), Arg::new("MODE").long("gamemode").default_value("survival"), - Arg::new("CUSTOM_SERVER_PROPERTIES").long("custom-property").help("add a custom property").action(ArgAction::Append), +// Arg::new("CUSTOM_SERVER_PROPERTIES").long("custom-property").help("add a custom property").action(ArgAction::Append), Arg::new("ALLOW_FLIGHT").short('f').long("alllow-flight").help("Allow flight in suvival mode").default_value("false"), Arg::new("ALLOW_NETHER").short('n').long("allow-nether").help("Enable the nether dimension").default_value("true"), Arg::new("HARDCORE").short('H').long("hardcore").help("Enable hardcore mode").default_value("false"), @@ -27,7 +29,7 @@ pub fn get_args() -> [Arg; 22] { ] } -pub fn args_to_env(arguments: &ArgMatches) -> String { +pub fn args_to_settings(arguments: &ArgMatches, default: bool) -> Vec<(String, String)> { let default_handle = vec!["VERSION", "MEMORY", "MAX_PLAYERS", "SPAWN_PROTECTION", "ONLINE_MODE", "TYPE", "MOTD", "DIFFICULTY", "OPS", "MODE", "ALLOW_FLIGHT", "ALLOW_NETHER", "HARDCORE", "MAX_TICK_TIME", "PAUSE_WHEN_EMPTY_SECONDS", "PVP", "SPAWN_ANIMALS", "SPAWN_MONSTERS", "SPAWN_NPCS"]; @@ -36,14 +38,20 @@ pub fn args_to_env(arguments: &ArgMatches) -> String { ]; for setting in default_handle { + if default == false && arguments.value_source(setting) == Some(ValueSource::DefaultValue) { + continue ; + } + if let Some(val) = arguments.get_one::(setting) { res.push((setting.to_string(), val.to_string())); } } if let Some(value) = arguments.get_one::("FORGE_VERSION") { - res.push(("FORGE_VERSION".to_string(), value.to_string())); - res.push(("NEOFORGE_VERSION".to_string(), value.to_string())); + if default == true || arguments.value_source("FORGE_VERSION") != Some(ValueSource::DefaultValue) { + res.push(("FORGE_VERSION".to_string(), value.to_string())); + res.push(("NEOFORGE_VERSION".to_string(), value.to_string())); + } } if let Some(value) = arguments.get_one::("WHITELIST") { @@ -51,15 +59,42 @@ pub fn args_to_env(arguments: &ArgMatches) -> String { res.push(("WHITELIST".to_string(),value.to_string())); } - if let Some(value) = arguments.get_many::("CUSTOM_SERVER_PROPERTIES") { - let properties = value.collect::>().iter().map(|x| x.to_string()).collect::>().join("\n"); - res.push(("CUSTOM_SERVER_PROPERTIES".to_string(), properties)); - } +// if let Some(value) = arguments.get_many::("CUSTOM_SERVER_PROPERTIES") { +// let properties = value.collect::>().iter().map(|x| x.to_string()).collect::>().join("\n"); +// res.push(("CUSTOM_SERVER_PROPERTIES".to_string(), properties)); +// } + res +} -// println!("\n\n\narguments : {:?}\nignored:{:?}", arguments, ignored); - res.iter() +pub fn settings_to_env(settings: Vec<(String, String)>) -> String { + settings.iter() .map(|x| format!("{}={}", x.0, x.1)) .collect::>().join("\n") } + +pub fn args_to_env(arguments: &ArgMatches, default: bool) -> String { + settings_to_env(args_to_settings(arguments, default)) +} + +pub fn env_to_settings(env: String) -> Vec<(String, String)> { + env.split("\n") + .filter_map(|s| s.split_once("=") + .map(|(x, y)| (x.to_string(), y.to_string()))) + .collect::>() +} + +pub fn update_env(arguments: &ArgMatches, prev_env: String) -> String{ + println!("env : {}" , prev_env); + let settings = env_to_settings(prev_env); + let new_settings = args_to_settings(arguments, false); + + let merged: Vec<(String, String)> = settings.into_iter() + .chain(new_settings.into_iter()) + .collect::>() + .into_iter() + .collect(); + settings_to_env(merged) + +} // START COMMAND : // docker run --rm --env-file ./env -u 1000:100 -it --volume $PWD/server:/data itzg/minecraft-server diff --git a/src/cli/ls.rs b/src/cli/ls.rs index c768314..83f96b6 100644 --- a/src/cli/ls.rs +++ b/src/cli/ls.rs @@ -1,6 +1,27 @@ +use clap::ArgMatches; use clap::Command; +use diesel::prelude::*; +use crate::DbPool; +use crate::schema; +use crate::models; + pub fn create_subcommand() -> Command { Command::new("ls") .about("list the servers") } + +pub async fn subcommand(_arguments: &ArgMatches, pool: DbPool) -> Result<(), String> { + use schema::servers::dsl::*; + + let conn = &mut pool.get().unwrap(); + + let list = servers.select(models::Servers::as_select()).load(conn).map_err(|_| "failed to get server list from db".to_string())?; + + + for server in list { + println!("{}", server); + } + + Ok(()) +} diff --git a/src/cli/remove.rs b/src/cli/remove.rs index 4d02025..5f785be 100644 --- a/src/cli/remove.rs +++ b/src/cli/remove.rs @@ -1,6 +1,34 @@ -use clap::Command; +use clap::{Arg, ArgMatches, Command}; + +use crate::{Config, DbPool}; + +use std::fs; + +use diesel::prelude::*; pub fn create_subcommand() -> Command { Command::new("remove") .about("remove a server") + .arg(Arg::new("NAME").required(true)) +} + +pub async fn subcommand(arguments: &ArgMatches, pool: DbPool) -> Result<(), String> { + use crate::schema::servers::dsl::*; + let server_name = arguments.get_one::("NAME").unwrap(); + + let config = Config::load(); + + let conn = &mut pool.get().unwrap(); + + //todo stop the server if it's started + + let deleted = diesel::delete(servers.filter(name.eq(server_name))).execute(conn).map_err(|_| "failed to delete from db".to_string())?; + if deleted != 1 { + return Err("servver was not found".to_string()); + } + + let path = config.config_path + "/" + server_name; + + fs::remove_dir_all(path).map_err(|e| format!("failed to remove the server directory, reason : {}", e))?; + Ok(()) } diff --git a/src/main.rs b/src/main.rs index ba6ed30..7299915 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ /* By: tomoron +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2026/05/29 21:22:17 by tomoron #+# #+# */ -/* Updated: 2026/06/05 17:48:58 by tomoron ### ########.fr */ +/* Updated: 2026/06/06 15:37:52 by tomoron ### ########.fr */ /* */ /* ************************************************************************** */ @@ -66,6 +66,9 @@ async fn main() -> Result<(), String> { match arg.subcommand() { Some(("serverMode", _)) => { mc_socket_listen(pool).await.map_err(|_| "socket error".to_string())?; }, Some(("add", sub_matches)) => { add::subcommand(sub_matches, pool).await?; }, + Some(("remove", sub_matches)) => { remove::subcommand(sub_matches, pool).await?; }, + Some(("ls", sub_matches)) => { ls::subcommand(sub_matches, pool).await?; }, + Some(("edit", sub_matches)) => { edit::subcommand(sub_matches, pool).await?; }, _ => {panic!("subcommand not implemented")} } Ok(()) diff --git a/src/models.rs b/src/models.rs index 90ebfd5..0ea1a14 100644 --- a/src/models.rs +++ b/src/models.rs @@ -1,5 +1,7 @@ +use std::fmt; use std::time::SystemTime; +use chrono::{DateTime, Utc}; use diesel::prelude::*; use serde::Deserialize; @@ -30,3 +32,18 @@ pub struct CreateServer<'a> { pub status: ServerStatus, pub redirect_ip: Option<&'a str> } + + +impl fmt::Display for Servers { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let login = match self.last_login { + Some(date) => { + let datetime: DateTime = date.into(); + + format!("{}", datetime.format("%d-%m-%Y at %H:%M").to_string()) + }, + None => { "never".to_string() } + }; + write!(f, "name: {} last_login: {} status: {}", self.name, login, self.status) + } +}