mirror of
https://codeberg.org/spire/dispenser.git
synced 2026-06-03 10:04:07 +02:00
working server spawn
This commit is contained in:
parent
04b08706ef
commit
f3b9d14678
4 changed files with 143 additions and 108 deletions
|
|
@ -1,14 +1,12 @@
|
|||
mod ssh;
|
||||
pub mod vultr;
|
||||
use std::net::IpAddr;
|
||||
|
||||
use crate::cloud::ssh::SshError;
|
||||
use crate::config::ServerConfig;
|
||||
use async_trait::async_trait;
|
||||
use chrono::{DateTime, Utc};
|
||||
use reqwest::StatusCode;
|
||||
use std::net::IpAddr;
|
||||
use thiserror::Error;
|
||||
|
||||
pub mod vultr;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum CloudError {
|
||||
#[error("Invalid credentials")]
|
||||
|
|
@ -21,8 +19,6 @@ pub enum CloudError {
|
|||
InvalidResponse(#[from] ResponseError),
|
||||
#[error("Server boot timed out")]
|
||||
StartTimeout,
|
||||
#[error("Error while trying to connect trough ssh: {0}")]
|
||||
Ssh(#[from] SshError),
|
||||
}
|
||||
|
||||
/// Intentionally opaque error
|
||||
|
|
@ -72,8 +68,6 @@ pub trait Cloud {
|
|||
async fn kill(&self, id: &str) -> Result<()>;
|
||||
/// Wait until the server has an ip
|
||||
async fn wait_for_ip(&self, id: &str) -> Result<Server>;
|
||||
/// Setup the tf2 server on the instance
|
||||
async fn setup(&self, id: &str, password: &str, config: &ServerConfig) -> Result<Server>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
use crate::cloud::ssh::{SshError, SshSession};
|
||||
use crate::cloud::{Cloud, CloudError, Created, NetworkError, ResponseError, Result, Server};
|
||||
use crate::config::ServerConfig;
|
||||
use async_trait::async_trait;
|
||||
use chrono::{DateTime, Utc};
|
||||
use petname::petname;
|
||||
|
|
@ -8,7 +6,7 @@ use reqwest::Client;
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::net::IpAddr;
|
||||
use std::time::Duration;
|
||||
use tokio::time::{sleep, timeout};
|
||||
use tokio::time::sleep;
|
||||
|
||||
pub struct Vultr {
|
||||
region: String,
|
||||
|
|
@ -91,65 +89,6 @@ impl Cloud for Vultr {
|
|||
};
|
||||
Ok(instance.into())
|
||||
}
|
||||
|
||||
async fn setup(
|
||||
&self,
|
||||
id: &str,
|
||||
password: &str,
|
||||
config: &ServerConfig,
|
||||
) -> Result<Server, CloudError> {
|
||||
let server = self.wait_for_ip(id).await?;
|
||||
let ip = server.ip;
|
||||
let mut ssh = timeout(Duration::from_secs(5 * 60), async move {
|
||||
loop {
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
match SshSession::open(ip, password).await {
|
||||
Ok(ssh) => return Ok(ssh),
|
||||
Err(SshError::ConnectionTimeout) => {}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
})
|
||||
.await
|
||||
.map_err(|_| CloudError::StartTimeout)??;
|
||||
println!("connected");
|
||||
|
||||
ssh.exec("docker pull spiretf/docker-spire-server").await?;
|
||||
println!("pulled");
|
||||
ssh.exec(format!(
|
||||
"docker run --name spire -d \
|
||||
-e NAME={name} -e TV_NAME={tv_name} -e PASSWORD={password} -e RCON_PASSWORD={rcon} \
|
||||
-e DEMOSTF_APIKEY={demostf} -e LOGSTF_APIKEY={logstf} \
|
||||
-e CONFIG_LEAGUE={league} -e CONFIG_MODE={mode} \
|
||||
-p 27015:27015 -p 27021:27021 -p 27015:27015/udp -p 27020:27020/udp -p 27025:27025 \
|
||||
-p 28015:27015 -p 28015:27015/udp -p 27115:27015 -p 27115:27015/udp -p 27215:27015 \
|
||||
-p 27215:27015/udp -p 27315:27015 -p 27315:27015/udp -p 27415:27015 -p 27415:27015/udp \
|
||||
-p 27515:27015 -p 27515:27015/udp -p 27615:27015 -p 27615:27015/udp -p 27715:27015 \
|
||||
-p 27715:27015/udp -p 27815:27015 -p 27815:27015/udp -p 27915:27015 -p 27915:27015/udp \
|
||||
{image}
|
||||
",
|
||||
name = config.name,
|
||||
tv_name = config.tv_name,
|
||||
password = config.password,
|
||||
rcon = config.rcon,
|
||||
demostf = config
|
||||
.demostf_key
|
||||
.as_ref()
|
||||
.map(String::as_str)
|
||||
.unwrap_or_default(),
|
||||
logstf = config
|
||||
.logstf_key
|
||||
.as_ref()
|
||||
.map(String::as_str)
|
||||
.unwrap_or_default(),
|
||||
league = config.config_league,
|
||||
mode = config.config_mode,
|
||||
image = config.image
|
||||
))
|
||||
.await?;
|
||||
|
||||
Ok(server)
|
||||
}
|
||||
}
|
||||
|
||||
impl Vultr {
|
||||
|
|
|
|||
92
src/main.rs
92
src/main.rs
|
|
@ -1,10 +1,18 @@
|
|||
pub mod cloud;
|
||||
pub mod config;
|
||||
use std::env::args;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use ssh::SshSession;
|
||||
|
||||
use crate::cloud::CloudError;
|
||||
use crate::config::{Config, ConfigError};
|
||||
use std::env::args;
|
||||
use thiserror::Error;
|
||||
use crate::config::{Config, ConfigError, ServerConfig};
|
||||
use crate::ssh::SshError;
|
||||
use std::time::Duration;
|
||||
use tokio::time::sleep;
|
||||
|
||||
pub mod cloud;
|
||||
pub mod config;
|
||||
pub mod ssh;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
|
|
@ -12,6 +20,61 @@ pub enum Error {
|
|||
Cloud(#[from] CloudError),
|
||||
#[error("Error while loading configuration: {0}")]
|
||||
Config(#[from] ConfigError),
|
||||
#[error("Error while trying to connect trough ssh: {0}")]
|
||||
Ssh(#[from] SshError),
|
||||
#[error("Setup command returned an error: {0}")]
|
||||
SetupError(String),
|
||||
}
|
||||
|
||||
async fn setup(ssh: &mut SshSession, config: &ServerConfig) -> Result<(), Error> {
|
||||
let mut tries = 0;
|
||||
loop {
|
||||
tries += 1;
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
let result = ssh.exec("docker pull spiretf/docker-spire-server").await?;
|
||||
if result.success() {
|
||||
break;
|
||||
} else if tries > 5 {
|
||||
return Err(Error::SetupError(result.output()));
|
||||
}
|
||||
}
|
||||
let result = ssh
|
||||
.exec(format!(
|
||||
"docker run --name spire -d \
|
||||
-e NAME={name} -e TV_NAME={tv_name} -e PASSWORD={password} -e RCON_PASSWORD={rcon} \
|
||||
-e DEMOSTF_APIKEY={demostf} -e LOGSTF_APIKEY={logstf} \
|
||||
-e CONFIG_LEAGUE={league} -e CONFIG_MODE={mode} \
|
||||
-p 27015:27015 -p 27021:27021 -p 27015:27015/udp -p 27020:27020/udp -p 27025:27025 \
|
||||
-p 28015:27015 -p 28015:27015/udp -p 27115:27015 -p 27115:27015/udp -p 27215:27015 \
|
||||
-p 27215:27015/udp -p 27315:27015 -p 27315:27015/udp -p 27415:27015 -p 27415:27015/udp \
|
||||
-p 27515:27015 -p 27515:27015/udp -p 27615:27015 -p 27615:27015/udp -p 27715:27015 \
|
||||
-p 27715:27015/udp -p 27815:27015 -p 27815:27015/udp -p 27915:27015 -p 27915:27015/udp \
|
||||
{image}
|
||||
",
|
||||
name = config.name,
|
||||
tv_name = config.tv_name,
|
||||
password = config.password,
|
||||
rcon = config.rcon,
|
||||
demostf = config
|
||||
.demostf_key
|
||||
.as_ref()
|
||||
.map(String::as_str)
|
||||
.unwrap_or_default(),
|
||||
logstf = config
|
||||
.logstf_key
|
||||
.as_ref()
|
||||
.map(String::as_str)
|
||||
.unwrap_or_default(),
|
||||
league = config.config_league,
|
||||
mode = config.config_mode,
|
||||
image = config.image
|
||||
))
|
||||
.await?;
|
||||
|
||||
if !result.success() {
|
||||
return Err(Error::SetupError(result.output()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
|
|
@ -28,14 +91,23 @@ async fn main() -> Result<(), Error> {
|
|||
};
|
||||
let cloud = config.cloud()?;
|
||||
|
||||
let created = dbg!(cloud.spawn().await?);
|
||||
let created = cloud.spawn().await?;
|
||||
let server = cloud.wait_for_ip(&created.id).await?;
|
||||
|
||||
println!("Server is booting");
|
||||
println!(" IP: {}", server.ip);
|
||||
println!(" Password: {}", created.password);
|
||||
dbg!(
|
||||
cloud
|
||||
.setup(&created.id, &created.password, &config.server)
|
||||
.await?
|
||||
|
||||
let mut ssh = SshSession::open(server.ip, &created.password).await?;
|
||||
setup(&mut ssh, &config.server).await?;
|
||||
ssh.close().await?;
|
||||
|
||||
println!("Server has been setup and is starting");
|
||||
println!("Connect using");
|
||||
println!(
|
||||
" connect {}; password {}",
|
||||
server.ip, config.server.password
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
use futures_util::future::{self};
|
||||
use std::io::Write;
|
||||
use std::net::IpAddr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use thiserror::Error;
|
||||
use thrussh::client::Handle;
|
||||
use thrussh::*;
|
||||
use thrussh_keys::key::PublicKey;
|
||||
use tokio::time::{sleep, timeout};
|
||||
|
||||
struct Client {}
|
||||
|
||||
|
|
@ -47,28 +50,9 @@ impl client::Handler for Client {
|
|||
fn finished(self, session: client::Session) -> Self::FutureUnit {
|
||||
future::ready(Ok((self, session)))
|
||||
}
|
||||
fn check_server_key(self, server_public_key: &PublicKey) -> Self::FutureBool {
|
||||
println!("check_server_key: {:?}", server_public_key);
|
||||
fn check_server_key(self, _server_public_key: &PublicKey) -> Self::FutureBool {
|
||||
self.finished_bool(true)
|
||||
}
|
||||
fn channel_open_confirmation(
|
||||
self,
|
||||
channel: ChannelId,
|
||||
_max_packet_size: u32,
|
||||
_window_size: u32,
|
||||
session: client::Session,
|
||||
) -> Self::FutureUnit {
|
||||
println!("channel_open_confirmation: {:?}", channel);
|
||||
self.finished(session)
|
||||
}
|
||||
fn data(self, channel: ChannelId, data: &[u8], session: client::Session) -> Self::FutureUnit {
|
||||
println!(
|
||||
"data on channel {:?}: {:?}",
|
||||
channel,
|
||||
std::str::from_utf8(data)
|
||||
);
|
||||
self.finished(session)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SshSession {
|
||||
|
|
@ -77,6 +61,21 @@ pub struct SshSession {
|
|||
|
||||
impl SshSession {
|
||||
pub async fn open(ip: IpAddr, password: &str) -> Result<Self, SshError> {
|
||||
Ok(timeout(Duration::from_secs(5 * 60), async move {
|
||||
loop {
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
match SshSession::open_impl(ip, password).await {
|
||||
Ok(ssh) => return Ok(ssh),
|
||||
Err(SshError::ConnectionTimeout) => {}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
})
|
||||
.await
|
||||
.map_err(|_| SshError::ConnectionTimeout)??)
|
||||
}
|
||||
|
||||
async fn open_impl(ip: IpAddr, password: &str) -> Result<Self, SshError> {
|
||||
let config = thrussh::client::Config::default();
|
||||
let config = Arc::new(config);
|
||||
let sh = Client {};
|
||||
|
|
@ -89,14 +88,45 @@ impl SshSession {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn exec<S: Into<String>>(&mut self, cmd: S) -> Result<(), SshError> {
|
||||
pub async fn exec<S: Into<String>>(&mut self, cmd: S) -> Result<CommandResult, SshError> {
|
||||
let mut channel = self.handle.channel_open_session().await?;
|
||||
println!("exec");
|
||||
channel.exec(true, cmd).await?;
|
||||
println!("exec'd");
|
||||
if let Some(msg) = channel.wait().await {
|
||||
println!("{:?}", msg)
|
||||
let mut output = Vec::new();
|
||||
let mut code = None;
|
||||
while let Some(msg) = channel.wait().await {
|
||||
match msg {
|
||||
thrussh::ChannelMsg::Data { ref data } => {
|
||||
output.write_all(&data).unwrap();
|
||||
}
|
||||
thrussh::ChannelMsg::ExitStatus { exit_status } => {
|
||||
code = Some(exit_status);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(CommandResult { output, code })
|
||||
}
|
||||
|
||||
pub async fn close(mut self) -> Result<(), SshError> {
|
||||
self.handle
|
||||
.disconnect(Disconnect::ByApplication, "", "English")
|
||||
.await?;
|
||||
self.handle.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CommandResult {
|
||||
output: Vec<u8>,
|
||||
pub code: Option<u32>,
|
||||
}
|
||||
|
||||
impl CommandResult {
|
||||
pub fn output(&self) -> String {
|
||||
String::from_utf8_lossy(&self.output).into()
|
||||
}
|
||||
|
||||
pub fn success(&self) -> bool {
|
||||
self.code == Some(0)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue