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

67
Cargo.lock generated
View file

@ -30,6 +30,15 @@ dependencies = [
"memchr",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
[[package]]
name = "async-trait"
version = "0.1.53"
@ -313,6 +322,8 @@ dependencies = [
"thrussh-keys",
"tokio",
"toml",
"tracing",
"tracing-subscriber",
]
[[package]]
@ -1186,6 +1197,15 @@ dependencies = [
"opaque-debug",
]
[[package]]
name = "sharded-slab"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
dependencies = [
"lazy_static",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.0"
@ -1201,6 +1221,12 @@ version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32"
[[package]]
name = "smallvec"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
[[package]]
name = "socket2"
version = "0.4.4"
@ -1308,6 +1334,15 @@ dependencies = [
"syn",
]
[[package]]
name = "thread_local"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
dependencies = [
"once_cell",
]
[[package]]
name = "thrussh"
version = "0.33.5"
@ -1511,6 +1546,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f"
dependencies = [
"lazy_static",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
dependencies = [
"lazy_static",
"log",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bc28f93baff38037f64e6f43d34cfa1605f27a49c34e8a04c5e78b0babf2596"
dependencies = [
"ansi_term",
"sharded-slab",
"smallvec",
"thread_local",
"tracing-core",
"tracing-log",
]
[[package]]
@ -1582,6 +1643,12 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "valuable"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "vcpkg"
version = "0.2.15"

View file

@ -19,4 +19,6 @@ thrussh-keys = "0.21"
futures-util = "0.3"
pretty_env_logger = "0.4"
cron = "0.11.0"
rcon = { version = "0.6.0", features = ["rt-tokio"] }
rcon = { version = "0.6.0", features = ["rt-tokio"] }
tracing = "0.1.33"
tracing-subscriber = "0.3.11"

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")