From 97f3e4c1393afbc2818cf80dff351e0810b0a862 Mon Sep 17 00:00:00 2001 From: tomoron Date: Fri, 12 Jun 2026 18:27:55 +0200 Subject: [PATCH] archive server --- Cargo.lock | 37 +++++++++++++++++++ Cargo.toml | 1 + flake.nix | 9 +++-- shell.nix | 2 +- src/cli/archive.rs | 69 +++++++++++++++++++++++++++++++++++ src/cli/mod.rs | 1 + src/dockermgr/start.rs | 2 +- src/main.rs | 6 ++- src/minecraft/client/login.rs | 8 ++-- src/minecraft/manager_loop.rs | 13 ++++++- src/status.rs | 4 +- 11 files changed, 138 insertions(+), 14 deletions(-) create mode 100644 src/cli/archive.rs diff --git a/Cargo.lock b/Cargo.lock index 53d7770..999a3e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -174,6 +174,42 @@ dependencies = [ "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]] name = "async-std" version = "1.13.2" @@ -609,6 +645,7 @@ dependencies = [ name = "dockermcmgr" version = "0.0.6" dependencies = [ + "async-process", "async-std", "bollard", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 1542d7d..f69cd5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ name = "dmm" path = "src/main.rs" [dependencies] +async-process = "2.5.0" async-std = "1.13.2" bollard = "0.21.0" chrono = "0.4.44" diff --git a/flake.nix b/flake.nix index ae85905..9ef81e6 100644 --- a/flake.nix +++ b/flake.nix @@ -46,10 +46,13 @@ doCheck = false; - buildInputs = - with pkgs; [ sqlite ]; +# env.COMPRESSION_COMMAND="${pkgs.lrzip}/bin/lrzip"; + + buildInputs = with pkgs; [ + sqlite + lrzip + ]; } - // specialArgs ); in diff --git a/shell.nix b/shell.nix index 1bca4e3..346c4e4 100644 --- a/shell.nix +++ b/shell.nix @@ -10,7 +10,7 @@ pkgs.mkShell { ]; PKG_CONFIG_PATH = "${pkgs.openssl.dev}/lib/pkgconfig"; - + COMPRESSION_COMMAND = "${pkgs.lrzip}/bin/lrzip"; shellHook = '' [ ! -f .env ] || export $(grep -v '^#' .env | xargs) diff --git a/src/cli/archive.rs b/src/cli/archive.rs new file mode 100644 index 0000000..02200cf --- /dev/null +++ b/src/cli/archive.rs @@ -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::("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(()) +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index e0e6e07..b764368 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -5,6 +5,7 @@ pub mod ls; pub mod start; pub mod stop; pub mod set_state; +pub mod archive; pub mod env_settings; diff --git a/src/dockermgr/start.rs b/src/dockermgr/start.rs index 4b2e25c..5cb8cfe 100644 --- a/src/dockermgr/start.rs +++ b/src/dockermgr/start.rs @@ -76,7 +76,7 @@ pub async fn start_server(docker: &Docker, pool: &DbPool, server_name: &String) } 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))?; diff --git a/src/main.rs b/src/main.rs index 4b606f4..8527e8f 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/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(start::create_subcommand()) .subcommand(stop::create_subcommand()) + .subcommand(archive::create_subcommand()) } #[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(("ls", sub_matches)) => { ls::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(("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")} } Ok(()) diff --git a/src/minecraft/client/login.rs b/src/minecraft/client/login.rs index ca43c15..5278eea 100644 --- a/src/minecraft/client/login.rs +++ b/src/minecraft/client/login.rs @@ -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 json::object; use crate::minecraft::varint::varint_write; use std::collections::VecDeque; -use crate::{ dockermgr }; - - impl Client { pub async fn login_intent_handle(&mut self, mut _packet: &mut VecDeque, _packet_id: i32) -> Result<(),String> { @@ -30,7 +27,8 @@ impl Client { 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(()) } diff --git a/src/minecraft/manager_loop.rs b/src/minecraft/manager_loop.rs index 0ba3fca..7edcee7 100644 --- a/src/minecraft/manager_loop.rs +++ b/src/minecraft/manager_loop.rs @@ -1,7 +1,7 @@ use bollard::Docker; 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; @@ -45,6 +45,7 @@ pub async fn server_manager_loop(pool: DbPool, docker: Docker) for srv in a_servers { let time_diff = Utc::now().naive_utc() - srv.last_login.unwrap(); should_stop(&docker, &pool, &time_diff, &srv).await; + should_archive(&docker, &pool, &time_diff, &srv).await; check_still_online(&docker, &pool, &srv).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) { use schema::servers::dsl::*; diff --git a/src/status.rs b/src/status.rs index a904948..2d0f8e2 100644 --- a/src/status.rs +++ b/src/status.rs @@ -16,7 +16,8 @@ pub enum ServerStatus { Stopping = 2, Starting = 3, Running = 4, - Unknown = 5, + Archiving = 5, + Unknown, } impl FromSql for ServerStatus{ @@ -42,6 +43,7 @@ impl fmt::Display for ServerStatus { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let str_status = match self { ServerStatus::Archived => "archived", + ServerStatus::Archiving => "archiving", ServerStatus::Stopped => "stopped", ServerStatus::Stopping => "stopping", ServerStatus::Starting => "starting",