archive server

This commit is contained in:
2026-06-12 18:27:55 +02:00
parent 6aa0ab473d
commit 97f3e4c139
11 changed files with 138 additions and 14 deletions

37
Cargo.lock generated
View File

@ -174,6 +174,42 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
] ]
[[package]]
name = "async-process"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75"
dependencies = [
"async-channel 2.5.0",
"async-io",
"async-lock",
"async-signal",
"async-task",
"blocking",
"cfg-if",
"event-listener 5.4.1",
"futures-lite",
"rustix",
]
[[package]]
name = "async-signal"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485"
dependencies = [
"async-io",
"async-lock",
"atomic-waker",
"cfg-if",
"futures-core",
"futures-io",
"rustix",
"signal-hook-registry",
"slab",
"windows-sys",
]
[[package]] [[package]]
name = "async-std" name = "async-std"
version = "1.13.2" version = "1.13.2"
@ -609,6 +645,7 @@ dependencies = [
name = "dockermcmgr" name = "dockermcmgr"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"async-process",
"async-std", "async-std",
"bollard", "bollard",
"chrono", "chrono",

View File

@ -8,6 +8,7 @@ name = "dmm"
path = "src/main.rs" path = "src/main.rs"
[dependencies] [dependencies]
async-process = "2.5.0"
async-std = "1.13.2" async-std = "1.13.2"
bollard = "0.21.0" bollard = "0.21.0"
chrono = "0.4.44" chrono = "0.4.44"

View File

@ -46,10 +46,13 @@
doCheck = false; doCheck = false;
buildInputs = # env.COMPRESSION_COMMAND="${pkgs.lrzip}/bin/lrzip";
with pkgs; [ sqlite ];
}
buildInputs = with pkgs; [
sqlite
lrzip
];
}
// specialArgs // specialArgs
); );
in in

View File

@ -10,7 +10,7 @@ pkgs.mkShell {
]; ];
PKG_CONFIG_PATH = "${pkgs.openssl.dev}/lib/pkgconfig"; PKG_CONFIG_PATH = "${pkgs.openssl.dev}/lib/pkgconfig";
COMPRESSION_COMMAND = "${pkgs.lrzip}/bin/lrzip";
shellHook = '' shellHook = ''
[ ! -f .env ] || export $(grep -v '^#' .env | xargs) [ ! -f .env ] || export $(grep -v '^#' .env | xargs)

69
src/cli/archive.rs Normal file
View File

@ -0,0 +1,69 @@
use bollard::Docker;
use clap::Arg;
use clap::{Command, ArgMatches};
use diesel::{SelectableHelper};
use diesel::result::Error::NotFound;
use crate::models::Servers;
use crate::status::ServerStatus;
use crate::{DbPool, schema, models};
use diesel::prelude::*;
use crate::dockermgr;
use async_process;
pub fn create_subcommand() -> Command {
Command::new("archive")
.about("archive a server")
.arg(Arg::new("NAME").required(true))
}
pub async fn subcommand(arguments: &ArgMatches, pool: &DbPool, docker: &Docker) -> Result<(), String> {
use schema::servers::dsl::*;
let server_name = arguments.get_one::<String>("NAME").unwrap();
let conn = &mut pool.get().unwrap();
let server = match servers.select(models::Servers::as_select()).filter(name.eq(server_name)).first(conn) {
Ok(server) => {server},
Err(NotFound) => { return Err("server not found".to_string()); },
Err(e) => { return Err(format!("Failed to fetch server from db : {}", e)); }
};
if server.status != ServerStatus::Running && server.status != ServerStatus::Stopped {
return Err(format!("invalid current server state : {}", server.status));
}
if server.status == ServerStatus::Running {
println!("Stopping container");
dockermgr::stop_server(&docker, pool, server_name).await?;
println!("container stopped");
}
archive_server(&server, pool).await?;
Ok(())
}
pub async fn archive_server(server: &Servers, pool: &DbPool) -> Result<(), String> {
use schema::servers::dsl::*;
let conn = &mut pool.get().unwrap();
let config = crate::Config::load();
diesel::update(servers).filter(name.eq(&server.name)).set(status.eq(ServerStatus::Archiving)).execute(conn).map_err(|e| format!("Failed to set the server as archiving :{}", e))?;
println!("Starting compression (this might take a while)");
let cmd = format!("cd {} && tar cf /dev/stdout {} | lrzip - -Q -o {}.tar.lrz", config.config_path, server.name, server.name);
let _ = async_process::Command::new("sh").arg("-c").arg(cmd).output().await.map_err(|e| format!("compression failed : {}", e))?;
std::fs::remove_dir_all(config.config_path.to_owned() + "/" + &server.name).map_err(|e| format!("Failed to delete server directory: {}", e))?;
diesel::update(servers).filter(name.eq(&server.name)).set(status.eq(ServerStatus::Archived)).execute(conn).map_err(|e| format!("Failed to set the server as achived :{}", e))?;
Ok(())
}

View File

@ -5,6 +5,7 @@ pub mod ls;
pub mod start; pub mod start;
pub mod stop; pub mod stop;
pub mod set_state; pub mod set_state;
pub mod archive;
pub mod env_settings; pub mod env_settings;

View File

@ -76,7 +76,7 @@ pub async fn start_server(docker: &Docker, pool: &DbPool, server_name: &String)
} }
if server.status != ServerStatus::Stopped { if server.status != ServerStatus::Stopped {
return Err(format!("Invalid server state({}), refusing to start", server.status)); return Err(format!("Invalid server state ({}), refusing to start", server.status));
} }
diesel::update(servers).filter(name.eq(server_name)).set(status.eq(ServerStatus::Starting)).execute(conn).map_err(|e| format!("Failed to set server to starting : {}", e))?; diesel::update(servers).filter(name.eq(server_name)).set(status.eq(ServerStatus::Starting)).execute(conn).map_err(|e| format!("Failed to set server to starting : {}", e))?;

View File

@ -6,7 +6,7 @@
/* By: tomoron <tomoron@student.42angouleme.fr> +#+ +:+ +#+ */ /* By: tomoron <tomoron@student.42angouleme.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */ /* +#+#+#+#+#+ +#+ */
/* Created: 2026/05/29 21:22:17 by tomoron #+# #+# */ /* Created: 2026/05/29 21:22:17 by tomoron #+# #+# */
/* Updated: 2026/06/12 12:02:28 by tomoron ### ########.fr */ /* Updated: 2026/06/12 17:03:13 by tomoron ### ########.fr */
/* */ /* */
/* ************************************************************************** */ /* ************************************************************************** */
@ -63,6 +63,7 @@ fn cli() -> Command {
.subcommand(set_state::create_subcommand()) .subcommand(set_state::create_subcommand())
.subcommand(start::create_subcommand()) .subcommand(start::create_subcommand())
.subcommand(stop::create_subcommand()) .subcommand(stop::create_subcommand())
.subcommand(archive::create_subcommand())
} }
#[tokio::main(flavor = "multi_thread")] #[tokio::main(flavor = "multi_thread")]
@ -80,10 +81,11 @@ async fn main() -> Result<(), String> {
Some(("remove", sub_matches)) => { remove::subcommand(sub_matches, &pool, &docker).await?; }, Some(("remove", sub_matches)) => { remove::subcommand(sub_matches, &pool, &docker).await?; },
Some(("ls", sub_matches)) => { ls::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?; }, Some(("edit", sub_matches)) => { edit::subcommand(sub_matches, pool).await?; },
Some(("setState", sub_matches)) => { set_state::subcommand(sub_matches, pool).await? } Some(("setState", sub_matches)) => { set_state::subcommand(sub_matches, pool).await? },
Some(("start", sub_matches)) => { start::subcommand(sub_matches, &pool, &docker).await?; }, Some(("start", sub_matches)) => { start::subcommand(sub_matches, &pool, &docker).await?; },
Some(("stop", sub_matches)) => { stop::subcommand(sub_matches, &pool, &docker).await?; }, Some(("stop", sub_matches)) => { stop::subcommand(sub_matches, &pool, &docker).await?; },
Some(("archive", sub_matches)) => { archive::subcommand(sub_matches, &pool, &docker).await? },
_ => {panic!("subcommand not implemented")} _ => {panic!("subcommand not implemented")}
} }
Ok(()) Ok(())

View File

@ -1,13 +1,10 @@
use crate::minecraft::client::client::Client; use crate::{dockermgr::start_server, minecraft::client::client::Client};
use diesel::result::Error::NotFound; use diesel::result::Error::NotFound;
use json::object; use json::object;
use crate::minecraft::varint::varint_write; use crate::minecraft::varint::varint_write;
use std::collections::VecDeque; use std::collections::VecDeque;
use crate::{ dockermgr };
impl Client { impl Client {
pub async fn login_intent_handle(&mut self, mut _packet: &mut VecDeque<u8>, _packet_id: i32) -> Result<(),String> { pub async fn login_intent_handle(&mut self, mut _packet: &mut VecDeque<u8>, _packet_id: i32) -> Result<(),String> {
@ -30,7 +27,8 @@ impl Client {
Err(e) => { return Err(format!("db fetch error : {}", e)); } Err(e) => { return Err(format!("db fetch error : {}", e)); }
}; };
dockermgr::start_server(&self.docker, &self.db_pool, &server.name).await?; start_server(&self.docker, &self.db_pool, &server.name).await?;
Ok(()) Ok(())
} }

View File

@ -1,7 +1,7 @@
use bollard::Docker; use bollard::Docker;
use chrono::{TimeDelta, Utc}; use chrono::{TimeDelta, Utc};
use crate::{ DbPool, dockermgr, models::{self, Servers}, schema, status::ServerStatus }; use crate::{ DbPool, cli::archive::archive_server, dockermgr, models::{self, Servers}, schema, status::ServerStatus };
use async_std::task; use async_std::task;
@ -45,6 +45,7 @@ pub async fn server_manager_loop(pool: DbPool, docker: Docker)
for srv in a_servers { for srv in a_servers {
let time_diff = Utc::now().naive_utc() - srv.last_login.unwrap(); let time_diff = Utc::now().naive_utc() - srv.last_login.unwrap();
should_stop(&docker, &pool, &time_diff, &srv).await; should_stop(&docker, &pool, &time_diff, &srv).await;
should_archive(&docker, &pool, &time_diff, &srv).await;
check_still_online(&docker, &pool, &srv).await; check_still_online(&docker, &pool, &srv).await;
} }
task::sleep(Duration::from_secs(30)).await; task::sleep(Duration::from_secs(30)).await;
@ -61,6 +62,16 @@ async fn should_stop(docker: &Docker, pool: &DbPool, diff: &TimeDelta, srv: &Ser
} }
} }
async fn should_archive(_docker: &Docker, pool: &DbPool, diff: &TimeDelta, srv: &Servers) {
if srv.status != ServerStatus::Stopped { return ; }
if diff.num_days() >= 30 {
println!("last login time exceedd, archiving {}", srv.name);
let _ = archive_server(srv, pool);
println!("{} archived", srv.name);
}
}
async fn check_still_online(docker: &Docker, pool: &DbPool, srv: &Servers) { async fn check_still_online(docker: &Docker, pool: &DbPool, srv: &Servers) {
use schema::servers::dsl::*; use schema::servers::dsl::*;

View File

@ -16,7 +16,8 @@ pub enum ServerStatus {
Stopping = 2, Stopping = 2,
Starting = 3, Starting = 3,
Running = 4, Running = 4,
Unknown = 5, Archiving = 5,
Unknown,
} }
impl FromSql<SmallInt, Sqlite> for ServerStatus{ impl FromSql<SmallInt, Sqlite> for ServerStatus{
@ -42,6 +43,7 @@ impl fmt::Display for ServerStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let str_status = match self { let str_status = match self {
ServerStatus::Archived => "archived", ServerStatus::Archived => "archived",
ServerStatus::Archiving => "archiving",
ServerStatus::Stopped => "stopped", ServerStatus::Stopped => "stopped",
ServerStatus::Stopping => "stopping", ServerStatus::Stopping => "stopping",
ServerStatus::Starting => "starting", ServerStatus::Starting => "starting",