add edit, ls and remove cli commands

This commit is contained in:
2026-06-06 17:20:15 +02:00
parent 25d7a6ffd1
commit e162ef10a3
7 changed files with 164 additions and 16 deletions

View File

@ -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))?;

View File

@ -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::<String>("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(())
}

View File

@ -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,30 +38,63 @@ 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::<String>(setting) {
res.push((setting.to_string(), val.to_string()));
}
}
if let Some(value) = arguments.get_one::<String>("FORGE_VERSION") {
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::<String>("WHITELIST") {
res.push(("ENABLE_WHITELIST".to_string(), "true".to_string()));
res.push(("WHITELIST".to_string(),value.to_string()));
}
if let Some(value) = arguments.get_many::<String>("CUSTOM_SERVER_PROPERTIES") {
let properties = value.collect::<Vec<&String>>().iter().map(|x| x.to_string()).collect::<Vec<String>>().join("\n");
res.push(("CUSTOM_SERVER_PROPERTIES".to_string(), properties));
}
// if let Some(value) = arguments.get_many::<String>("CUSTOM_SERVER_PROPERTIES") {
// let properties = value.collect::<Vec<&String>>().iter().map(|x| x.to_string()).collect::<Vec<String>>().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::<Vec<String>>().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::<Vec<(String, String)>>()
}
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::<HashMap<String, String>>()
.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

View File

@ -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(())
}

View File

@ -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::<String>("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(())
}

View File

@ -6,7 +6,7 @@
/* By: tomoron <tomoron@student.42angouleme.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* 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(())

View File

@ -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<Utc> = 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)
}
}