mirror of
https://codeberg.org/spire/dispenser.git
synced 2026-06-03 18:14:06 +02:00
cli commands
This commit is contained in:
parent
756aea0d3b
commit
4e9c1094bb
4 changed files with 279 additions and 96 deletions
|
|
@ -3,7 +3,7 @@ use async_trait::async_trait;
|
|||
use chrono::{DateTime, Utc};
|
||||
use petname::petname;
|
||||
use reqwest::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use std::net::IpAddr;
|
||||
use std::time::Duration;
|
||||
use tokio::time::sleep;
|
||||
|
|
@ -215,6 +215,7 @@ struct VultrInstanceResponse {
|
|||
os: String,
|
||||
ram: u64,
|
||||
main_ip: IpAddr,
|
||||
#[serde(deserialize_with = "ok_or_default")]
|
||||
v6_main_ip: Option<IpAddr>,
|
||||
region: String,
|
||||
vcpu_count: u16,
|
||||
|
|
@ -222,6 +223,15 @@ struct VultrInstanceResponse {
|
|||
tag: String,
|
||||
}
|
||||
|
||||
fn ok_or_default<'a, T, D>(deserializer: D) -> Result<T, D::Error>
|
||||
where
|
||||
T: Deserialize<'a> + Default,
|
||||
D: Deserializer<'a>,
|
||||
{
|
||||
let v: T = Deserialize::deserialize(deserializer).unwrap_or_default();
|
||||
Ok(v)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct VultrCreatedInstanceResponse {
|
||||
id: String,
|
||||
|
|
|
|||
138
src/main.rs
138
src/main.rs
|
|
@ -4,9 +4,11 @@ use crate::dns::{DynDnsClient, DynDnsError};
|
|||
use crate::rcon::Rcon;
|
||||
use crate::ssh::SshError;
|
||||
use chrono::Utc;
|
||||
use clap::{Parser, Subcommand};
|
||||
use cron::Schedule;
|
||||
use main_error::MainResult;
|
||||
use ssh::SshSession;
|
||||
use std::env::args;
|
||||
use std::net::IpAddr;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
|
@ -22,6 +24,33 @@ mod dns;
|
|||
mod rcon;
|
||||
mod ssh;
|
||||
|
||||
/// Manage ephemeral tf2 servers
|
||||
#[derive(Parser)]
|
||||
#[clap(author, version, about, long_about = None)]
|
||||
struct Args {
|
||||
#[clap(subcommand)]
|
||||
command: Option<Commands>,
|
||||
config: String,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
/// Start a new server if none is running
|
||||
Start,
|
||||
/// Start the server if one is running
|
||||
Stop,
|
||||
/// List running servers
|
||||
List,
|
||||
/// Run the management daemon
|
||||
Daemon,
|
||||
}
|
||||
|
||||
impl Default for Commands {
|
||||
fn default() -> Self {
|
||||
Commands::Daemon
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("Error while interacting with cloud provider: {0}")]
|
||||
|
|
@ -51,14 +80,23 @@ async fn setup(ssh: &mut SshSession, config: &ServerConfig) -> Result<(), Error>
|
|||
debug!(image = display(&config.image), "pulling image");
|
||||
loop {
|
||||
tries += 1;
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
sleep(Duration::from_secs(2)).await;
|
||||
let result = ssh.exec(format!("docker pull {}", config.image)).await?;
|
||||
if result.success() {
|
||||
break;
|
||||
} else if tries > 5 {
|
||||
error!(
|
||||
tries = tries,
|
||||
output = display(result.output()),
|
||||
"Failed to pull docker image to many times, giving up"
|
||||
);
|
||||
return Err(Error::SetupError(result.output()));
|
||||
} else {
|
||||
error!(tries = tries, "Failed to pull docker image, retrying");
|
||||
error!(
|
||||
tries = tries,
|
||||
output = display(result.output()),
|
||||
"Failed to pull docker image, retrying"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -101,27 +139,64 @@ async fn setup(ssh: &mut SshSession, config: &ServerConfig) -> Result<(), Error>
|
|||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Error> {
|
||||
async fn main() -> MainResult {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let mut args = args();
|
||||
let bin = args.next().unwrap();
|
||||
let cli = Args::parse();
|
||||
|
||||
let config = match args.next() {
|
||||
Some(file) => Config::from_file(file)?,
|
||||
None => {
|
||||
eprintln!("Usage {} <config.toml>", bin);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
let config = Config::from_file(&cli.config)?;
|
||||
let cloud = config.cloud()?;
|
||||
|
||||
let start_schedule = Schedule::from_str(&config.schedule.start)?;
|
||||
let stop_schedule = Schedule::from_str(&config.schedule.stop)?;
|
||||
match cli.command.unwrap_or_default() {
|
||||
Commands::Daemon => {
|
||||
let start_schedule = Schedule::from_str(&config.schedule.start)?;
|
||||
let stop_schedule = Schedule::from_str(&config.schedule.stop)?;
|
||||
|
||||
select! {
|
||||
_ = run_loop(cloud, config, start_schedule, stop_schedule) => {},
|
||||
_ = ctrl_c() => {},
|
||||
select! {
|
||||
_ = run_loop(cloud, config, start_schedule, stop_schedule) => {},
|
||||
_ = ctrl_c() => {},
|
||||
}
|
||||
}
|
||||
Commands::List => {
|
||||
let servers = cloud.list().await?;
|
||||
if servers.is_empty() {
|
||||
println!("No running server");
|
||||
} else {
|
||||
for server in servers {
|
||||
let player_count =
|
||||
match Rcon::new((server.ip, 27015), &config.server.rcon).await {
|
||||
Ok(mut rcon) => rcon.player_count().await,
|
||||
Err(e) => Err(e),
|
||||
};
|
||||
|
||||
if let Ok(player_count) = player_count {
|
||||
println!("{}: {} with {} players", server.id, server.ip, player_count);
|
||||
} else {
|
||||
println!("{}: {}", server.id, server.ip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Commands::Start => {
|
||||
match start(cloud.as_ref(), &config).await {
|
||||
Ok(_) => {}
|
||||
Err(Error::AlreadyRunning(_)) => {
|
||||
println!("Server already running");
|
||||
}
|
||||
Err(e) => eprintln!("{:#}", e),
|
||||
};
|
||||
}
|
||||
Commands::Stop => match cloud.list().await?.first() {
|
||||
Some(server) => match cloud.kill(&server.id).await {
|
||||
Ok(_) => {
|
||||
println!("Server stopped");
|
||||
}
|
||||
Err(e) => eprintln!("{:#}", e),
|
||||
},
|
||||
None => {
|
||||
eprintln!("No server running");
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
@ -250,7 +325,7 @@ async fn start(cloud: &dyn Cloud, config: &Config) -> Result<Server, Error> {
|
|||
format!("{}", server.ip)
|
||||
};
|
||||
|
||||
let mut ssh = SshSession::open(server.ip, &created.password).await?;
|
||||
let mut ssh = connect_ssh(server.ip, &created.password).await?;
|
||||
setup(&mut ssh, &config.server).await?;
|
||||
ssh.close().await?;
|
||||
|
||||
|
|
@ -262,3 +337,28 @@ async fn start(cloud: &dyn Cloud, config: &Config) -> Result<Server, Error> {
|
|||
);
|
||||
Ok(server)
|
||||
}
|
||||
|
||||
async fn connect_ssh(ip: IpAddr, password: &str) -> Result<SshSession, Error> {
|
||||
let mut tries = 0;
|
||||
|
||||
loop {
|
||||
tries += 1;
|
||||
sleep(Duration::from_secs(2)).await;
|
||||
|
||||
match SshSession::open(ip, password).await {
|
||||
Ok(ssh) => {
|
||||
return Ok(ssh);
|
||||
}
|
||||
Err(e) if tries > 5 => {
|
||||
error!(
|
||||
tries = tries,
|
||||
"Failed to connect to ssh to many times, giving up"
|
||||
);
|
||||
return Err(e.into());
|
||||
}
|
||||
Err(_) => {
|
||||
error!(tries = tries, "Failed to connect to ssh, giving up");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue