add docker container creation (add broken since sqlite change)
This commit is contained in:
@ -4,8 +4,7 @@ use crate::status::ServerStatus;
|
||||
use crate::utils::{ validate_name, extract_archive, move_all_elements};
|
||||
|
||||
use diesel::prelude::*;
|
||||
use crate::{DbPool, models};
|
||||
use crate::schema;
|
||||
use crate::{DbPool, models}; use crate::schema;
|
||||
|
||||
use tokio::{fs::{File, create_dir}, io::AsyncWriteExt};
|
||||
|
||||
@ -27,7 +26,7 @@ pub fn create_subcommand() -> Command {
|
||||
|
||||
|
||||
pub async fn subcommand(arg: &ArgMatches, pool: DbPool) -> Result<(), String>{
|
||||
// use schema::servers::dsl::*;
|
||||
use schema::servers::dsl::*;
|
||||
let config = crate::Config::load();
|
||||
let server_name = arg.get_one::<String>("NAME").unwrap();
|
||||
|
||||
@ -65,6 +64,7 @@ pub async fn subcommand(arg: &ArgMatches, pool: DbPool) -> Result<(), String>{
|
||||
redirect_ip: None
|
||||
};
|
||||
|
||||
// insert_into(schema::servers::dsl::servers).values(&new_server).execute(conn).map_err(|e| format!("Failed to insert in db, error : {:?}", e))?;
|
||||
// diesel::insert_into(schema::servers::dsl::servers).values(&new_server).execute(conn).map_err(|e| format!("Failed to insert in db, error : {:?}", e))?;
|
||||
// TODO: fix weird error
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -96,5 +96,3 @@ pub fn update_env(arguments: &ArgMatches, prev_env: String) -> String{
|
||||
settings_to_env(merged)
|
||||
|
||||
}
|
||||
// START COMMAND :
|
||||
// docker run --rm --env-file ./env -u 1000:100 -it --volume $PWD/server:/data itzg/minecraft-server
|
||||
|
||||
@ -2,6 +2,7 @@ pub mod add;
|
||||
pub mod edit;
|
||||
pub mod remove;
|
||||
pub mod ls;
|
||||
pub mod start;
|
||||
|
||||
pub mod env_settings;
|
||||
|
||||
|
||||
20
src/cli/start.rs
Normal file
20
src/cli/start.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use bollard::Docker;
|
||||
use clap::Arg;
|
||||
use clap::{Command, ArgMatches};
|
||||
|
||||
use crate::DbPool;
|
||||
|
||||
use crate::dockermgr;
|
||||
|
||||
pub fn create_subcommand() -> Command {
|
||||
Command::new("start")
|
||||
.about("manually start a server (and set last login datetime to now)")
|
||||
.arg(Arg::new("NAME").required(true))
|
||||
}
|
||||
|
||||
pub async fn subcommand(arguments: &ArgMatches, pool: &DbPool, docker: Docker) -> Result<(), String> {
|
||||
let server_name = arguments.get_one::<String>("NAME").unwrap();
|
||||
dockermgr::start_server(&docker, pool, server_name).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -1,11 +1,13 @@
|
||||
pub struct Config {
|
||||
pub config_path: String
|
||||
pub config_path: String,
|
||||
pub docker_image: String,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn load() -> Self {
|
||||
Self {
|
||||
config_path: "/home/tom/desktop/rust/yo_mama/servers".to_string() // that's my program I do what I want
|
||||
config_path: "/home/tom/desktop/rust/yo_mama/servers".to_string(), // that's my program I do what I want
|
||||
docker_image: "itzg/minecraft-server".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
pub async fn start_server() -> Result<String, String> {
|
||||
Ok("".to_string())
|
||||
}
|
||||
@ -1,2 +1,4 @@
|
||||
mod start;
|
||||
pub use start::start_server;
|
||||
|
||||
pub mod utils;
|
||||
75
src/dockermgr/start.rs
Normal file
75
src/dockermgr/start.rs
Normal file
@ -0,0 +1,75 @@
|
||||
use bollard::{Docker, plugin::{ContainerCreateBody, HostConfig, Mount, MountType}, query_parameters::CreateContainerOptionsBuilder};
|
||||
use diesel::{prelude::*, result::Error::NotFound};
|
||||
|
||||
use crate::{schema, models, status::ServerStatus};
|
||||
|
||||
use crate::{DbPool, dockermgr::utils::{self, get_container_ip}};
|
||||
|
||||
async fn start_docker_container(docker: &Docker, name: &String) -> Result<String, String> {
|
||||
utils::check_image(&docker).await?;
|
||||
|
||||
let container_name = format!("minecraft-{}", name);
|
||||
|
||||
if let Some(_) = utils::get_container(&docker, &container_name).await {
|
||||
return Err("Container already exists".to_string())
|
||||
}
|
||||
|
||||
let config_path = crate::Config::load().config_path;
|
||||
|
||||
let env_file = std::fs::read_to_string(config_path.clone() + "/" + name + "/env").map_err(|e| format!("Failed to read env file {}", e))?;
|
||||
|
||||
let env = env_file.split("\n").filter_map(|x| if x.len() == 0 { None } else { Some(x.to_string()) } ).collect();
|
||||
|
||||
let mut mount = Mount::default();
|
||||
mount.typ = Some(MountType::BIND);
|
||||
mount.target = Some("/data".to_string());
|
||||
mount.source = Some(config_path + "/" + name + "/server");
|
||||
mount.read_only = Some(false);
|
||||
|
||||
let mut host_config = HostConfig::default();
|
||||
host_config.mounts = Some(vec![mount]);
|
||||
host_config.auto_remove = Some(true);
|
||||
|
||||
let mut create_config = ContainerCreateBody::default();
|
||||
create_config.tty = Some(true);
|
||||
create_config.open_stdin = Some(true);
|
||||
create_config.user = Some("1000:100".to_string());
|
||||
create_config.stop_timeout = Some(60);
|
||||
create_config.host_config = Some(host_config);
|
||||
create_config.image = Some(crate::Config::load().docker_image + ":latest");
|
||||
create_config.env = Some(env);
|
||||
|
||||
let options = CreateContainerOptionsBuilder::default()
|
||||
.name(&container_name)
|
||||
.build();
|
||||
|
||||
docker.create_container(Some(options), create_config).await.map_err(|e| format!("failed to create the container : {}", e))?;
|
||||
println!("created container");
|
||||
|
||||
println!("starting container");
|
||||
docker.start_container(&container_name, None).await.map_err(|e| format!("Failed to start the container : {}", e))?;
|
||||
Ok(get_container_ip(docker, name).await?)
|
||||
}
|
||||
|
||||
pub async fn start_server(docker: &Docker, pool: &DbPool, server_name: &String) -> Result<(), String> {
|
||||
use schema::servers::dsl::*;
|
||||
|
||||
let conn = &mut pool.get().unwrap();
|
||||
|
||||
let server = match servers.select(models::Servers::as_select()).filter(name.eq(server_name)).first(conn) {
|
||||
Err(NotFound) => { return Err("This server does not exist".to_string()); },
|
||||
Err(e) => { return Err(format!("Failed to get server from db : {}", e)) },
|
||||
Ok(server) => { server }
|
||||
};
|
||||
|
||||
if server.status == ServerStatus::Starting {
|
||||
return Err("Server is starting, please wait".to_string());
|
||||
}
|
||||
|
||||
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))?;
|
||||
|
||||
let ip = start_docker_container(docker, server_name).await?;
|
||||
|
||||
diesel::update(servers).filter(name.eq(server_name)).set((status.eq(ServerStatus::Starting), redirect_ip.eq(ip.to_string()))).execute(conn).map_err(|e| format!("Failed to set server to starting : {}", e))?;
|
||||
Ok(())
|
||||
}
|
||||
67
src/dockermgr/utils.rs
Normal file
67
src/dockermgr/utils.rs
Normal file
@ -0,0 +1,67 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use bollard::{Docker, plugin::{ContainerInspectResponse, ContainerSummary, ImageId}, query_parameters::{CreateImageOptionsBuilder, ListContainersOptionsBuilder, ListImagesOptionsBuilder}};
|
||||
|
||||
use futures_util::stream::StreamExt;
|
||||
use tokio::task;
|
||||
|
||||
pub async fn check_image(docker: &Docker) -> Result<(), String> {
|
||||
let mut filters = HashMap::new();
|
||||
let container_name = crate::Config::load().docker_image;
|
||||
filters.insert("reference", vec![&container_name]);
|
||||
let options = ListImagesOptionsBuilder::default()
|
||||
.all(true)
|
||||
.filters(&filters)
|
||||
.build();
|
||||
|
||||
|
||||
let images = docker.list_images(Some(options)).await.map_err(|e| format!("failed to check if image exists, error : {:?}", e))?;
|
||||
if images.len() >= 1 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let options = CreateImageOptionsBuilder::default()
|
||||
.from_image(&container_name)
|
||||
.tag("latest")
|
||||
.build();
|
||||
|
||||
|
||||
println!("pulling {} from docker hub ... (this might take a while and I'm too lazy to do an Interface to show the progress, if you want progress info, pull it yourself on with `docker pull {}`)", container_name, container_name);
|
||||
|
||||
let mut pull_result = docker.create_image(Some(options), None, None);
|
||||
|
||||
while let Some(update) = pull_result.next().await {
|
||||
if let Err(e) = update {
|
||||
return Err(format!("Failed to pull the image from docker hub, error : {:?}", e));
|
||||
}
|
||||
}
|
||||
println!("pulled successfully");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_container(docker: &Docker, name: &String) -> Option<ContainerInspectResponse> {
|
||||
println!("get {}", name);
|
||||
|
||||
let result = docker.inspect_container(name, None).await.map_err(|e| format!("failed to retreive container : {:?}", e));
|
||||
match result {
|
||||
Err(_) => { None },
|
||||
Ok(container) => { Some(container) }
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_container_ip(docker: &Docker, name: &String) -> Result<String, String>{
|
||||
let mut retries = 20;
|
||||
|
||||
while retries > 0 {
|
||||
let container = get_container(docker, name).await.unwrap();
|
||||
if let Some(network) = container.network_settings.unwrap().networks.unwrap().get("bridge") {
|
||||
if let Some(ip) = &network.ip_address {
|
||||
return Ok(ip.to_string());
|
||||
}
|
||||
}
|
||||
async_std::task::sleep(std::time::Duration::from_millis(500)).await;
|
||||
retries -= 1;
|
||||
}
|
||||
Ok("can't get container ip".to_string())
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
use bollard::Docker;
|
||||
|
||||
use std::sync::LazyLock;
|
||||
|
||||
pub static mut DOCKER: LazyLock<Docker> = LazyLock::new(|| Docker::connect_with_local_defaults().expect("Failed to connect to the docker socket") );
|
||||
|
||||
18
src/main.rs
18
src/main.rs
@ -6,7 +6,7 @@
|
||||
/* By: tomoron <tomoron@student.42angouleme.fr> +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2026/05/29 21:22:17 by tomoron #+# #+# */
|
||||
/* Updated: 2026/06/06 19:39:47 by tomoron ### ########.fr */
|
||||
/* Updated: 2026/06/09 00:05:23 by tomoron ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
@ -20,7 +20,7 @@ pub mod models;
|
||||
|
||||
pub mod status;
|
||||
|
||||
pub mod docker;
|
||||
pub mod dockermgr;
|
||||
|
||||
mod cli;
|
||||
use cli::*;
|
||||
@ -28,20 +28,22 @@ use cli::*;
|
||||
mod config;
|
||||
pub use config::Config;
|
||||
|
||||
use clap::Command;
|
||||
|
||||
pub type DbPool = Pool<ConnectionManager<SqliteConnection>>;
|
||||
|
||||
use bollard::Docker;
|
||||
|
||||
fn get_connection_pool() -> DbPool {
|
||||
let database_url = "sqlite:///".to_string() + &Config::load().config_path + "/db.sqlite";
|
||||
let database_url = "sqlite://".to_string() + &Config::load().config_path + "/db.sqlite";
|
||||
|
||||
let manager = ConnectionManager::<SqliteConnection>::new(&database_url);
|
||||
Pool::builder()
|
||||
.build(manager)
|
||||
.unwrap_or_else(|_| panic!("Error creating pool for {}", database_url))
|
||||
.unwrap_or_else(|e| panic!("Error creating pool for {}\nerror: {:?}", database_url, e))
|
||||
}
|
||||
|
||||
|
||||
use clap::Command;
|
||||
|
||||
fn cli() -> Command {
|
||||
Command::new("dmm")
|
||||
.about("Docker Minecraft server Manager cli")
|
||||
@ -56,12 +58,14 @@ fn cli() -> Command {
|
||||
.subcommand(remove::create_subcommand())
|
||||
.subcommand(edit::create_subcommand())
|
||||
.subcommand(ls::create_subcommand())
|
||||
.subcommand(start::create_subcommand())
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> Result<(), String> {
|
||||
let arg = cli().get_matches();
|
||||
let pool = get_connection_pool();
|
||||
let docker = Docker::connect_with_defaults().expect("Failed to connect to local docker");
|
||||
|
||||
match arg.subcommand() {
|
||||
Some(("serverMode", _)) => { mc_socket_listen(pool).await.map_err(|_| "socket error".to_string())?; },
|
||||
@ -69,6 +73,8 @@ async fn main() -> Result<(), String> {
|
||||
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?; },
|
||||
|
||||
Some(("start", sub_matches)) => { start::subcommand(sub_matches, &pool, docker).await?; },
|
||||
_ => {panic!("subcommand not implemented")}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user