This commit is contained in:
Robin Appelman 2022-05-20 19:19:46 +02:00
commit a581121bd6
5 changed files with 117 additions and 13 deletions

View file

@ -14,6 +14,7 @@ use thiserror::Error;
use tokio::select;
use tokio::signal::ctrl_c;
use tokio::time::sleep;
use tracing::{debug, error, info, instrument, warn};
mod cloud;
mod config;
@ -41,18 +42,25 @@ pub enum Error {
Rcon(#[from] ::rcon::Error),
}
#[instrument(skip(config))]
async fn setup(ssh: &mut SshSession, config: &ServerConfig) -> Result<(), Error> {
let mut tries = 0;
debug!(image = display(&config.image), "pulling image");
loop {
tries += 1;
sleep(Duration::from_secs(1)).await;
let result = ssh.exec("docker pull spiretf/docker-spire-server").await?;
let result = ssh.exec(format!("docker pull {}", config.image)).await?;
if result.success() {
break;
} else if tries > 5 {
return Err(Error::SetupError(result.output()));
} else {
error!(tries = tries, "Failed to pull docker image, retrying");
}
}
debug!("starting container");
let result = ssh
.exec(format!(
"docker run --name spire -d \
@ -82,6 +90,7 @@ async fn setup(ssh: &mut SshSession, config: &ServerConfig) -> Result<(), Error>
return Err(Error::SetupError(result.output()));
}
debug!("setting up swap");
ssh.exec("dd if=/dev/zero of=/swapfile bs=1M count=1024")
.await?;
ssh.exec("chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile")
@ -91,7 +100,7 @@ async fn setup(ssh: &mut SshSession, config: &ServerConfig) -> Result<(), Error>
#[tokio::main]
async fn main() -> Result<(), Error> {
pretty_env_logger::init();
tracing_subscriber::fmt::init();
let mut args = args();
let bin = args.next().unwrap();
@ -138,6 +147,10 @@ async fn run_loop(
match start(cloud.as_ref(), &config).await {
Ok(server) => active_server = Some(server),
Err(Error::AlreadyRunning(server)) if config.server.manage_existing => {
info!(
server = debug(&server),
"Taking ownership of existing server"
);
active_server = Some(server);
}
Err(e) => eprintln!("{:#}", e),
@ -158,14 +171,14 @@ async fn run_loop(
let stop = match active_players_res {
Ok(0) => true,
Ok(count) => {
println!(
info!(
"Want to stop server, but there are still {} active players",
count
);
false
}
Err(e) => {
eprintln!("{}", e);
error!("Error while trying get player count: {}", e);
true
}
};
@ -185,12 +198,13 @@ async fn run_loop(
}
}
#[instrument(skip(cloud, config))]
async fn start(cloud: &dyn Cloud, config: &Config) -> Result<Server, Error> {
let list = cloud.list().await?;
let count = list.len();
let first = list.into_iter().next();
if let Some(first) = first {
eprintln!(
warn!(
"Non empty server list while starting: {:?}, and {} more",
first,
count - 1

View file

@ -1,14 +1,18 @@
use crate::Error;
use rcon::Connection;
use std::fmt::Debug;
use tokio::net::{TcpStream, ToSocketAddrs};
use tracing::instrument;
pub struct Rcon(Connection<TcpStream>);
impl Rcon {
pub async fn new<A: ToSocketAddrs>(host: A, password: &str) -> Result<Self, Error> {
#[instrument(skip(password))]
pub async fn new<A: ToSocketAddrs + Debug>(host: A, password: &str) -> Result<Self, Error> {
Ok(Rcon(Connection::builder().connect(host, password).await?))
}
#[instrument(skip(self))]
pub async fn player_count(&mut self) -> Result<usize, Error> {
let status = self.0.cmd("status").await?;
let player_lines = status

View file

@ -1,4 +1,5 @@
use futures_util::future::{self};
use std::fmt::{Debug, Formatter};
use std::io::Write;
use std::net::IpAddr;
use std::sync::Arc;
@ -8,6 +9,7 @@ use thrussh::client::Handle;
use thrussh::*;
use thrussh_keys::key::PublicKey;
use tokio::time::{sleep, timeout};
use tracing::instrument;
struct Client {}
@ -56,10 +58,20 @@ impl client::Handler for Client {
}
pub struct SshSession {
ip: IpAddr,
handle: Handle<Client>,
}
impl Debug for SshSession {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SshSession")
.field("ip", &format_args!("{}", self.ip))
.finish_non_exhaustive()
}
}
impl SshSession {
#[instrument(skip(password))]
pub async fn open(ip: IpAddr, password: &str) -> Result<Self, SshError> {
Ok(timeout(Duration::from_secs(5 * 60), async move {
loop {
@ -76,29 +88,33 @@ impl SshSession {
}
async fn open_impl(ip: IpAddr, password: &str) -> Result<Self, SshError> {
let config = thrussh::client::Config::default();
let config = client::Config::default();
let config = Arc::new(config);
let sh = Client {};
let mut handle = thrussh::client::connect(config, (ip, 22), sh).await?;
let mut handle = client::connect(config, (ip, 22), sh).await?;
if handle.authenticate_password("root", password).await? {
Ok(SshSession { handle })
Ok(SshSession { ip, handle })
} else {
Err(SshError::Unauthorized)
}
}
pub async fn exec<S: Into<String>>(&mut self, cmd: S) -> Result<CommandResult, SshError> {
#[instrument]
pub async fn exec<S: Into<String> + Debug>(
&mut self,
cmd: S,
) -> Result<CommandResult, SshError> {
let mut channel = self.handle.channel_open_session().await?;
channel.exec(true, cmd).await?;
let mut output = Vec::new();
let mut code = None;
while let Some(msg) = channel.wait().await {
match msg {
thrussh::ChannelMsg::Data { ref data } => {
ChannelMsg::Data { ref data } => {
output.write_all(data).unwrap();
}
thrussh::ChannelMsg::ExitStatus { exit_status } => {
ChannelMsg::ExitStatus { exit_status } => {
code = Some(exit_status);
}
_ => {}
@ -107,6 +123,7 @@ impl SshSession {
Ok(CommandResult { output, code })
}
#[instrument]
pub async fn close(mut self) -> Result<(), SshError> {
self.handle
.disconnect(Disconnect::ByApplication, "", "English")