embed migrations, fix start, add stop
This commit is contained in:
@ -1,3 +1,4 @@
|
||||
use chrono::Utc;
|
||||
use clap::{ArgMatches, Command, arg};
|
||||
use crate::cli::env_settings;
|
||||
use crate::status::ServerStatus;
|
||||
@ -9,7 +10,7 @@ use crate::{DbPool, models}; use crate::schema;
|
||||
use tokio::{fs::{File, create_dir}, io::AsyncWriteExt};
|
||||
|
||||
use std::path::Path;
|
||||
use std::time::SystemTime;
|
||||
|
||||
|
||||
pub fn create_subcommand() -> Command {
|
||||
let mut arguments = vec![
|
||||
@ -56,15 +57,13 @@ pub async fn subcommand(arg: &ArgMatches, pool: DbPool) -> Result<(), String>{
|
||||
std::fs::remove_dir_all(path.to_string() + "tmp").map_err(|e| format!("failed to remove the tmp dir, reason : {}", e))?;
|
||||
}
|
||||
|
||||
let _new_server = models::CreateServer {
|
||||
let new_server = models::CreateServer {
|
||||
name: server_name,
|
||||
last_login: Some(SystemTime::now()),
|
||||
container_id: None,
|
||||
last_login: Some(Utc::now().naive_utc()),
|
||||
status: ServerStatus::Stopped,
|
||||
redirect_ip: None
|
||||
};
|
||||
|
||||
// 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
|
||||
diesel::insert_into(servers).values(&new_server).execute(conn).map_err(|e| format!("Failed to insert in db, error : {:?}", e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ pub mod edit;
|
||||
pub mod remove;
|
||||
pub mod ls;
|
||||
pub mod start;
|
||||
pub mod stop;
|
||||
|
||||
pub mod env_settings;
|
||||
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
use bollard::Docker;
|
||||
use chrono::Utc;
|
||||
use clap::Arg;
|
||||
use clap::{Command, ArgMatches};
|
||||
|
||||
use crate::DbPool;
|
||||
use crate::{ DbPool, schema };
|
||||
|
||||
use crate::dockermgr;
|
||||
|
||||
use diesel::prelude::*;
|
||||
|
||||
pub fn create_subcommand() -> Command {
|
||||
Command::new("start")
|
||||
.about("manually start a server (and set last login datetime to now)")
|
||||
@ -13,8 +16,11 @@ pub fn create_subcommand() -> Command {
|
||||
}
|
||||
|
||||
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();
|
||||
dockermgr::start_server(&docker, pool, server_name).await?;
|
||||
|
||||
diesel::update(servers).filter(name.eq(server_name)).set(last_login.eq(Utc::now().naive_utc())).execute(&mut pool.get().unwrap()).map_err(|e| format!("Failed to set the last login date: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
22
src/cli/stop.rs
Normal file
22
src/cli/stop.rs
Normal file
@ -0,0 +1,22 @@
|
||||
use bollard::Docker;
|
||||
use clap::Arg;
|
||||
use clap::{Command, ArgMatches};
|
||||
|
||||
use crate::DbPool;
|
||||
|
||||
use crate::dockermgr;
|
||||
|
||||
pub fn create_subcommand() -> Command {
|
||||
Command::new("stop")
|
||||
.about("manually stop a server")
|
||||
.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();
|
||||
println!("Stopping container");
|
||||
dockermgr::stop_server(&docker, pool, server_name).await?;
|
||||
println!("container stopped");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -2,3 +2,6 @@ mod start;
|
||||
pub use start::start_server;
|
||||
|
||||
pub mod utils;
|
||||
|
||||
mod stop;
|
||||
pub use stop::stop_server;
|
||||
|
||||
@ -48,13 +48,14 @@ async fn start_docker_container(docker: &Docker, name: &String) -> Result<String
|
||||
|
||||
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?)
|
||||
Ok(get_container_ip(docker, &container_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 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()); },
|
||||
@ -66,10 +67,14 @@ pub async fn start_server(docker: &Docker, pool: &DbPool, server_name: &String)
|
||||
return Err("Server is starting, please wait".to_string());
|
||||
}
|
||||
|
||||
if server.status != ServerStatus::Stopped {
|
||||
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))?;
|
||||
|
||||
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))?;
|
||||
diesel::update(servers).filter(name.eq(server_name)).set((status.eq(ServerStatus::Running), redirect_ip.eq(ip.to_string() + ":25565"))).execute(conn).map_err(|e| format!("Failed to set server to starting : {}", e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
41
src/dockermgr/stop.rs
Normal file
41
src/dockermgr/stop.rs
Normal file
@ -0,0 +1,41 @@
|
||||
use bollard::{Docker, query_parameters::StopContainerOptionsBuilder};
|
||||
use diesel::result::Error::NotFound;
|
||||
|
||||
use diesel::prelude::*;
|
||||
|
||||
use crate::{ DbPool, models, schema, status::ServerStatus };
|
||||
|
||||
|
||||
async fn stop_container(docker: &Docker, name: &String) -> Result<(), String> {
|
||||
let container_name = format!("minecraft-{}", name);
|
||||
|
||||
let options = StopContainerOptionsBuilder::new()
|
||||
.t(60)
|
||||
.build();
|
||||
|
||||
docker.stop_container(&container_name, Some(options)).await.map_err(|e| format!("Failed to stop the container : {}", e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn stop_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::Running {
|
||||
return Err(format!("Invalid server state ({}), refusing to stop the server", server.status));
|
||||
}
|
||||
|
||||
diesel::update(servers).filter(name.eq(server_name)).set(status.eq(ServerStatus::Stopping)).execute(conn).map_err(|e| format!("Failed to set the server state to stopping : {}", e))?;
|
||||
|
||||
stop_container(docker, server_name).await?;
|
||||
|
||||
diesel::update(servers).filter(name.eq(server_name)).set((redirect_ip.eq(None::<String>), status.eq(ServerStatus::Stopped))).execute(conn).map_err(|e| format!("Failed to set the server state to stopped : {}", e))?;
|
||||
Ok(())
|
||||
}
|
||||
@ -1,9 +1,12 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use bollard::{Docker, plugin::{ContainerInspectResponse, ContainerSummary, ImageId}, query_parameters::{CreateImageOptionsBuilder, ListContainersOptionsBuilder, ListImagesOptionsBuilder}};
|
||||
use bollard::{
|
||||
Docker,
|
||||
plugin::{ContainerInspectResponse},
|
||||
query_parameters::{CreateImageOptionsBuilder, ListImagesOptionsBuilder}
|
||||
};
|
||||
|
||||
use futures_util::stream::StreamExt;
|
||||
use tokio::task;
|
||||
|
||||
pub async fn check_image(docker: &Docker) -> Result<(), String> {
|
||||
let mut filters = HashMap::new();
|
||||
@ -54,8 +57,9 @@ pub async fn get_container_ip(docker: &Docker, name: &String) -> Result<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") {
|
||||
let container = get_container(docker, name).await;
|
||||
|
||||
if let Some(container) = container && let Some(network) = container.network_settings.unwrap().networks.unwrap().get("bridge") {
|
||||
if let Some(ip) = &network.ip_address {
|
||||
return Ok(ip.to_string());
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
/* By: tomoron <tomoron@student.42angouleme.fr> +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2026/05/29 21:22:17 by tomoron #+# #+# */
|
||||
/* Updated: 2026/06/09 00:05:23 by tomoron ### ########.fr */
|
||||
/* Updated: 2026/06/09 17:04:32 by tomoron ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
@ -34,6 +34,8 @@ pub type DbPool = Pool<ConnectionManager<SqliteConnection>>;
|
||||
|
||||
use bollard::Docker;
|
||||
|
||||
mod migrations;
|
||||
|
||||
fn get_connection_pool() -> DbPool {
|
||||
let database_url = "sqlite://".to_string() + &Config::load().config_path + "/db.sqlite";
|
||||
|
||||
@ -59,12 +61,16 @@ fn cli() -> Command {
|
||||
.subcommand(edit::create_subcommand())
|
||||
.subcommand(ls::create_subcommand())
|
||||
.subcommand(start::create_subcommand())
|
||||
.subcommand(stop::create_subcommand())
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> Result<(), String> {
|
||||
let arg = cli().get_matches();
|
||||
let pool = get_connection_pool();
|
||||
|
||||
migrations::run_migrations(&pool).map_err(|e| format!("Failed to run migrations : {}", e))?;
|
||||
|
||||
let docker = Docker::connect_with_defaults().expect("Failed to connect to local docker");
|
||||
|
||||
match arg.subcommand() {
|
||||
@ -75,6 +81,7 @@ async fn main() -> Result<(), String> {
|
||||
Some(("edit", sub_matches)) => { edit::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?; },
|
||||
_ => {panic!("subcommand not implemented")}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
19
src/migrations.rs
Normal file
19
src/migrations.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use std::error::Error;
|
||||
|
||||
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
|
||||
|
||||
use crate::DbPool;
|
||||
pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("./migrations");
|
||||
|
||||
|
||||
pub fn run_migrations(pool: &DbPool) -> Result<(), Box<dyn Error + Send + Sync + 'static>> {
|
||||
|
||||
// This will run the necessary migrations.
|
||||
//
|
||||
// See the documentation for `MigrationHarness` for
|
||||
// all available methods.
|
||||
let connection = &mut pool.get().unwrap();
|
||||
connection.run_pending_migrations(MIGRATIONS)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -1,11 +1,8 @@
|
||||
use std::fmt;
|
||||
use std::time::SystemTime;
|
||||
|
||||
use chrono::{DateTime, NaiveDateTime, TimeZone, Utc};
|
||||
use diesel::prelude::*;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::status::ServerStatus;
|
||||
|
||||
use crate::schema;
|
||||
@ -16,18 +13,16 @@ use crate::schema;
|
||||
pub struct Servers {
|
||||
pub name: String,
|
||||
pub last_login: Option<NaiveDateTime>,
|
||||
pub container_id: Option<String>,
|
||||
pub status: ServerStatus,
|
||||
pub redirect_ip: Option<String>
|
||||
}
|
||||
|
||||
|
||||
#[derive(Deserialize, Insertable)]
|
||||
#[derive(Insertable)]
|
||||
#[diesel(table_name = schema::servers)]
|
||||
pub struct CreateServer<'a> {
|
||||
pub name: &'a str,
|
||||
pub last_login: Option<SystemTime>,
|
||||
pub container_id: Option<&'a str>,
|
||||
pub last_login: Option<NaiveDateTime>,
|
||||
pub status: ServerStatus,
|
||||
pub redirect_ip: Option<&'a str>
|
||||
}
|
||||
|
||||
@ -4,7 +4,6 @@ diesel::table! {
|
||||
servers (name) {
|
||||
name -> Text,
|
||||
last_login -> Nullable<Timestamp>,
|
||||
container_id -> Nullable<Text>,
|
||||
status -> SmallInt,
|
||||
is_default -> Bool,
|
||||
redirect_ip -> Nullable<Text>,
|
||||
|
||||
@ -32,7 +32,7 @@ impl FromSql<SmallInt, Sqlite> for ServerStatus{
|
||||
impl ToSql<SmallInt, Sqlite> for ServerStatus {
|
||||
fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> diesel::serialize::Result {
|
||||
let value: i16 = self.clone() as i16;
|
||||
out.set_value(value.to_le_bytes().to_vec());
|
||||
out.set_value(value as i32);
|
||||
Ok(diesel::serialize::IsNull::No)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user