scheduling

This commit is contained in:
Robin Appelman 2021-03-28 19:23:54 +02:00
commit da0f12fb91
8 changed files with 456 additions and 39 deletions

213
Cargo.lock generated
View file

@ -6,6 +6,15 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aho-corasick"
version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "arrayref" name = "arrayref"
version = "0.3.6" version = "0.3.6"
@ -29,6 +38,17 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.0.1" version = "1.0.1"
@ -140,6 +160,16 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "cron"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "628a3464535cee4e75af89e8c293bab926deaddfa166553b75029066c846be3f"
dependencies = [
"chrono",
"nom",
]
[[package]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.3" version = "0.8.3"
@ -196,12 +226,14 @@ dependencies = [
"chrono", "chrono",
"futures-util", "futures-util",
"petname", "petname",
"pretty_env_logger",
"reqwest", "reqwest",
"serde", "serde",
"thiserror", "thiserror",
"thrussh", "thrussh",
"thrussh-keys", "thrussh-keys",
"tokio", "tokio",
"tokio-cron-scheduler",
"toml", "toml",
] ]
@ -220,6 +252,19 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "env_logger"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]] [[package]]
name = "flate2" name = "flate2"
version = "1.0.20" version = "1.0.20"
@ -374,7 +419,7 @@ checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"wasi 0.10.0+wasi-snapshot-preview1", "wasi 0.10.2+wasi-snapshot-preview1",
] ]
[[package]] [[package]]
@ -445,6 +490,15 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47"
[[package]]
name = "humantime"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
dependencies = [
"quick-error",
]
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "0.14.5" version = "0.14.5"
@ -505,6 +559,15 @@ dependencies = [
"hashbrown", "hashbrown",
] ]
[[package]]
name = "instant"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "ipnet" name = "ipnet"
version = "2.3.0" version = "2.3.0"
@ -558,6 +621,15 @@ dependencies = [
"pkg-config", "pkg-config",
] ]
[[package]]
name = "lock_api"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312"
dependencies = [
"scopeguard",
]
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.14" version = "0.4.14"
@ -617,6 +689,15 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "nom"
version = "4.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c349f68f25f596b9f44cf0e7c69752a5c633b0550c3ff849518bfba0233774a"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "ntapi" name = "ntapi"
version = "0.3.6" version = "0.3.6"
@ -699,6 +780,31 @@ dependencies = [
"vcpkg", "vcpkg",
] ]
[[package]]
name = "parking_lot"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
dependencies = [
"instant",
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018"
dependencies = [
"cfg-if",
"instant",
"libc",
"redox_syscall 0.2.5",
"smallvec",
"winapi",
]
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.1.0" version = "2.1.0"
@ -760,6 +866,16 @@ version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
[[package]]
name = "pretty_env_logger"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d"
dependencies = [
"env_logger",
"log",
]
[[package]] [[package]]
name = "proc-macro-hack" name = "proc-macro-hack"
version = "0.5.19" version = "0.5.19"
@ -781,6 +897,12 @@ dependencies = [
"unicode-xid", "unicode-xid",
] ]
[[package]]
name = "quick-error"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.9" version = "1.0.9"
@ -836,6 +958,15 @@ version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
[[package]]
name = "redox_syscall"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9"
dependencies = [
"bitflags",
]
[[package]] [[package]]
name = "redox_users" name = "redox_users"
version = "0.3.5" version = "0.3.5"
@ -843,10 +974,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d"
dependencies = [ dependencies = [
"getrandom 0.1.16", "getrandom 0.1.16",
"redox_syscall", "redox_syscall 0.1.57",
"rust-argon2", "rust-argon2",
] ]
[[package]]
name = "regex"
version = "1.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548"
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.11.2" version = "0.11.2"
@ -929,6 +1077,12 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]] [[package]]
name = "sct" name = "sct"
version = "0.6.0" version = "0.6.0"
@ -997,6 +1151,12 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
[[package]]
name = "smallvec"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.4.0" version = "0.4.0"
@ -1024,6 +1184,15 @@ dependencies = [
"unicode-xid", "unicode-xid",
] ]
[[package]]
name = "termcolor"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
dependencies = [
"winapi-util",
]
[[package]] [[package]]
name = "textwrap" name = "textwrap"
version = "0.11.0" version = "0.11.0"
@ -1111,12 +1280,11 @@ dependencies = [
[[package]] [[package]]
name = "time" name = "time"
version = "0.1.44" version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
dependencies = [ dependencies = [
"libc", "libc",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi", "winapi",
] ]
@ -1148,12 +1316,25 @@ dependencies = [
"mio", "mio",
"num_cpus", "num_cpus",
"once_cell", "once_cell",
"parking_lot",
"pin-project-lite", "pin-project-lite",
"signal-hook-registry", "signal-hook-registry",
"tokio-macros", "tokio-macros",
"winapi", "winapi",
] ]
[[package]]
name = "tokio-cron-scheduler"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6af42ec81010dbf80a8762206e4cf5273ef291b91f203b48e250130ca4289392"
dependencies = [
"chrono",
"cron",
"tokio",
"uuid",
]
[[package]] [[package]]
name = "tokio-macros" name = "tokio-macros"
version = "1.1.0" version = "1.1.0"
@ -1279,6 +1460,15 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "uuid"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
dependencies = [
"getrandom 0.2.2",
]
[[package]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.11" version = "0.2.11"
@ -1303,9 +1493,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.10.0+wasi-snapshot-preview1" version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
@ -1420,6 +1610,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]] [[package]]
name = "winapi-x86_64-pc-windows-gnu" name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0" version = "0.4.0"

View file

@ -17,3 +17,5 @@ petname = "1"
thrussh = "0.32" thrussh = "0.32"
thrussh-keys = "0.20" thrussh-keys = "0.20"
futures-util = "0.3" futures-util = "0.3"
pretty_env_logger = "0.4"
tokio-cron-scheduler = "0.2"

View file

@ -1,14 +0,0 @@
[server]
rcon = "xxxx"
password = "xxxx"
demostf_key = "xxxx" # optional
logstf_key = "xxxx" # optional
config_league = "etf2l" # optional, defaults to etf2l
config_mode = "6v6" # optional, defaults to 6v6
name = "MyCoolServer" # optional, defaults to Spire
tv_name = "MyCoolSTV" # optional, defaults to SpireTV
[vultr]
api_key = "xxx"
region = "ams" # see https://api.vultr.com/v2/regions for a list of regions
plan = "vc2-1c-2gb" # optional, defaults to vc2-1c-2gb (2GB, $10/month) see https://api.vultr.com/v2/plans for a lis of plan

28
config.sample.toml Normal file
View file

@ -0,0 +1,28 @@
[server] # configuration details for the tf2 server
rcon = "xxx" # rcon password
password = "xxx" # server password
demostf_key = "xxx" # api key for demos.tf
logstf_key = "xxx" # api key for logs.tf
config_league = "etf2l" # etf2l or #ugc. optional, defaults to "etf2l"
config_mode = "6v6" # 6v6 or 9v9, defaults to "6v6"
name = "Spire" # server name. optional, defaults to "Spire"
tv_name = "SpireTV" # stv name. optional, defaults to "SpireTV"
image = "spiretf/docker-spire-server" # docker image for the tf2 server. optional, defaults to "spiretf/docker-spire-server"
[vultr]
api_key = "xxx"
region = "ams" # see https://api.vultr.com/v2/regions for a list of regions
plan = "vc2-1c-2gb" # optional, defaults to vc2-1c-2gb (2GB, $10/month) see https://api.vultr.com/v2/plans for a lis of plan
[dyndns] # optional dyndns2 details
update_url = "https://update.eurodyndns.org/update/" # Update url for dyndns2
hostname = "nipple.tf"
username = "xxx"
password = "xxx"
[schedule]
# sec min hour day-of-month month day-of-week
start = "0 0 17 * * Sun" # cron string to start the server on
stop = "0 0 23 * * Sun" # cron string to stop the server on
# note that the above is in UTC

View file

@ -59,7 +59,7 @@ impl From<reqwest::Error> for ResponseError {
pub type Result<T, E = CloudError> = std::result::Result<T, E>; pub type Result<T, E = CloudError> = std::result::Result<T, E>;
#[async_trait] #[async_trait]
pub trait Cloud { pub trait Cloud: Send + Sync + 'static {
/// List all running servers on this cloud /// List all running servers on this cloud
async fn list(&self) -> Result<Vec<Server>>; async fn list(&self) -> Result<Vec<Server>>;
/// Create a new server with the given parameter /// Create a new server with the given parameter

View file

@ -4,6 +4,7 @@ use camino::Utf8PathBuf;
use serde::Deserialize; use serde::Deserialize;
use std::fs::read_to_string; use std::fs::read_to_string;
use std::path::Path; use std::path::Path;
use std::sync::Arc;
use thiserror::Error; use thiserror::Error;
#[derive(Debug, Error)] #[derive(Debug, Error)]
@ -31,6 +32,8 @@ impl From<toml::de::Error> for TomlError {
pub struct Config { pub struct Config {
pub vultr: Option<VultrConfig>, pub vultr: Option<VultrConfig>,
pub server: ServerConfig, pub server: ServerConfig,
pub dyndns: Option<DynDnsConfig>,
pub schedule: ScheduleConfig,
} }
impl Config { impl Config {
@ -39,9 +42,9 @@ impl Config {
Ok(toml::from_str(&content).map_err(TomlError::from)?) Ok(toml::from_str(&content).map_err(TomlError::from)?)
} }
pub fn cloud(&self) -> Result<Box<dyn Cloud>, ConfigError> { pub fn cloud(&self) -> Result<Arc<dyn Cloud>, ConfigError> {
if let Some(vultr) = &self.vultr { if let Some(vultr) = &self.vultr {
Ok(Box::new(Vultr::new( Ok(Arc::new(Vultr::new(
vultr.api_key.clone(), vultr.api_key.clone(),
vultr.region.clone(), vultr.region.clone(),
vultr.plan.clone(), vultr.plan.clone(),
@ -103,3 +106,17 @@ pub struct VultrConfig {
fn vultr_default_plan() -> String { fn vultr_default_plan() -> String {
String::from("vc2-1c-2gb") String::from("vc2-1c-2gb")
} }
#[derive(Deserialize, Debug)]
pub struct DynDnsConfig {
pub update_url: String,
pub hostname: String,
pub username: String,
pub password: String,
}
#[derive(Deserialize, Debug)]
pub struct ScheduleConfig {
pub start: String,
pub stop: String,
}

85
src/dns.rs Normal file
View file

@ -0,0 +1,85 @@
use reqwest::{Client, StatusCode};
use serde::Serialize;
use std::net::IpAddr;
use thiserror::Error;
pub type Result<T, E = DynDnsError> = std::result::Result<T, E>;
#[derive(Debug, Error)]
pub enum DynDnsError {
#[error("Invalid credentials")]
Unauthorized,
#[error("Network error: {0}")]
Network(#[from] NetworkError),
#[error("Network response from server: {0}")]
InvalidResponse(String),
#[error("Domain belongs to another user")]
NotYourDomain,
#[error("Invalid hostname")]
InvalidHostname,
}
impl DynDnsError {
fn from_status_code(status: StatusCode) -> Result<()> {
if status == StatusCode::UNAUTHORIZED || status == StatusCode::FORBIDDEN {
return Err(DynDnsError::Unauthorized);
}
Ok(())
}
}
/// Intentionally opaque error
#[derive(Debug, Error)]
#[error("{0}")]
pub struct NetworkError(reqwest::Error);
pub struct DynDnsClient {
client: Client,
update_url: String,
username: String,
password: String,
}
impl DynDnsClient {
pub fn new(update_url: String, username: String, password: String) -> Self {
DynDnsClient {
client: Client::new(),
update_url,
username,
password,
}
}
pub async fn update(&self, hostname: &str, ip: IpAddr) -> Result<()> {
let response = self
.client
.get(&self.update_url)
.basic_auth(&self.username, Some(&self.password))
.query(&DynDnsParams { hostname, ip })
.send()
.await
.map_err(NetworkError)?;
let status = response.status();
DynDnsError::from_status_code(status)?;
let text = response.text().await.map_err(NetworkError)?;
match text.as_str() {
"badauth" => Err(DynDnsError::Unauthorized),
"!yours" => Err(DynDnsError::NotYourDomain),
"nochg" => Ok(()),
"good" => Ok(()),
"notfqdn" => Err(DynDnsError::InvalidHostname),
"nohost" => Err(DynDnsError::InvalidHostname),
"numhost" => Err(DynDnsError::InvalidHostname),
_ => Err(DynDnsError::InvalidResponse(text)),
}
}
}
#[derive(Serialize)]
struct DynDnsParams<'a> {
hostname: &'a str,
#[serde(rename = "myip")]
ip: IpAddr,
}

View file

@ -1,18 +1,20 @@
use std::env::args; use crate::cloud::{Cloud, CloudError};
use thiserror::Error;
use ssh::SshSession;
use crate::cloud::CloudError;
use crate::config::{Config, ConfigError, ServerConfig}; use crate::config::{Config, ConfigError, ServerConfig};
use crate::dns::{DynDnsClient, DynDnsError};
use crate::ssh::SshError; use crate::ssh::SshError;
use ssh::SshSession;
use std::env::args;
use std::sync::{Arc, Mutex};
use std::time::Duration; use std::time::Duration;
use thiserror::Error;
use tokio::task::{spawn, JoinError};
use tokio::time::sleep; use tokio::time::sleep;
use tokio_cron_scheduler::{Job, JobScheduler};
pub mod cloud; mod cloud;
pub mod config; mod config;
pub mod ssh; mod dns;
mod ssh;
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
@ -24,6 +26,30 @@ pub enum Error {
Ssh(#[from] SshError), Ssh(#[from] SshError),
#[error("Setup command returned an error: {0}")] #[error("Setup command returned an error: {0}")]
SetupError(String), SetupError(String),
#[error("Error while updating dyndns: {0}")]
DynDns(#[from] DynDnsError),
#[error("Already running")]
AlreadyRunning,
#[error("{0}")]
Schedule(ScheduleError),
}
#[derive(Debug, Error)]
#[error("{0}")]
pub struct ScheduleError(ScheduleErrorImpl);
#[derive(Debug, Error)]
enum ScheduleErrorImpl {
#[error("Error setting up schedule")]
Schedule(String),
#[error("Error running schedule")]
Join(JoinError),
}
impl From<ScheduleErrorImpl> for Error {
fn from(e: ScheduleErrorImpl) -> Self {
Error::Schedule(ScheduleError(e))
}
} }
async fn setup(ssh: &mut SshSession, config: &ServerConfig) -> Result<(), Error> { async fn setup(ssh: &mut SshSession, config: &ServerConfig) -> Result<(), Error> {
@ -79,6 +105,8 @@ async fn setup(ssh: &mut SshSession, config: &ServerConfig) -> Result<(), Error>
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), Error> { async fn main() -> Result<(), Error> {
pretty_env_logger::init();
let mut args = args(); let mut args = args();
let bin = args.next().unwrap(); let bin = args.next().unwrap();
@ -91,12 +119,85 @@ async fn main() -> Result<(), Error> {
}; };
let cloud = config.cloud()?; let cloud = config.cloud()?;
let mut sched = JobScheduler::new();
let server_id: Arc<Mutex<Option<String>>> = Arc::default();
sched
.add(stop_job(cloud.clone(), &config, server_id.clone()))
.map_err(|e| ScheduleErrorImpl::Schedule(format!("{:#}", e)))?;
sched
.add(start_job(cloud, config, server_id))
.map_err(|e| ScheduleErrorImpl::Schedule(format!("{:#}", e)))?;
sched.start().await.map_err(ScheduleErrorImpl::Join)?;
Ok(())
}
fn stop_job(_cloud: Arc<dyn Cloud>, config: &Config, server_id: Arc<Mutex<Option<String>>>) -> Job {
Job::new(&config.schedule.stop, move |_uuid, _l| {
let server_id = server_id.clone();
spawn(async move {
println!("Stopping server");
if let Some(id) = server_id.lock().unwrap().take() {
println!("Would have killed {}", id);
// match cloud.kill(&id).await {
// Ok(_) => {}
// Err(e) => eprintln!("{:#}", e),
// };
}
});
})
.unwrap()
}
fn start_job(cloud: Arc<dyn Cloud>, config: Config, server_id: Arc<Mutex<Option<String>>>) -> Job {
let schedule = config.schedule.start.clone();
let config = Arc::new(config);
Job::new(&schedule, move |_uuid, _l| {
let cloud = cloud.clone();
let config = config.clone();
let server_id = server_id.clone();
spawn(async move {
let cloud = cloud.as_ref();
println!("Starting server");
match start(cloud, &config).await {
Ok(id) => *server_id.lock().unwrap() = Some(id),
Err(e) => eprintln!("{:#}", e),
};
});
})
.unwrap()
}
async fn start(cloud: &dyn Cloud, config: &Config) -> Result<String, Error> {
let list = cloud.list().await?;
if !list.is_empty() {
return Err(Error::AlreadyRunning);
}
let created = cloud.spawn().await?; let created = cloud.spawn().await?;
let server = cloud.wait_for_ip(&created.id).await?; let server = cloud.wait_for_ip(&created.id).await?;
println!("Server is booting"); println!("Server is booting");
println!(" IP: {}", server.ip); println!(" IP: {}", server.ip);
println!(" Password: {}", created.password); println!(" Root Password: {}", created.password);
let connect_host = if let Some(dns_config) = config.dyndns.as_ref() {
let dns = DynDnsClient::new(
dns_config.update_url.to_string(),
dns_config.username.to_string(),
dns_config.password.to_string(),
);
println!(
"Updating DynDNS entry for {} to {}",
dns_config.hostname, server.ip
);
dns.update(&dns_config.hostname, server.ip).await?;
dns_config.hostname.to_string()
} else {
format!("{}", server.ip)
};
let mut ssh = SshSession::open(server.ip, &created.password).await?; let mut ssh = SshSession::open(server.ip, &created.password).await?;
setup(&mut ssh, &config.server).await?; setup(&mut ssh, &config.server).await?;
@ -106,8 +207,7 @@ async fn main() -> Result<(), Error> {
println!("Connect using"); println!("Connect using");
println!( println!(
" connect {}; password {}", " connect {}; password {}",
server.ip, config.server.password connect_host, config.server.password
); );
Ok(server.id)
Ok(())
} }