start porting dnapi
This commit is contained in:
parent
05d452bc50
commit
26ef187ff3
|
@ -154,6 +154,16 @@ version = "0.21.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
|
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64-serde"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba368df5de76a5bea49aaf0cf1b39ccfbbef176924d1ba5db3e4135216cbe3c7"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.21.0",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64ct"
|
name = "base64ct"
|
||||||
version = "1.5.3"
|
version = "1.5.3"
|
||||||
|
@ -2421,6 +2431,7 @@ name = "tfclient"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.21.0",
|
"base64 0.21.0",
|
||||||
|
"base64-serde",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"ctrlc",
|
"ctrlc",
|
||||||
|
|
|
@ -23,7 +23,7 @@ reqwest = { version = "0.11.16", features = ["blocking"] }
|
||||||
base64 = "0.21.0"
|
base64 = "0.21.0"
|
||||||
chrono = "0.4.24"
|
chrono = "0.4.24"
|
||||||
ipnet = "2.7.1"
|
ipnet = "2.7.1"
|
||||||
|
base64-serde = "0.7.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
serde = { version = "1.0.157", features = ["derive"] }
|
serde = { version = "1.0.157", features = ["derive"] }
|
||||||
|
|
|
@ -1,61 +1,13 @@
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
use base64_serde::base64_serde_type;
|
||||||
use log::trace;
|
use log::trace;
|
||||||
use reqwest::blocking::Client;
|
use reqwest::blocking::Client;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
const ENDPOINT_V1: &str = "/v1/dnclient";
|
||||||
pub struct EnrollRequest {
|
|
||||||
pub code: String,
|
|
||||||
#[serde(rename = "dhPubkey")]
|
|
||||||
pub dh_pubkey: String,
|
|
||||||
#[serde(rename = "edPubkey")]
|
|
||||||
pub ed_pubkey: String,
|
|
||||||
pub timestamp: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
base64_serde_type!(Base64Standard, base64::engine::general_purpose::STANDARD);
|
||||||
pub struct EnrollResponseMetadata {}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct EnrollResponseOrganization {
|
|
||||||
pub id: String,
|
|
||||||
pub name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct EnrollResponseData {
|
|
||||||
pub config: String,
|
|
||||||
#[serde(rename = "hostID")]
|
|
||||||
pub host_id: String,
|
|
||||||
pub counter: i64,
|
|
||||||
#[serde(rename = "trustedKeys")]
|
|
||||||
pub trusted_keys: String,
|
|
||||||
pub organization: EnrollResponseOrganization,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct EnrollResponse {
|
|
||||||
pub data: EnrollResponseData,
|
|
||||||
pub metadata: EnrollResponseMetadata,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
#[serde(untagged)]
|
|
||||||
pub enum APIResponse {
|
|
||||||
Error(EnrollError),
|
|
||||||
Success(EnrollResponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct EnrollError {
|
|
||||||
pub errors: Vec<EnrollErrorSingular>
|
|
||||||
}
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct EnrollErrorSingular {
|
|
||||||
pub code: String,
|
|
||||||
pub message: String
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn enroll(server: &Url, request: &EnrollRequest) -> Result<APIResponse, Box<dyn Error>> {
|
pub fn enroll(server: &Url, request: &EnrollRequest) -> Result<APIResponse, Box<dyn Error>> {
|
||||||
let endpoint = server.join("/v2/enroll")?;
|
let endpoint = server.join("/v2/enroll")?;
|
||||||
|
@ -67,4 +19,127 @@ pub fn enroll(server: &Url, request: &EnrollRequest) -> Result<APIResponse, Box<
|
||||||
|
|
||||||
let resp = client.post(endpoint).body(text).send()?;
|
let resp = client.post(endpoint).body(text).send()?;
|
||||||
Ok(resp.json()?)
|
Ok(resp.json()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct RequestV1 {
|
||||||
|
pub version: i32,
|
||||||
|
#[serde(rename = "hostID")]
|
||||||
|
pub host_id: String,
|
||||||
|
pub counter: u32,
|
||||||
|
pub message: String,
|
||||||
|
#[serde(with = "Base64Standard")]
|
||||||
|
pub signature: Vec<u8>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct RequestWrapper {
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub message_type: String,
|
||||||
|
#[serde(with = "Base64Standard")]
|
||||||
|
pub value: Vec<u8>,
|
||||||
|
pub timestamp: String
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct SignedResponseWrapper {
|
||||||
|
pub data: SignedResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct SignedResponse {
|
||||||
|
pub version: i32,
|
||||||
|
#[serde(with = "Base64Standard")]
|
||||||
|
pub message: Vec<u8>,
|
||||||
|
#[serde(with = "Base64Standard")]
|
||||||
|
pub signature: Vec<u8>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct CheckForUpdateResponseWrapper {
|
||||||
|
pub data: CheckForUpdateResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct CheckForUpdateResponse {
|
||||||
|
#[serde(rename = "updateAvailable")]
|
||||||
|
pub update_available: bool
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct DoUpdateRequest {
|
||||||
|
#[serde(rename = "edPubkeyPEM")]
|
||||||
|
#[serde(with = "Base64Standard")]
|
||||||
|
pub ed_pubkey_pem: Vec<u8>,
|
||||||
|
#[serde(rename = "dhPubkeyPEM")]
|
||||||
|
#[serde(with = "Base64Standard")]
|
||||||
|
pub dh_pubkey_pem: Vec<u8>,
|
||||||
|
#[serde(with = "Base64Standard")]
|
||||||
|
pub nonce: Vec<u8>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct DoUpdateResponse {
|
||||||
|
#[serde(with = "Base64Standard")]
|
||||||
|
pub config: Vec<u8>,
|
||||||
|
pub counter: u32,
|
||||||
|
#[serde(with = "Base64Standard")]
|
||||||
|
pub nonce: Vec<u8>,
|
||||||
|
#[serde(rename = "trustedKeys")]
|
||||||
|
#[serde(with = "Base64Standard")]
|
||||||
|
pub trusted_keys: Vec<u8>
|
||||||
|
}
|
||||||
|
|
||||||
|
const ENROLL_ENDPOINT: &str = "/v2/enroll";
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct EnrollRequest {
|
||||||
|
pub code: String,
|
||||||
|
#[serde(rename = "dhPubkey")]
|
||||||
|
#[serde(with = "Base64Standard")]
|
||||||
|
pub dh_pubkey: Vec<u8>,
|
||||||
|
#[serde(rename = "edPubkey")]
|
||||||
|
#[serde(with = "Base64Standard")]
|
||||||
|
pub ed_pubkey: Vec<u8>,
|
||||||
|
pub timestamp: String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum EnrollResponse {
|
||||||
|
Success {
|
||||||
|
data: EnrollResponseData
|
||||||
|
},
|
||||||
|
Error {
|
||||||
|
errors: APIErrors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct EnrollResponseData {
|
||||||
|
#[serde(with = "Base64Standard")]
|
||||||
|
pub config: Vec<u8>,
|
||||||
|
#[serde(rename = "hostID")]
|
||||||
|
pub host_id: String,
|
||||||
|
pub counter: u32,
|
||||||
|
#[serde(rename = "trustedKeys")]
|
||||||
|
#[serde(with = "Base64Standard")]
|
||||||
|
pub trusted_keys: Vec<u8>,
|
||||||
|
pub organization: EnrollResponseDataOrg
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct EnrollResponseDataOrg {
|
||||||
|
pub id: String,
|
||||||
|
pub name: String
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct APIError {
|
||||||
|
pub code: String,
|
||||||
|
pub message: String,
|
||||||
|
pub path: Option<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type APIErrors = Vec<APIError>;
|
|
@ -3,7 +3,6 @@ use base64::Engine;
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use trifid_pki::ca::NebulaCAPool;
|
|
||||||
use trifid_pki::cert::{serialize_ed25519_public, serialize_x25519_public};
|
use trifid_pki::cert::{serialize_ed25519_public, serialize_x25519_public};
|
||||||
use trifid_pki::ed25519_dalek::{SecretKey, SigningKey};
|
use trifid_pki::ed25519_dalek::{SecretKey, SigningKey};
|
||||||
use trifid_pki::rand_core::OsRng;
|
use trifid_pki::rand_core::OsRng;
|
||||||
|
@ -14,7 +13,8 @@ use crate::daemon::ThreadMessageSender;
|
||||||
|
|
||||||
pub enum APIWorkerMessage {
|
pub enum APIWorkerMessage {
|
||||||
Shutdown,
|
Shutdown,
|
||||||
Enroll { code: String }
|
Enroll { code: String },
|
||||||
|
Timer
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apiworker_main(config: TFClientConfig, instance: String, url: String, _transmitters: ThreadMessageSender, rx: Receiver<APIWorkerMessage>) {
|
pub fn apiworker_main(config: TFClientConfig, instance: String, url: String, _transmitters: ThreadMessageSender, rx: Receiver<APIWorkerMessage>) {
|
||||||
|
@ -68,6 +68,22 @@ pub fn apiworker_main(config: TFClientConfig, instance: String, url: String, _tr
|
||||||
info!("recv on command socket: shutdown, stopping");
|
info!("recv on command socket: shutdown, stopping");
|
||||||
break;
|
break;
|
||||||
},
|
},
|
||||||
|
APIWorkerMessage::Timer => {
|
||||||
|
info!("updating config");
|
||||||
|
let mut cdata = match load_cdata(&instance) {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
error!("error in api worker thread: {}", e);
|
||||||
|
error!("APIWorker exiting with error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if cdata.host_id.is_none() {
|
||||||
|
info!("not enrolled, cannot perform config update");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
APIWorkerMessage::Enroll { code } => {
|
APIWorkerMessage::Enroll { code } => {
|
||||||
info!("recv on command socket: enroll {}", code);
|
info!("recv on command socket: enroll {}", code);
|
||||||
let mut cdata = match load_cdata(&instance) {
|
let mut cdata = match load_cdata(&instance) {
|
||||||
|
|
|
@ -8,6 +8,7 @@ use crate::config::load_config;
|
||||||
use crate::main;
|
use crate::main;
|
||||||
use crate::nebulaworker::{nebulaworker_main, NebulaWorkerMessage};
|
use crate::nebulaworker::{nebulaworker_main, NebulaWorkerMessage};
|
||||||
use crate::socketworker::{socketworker_main, SocketWorkerMessage};
|
use crate::socketworker::{socketworker_main, SocketWorkerMessage};
|
||||||
|
use crate::timerworker::{timer_main, TimerWorkerMessage};
|
||||||
use crate::util::check_server_url;
|
use crate::util::check_server_url;
|
||||||
|
|
||||||
pub fn daemon_main(name: String, server: String) {
|
pub fn daemon_main(name: String, server: String) {
|
||||||
|
@ -28,11 +29,13 @@ pub fn daemon_main(name: String, server: String) {
|
||||||
let (tx_api, rx_api) = mpsc::channel::<APIWorkerMessage>();
|
let (tx_api, rx_api) = mpsc::channel::<APIWorkerMessage>();
|
||||||
let (tx_socket, rx_socket) = mpsc::channel::<SocketWorkerMessage>();
|
let (tx_socket, rx_socket) = mpsc::channel::<SocketWorkerMessage>();
|
||||||
let (tx_nebula, rx_nebula) = mpsc::channel::<NebulaWorkerMessage>();
|
let (tx_nebula, rx_nebula) = mpsc::channel::<NebulaWorkerMessage>();
|
||||||
|
let (tx_timer, rx_timer) = mpsc::channel::<TimerWorkerMessage>();
|
||||||
|
|
||||||
let transmitter = ThreadMessageSender {
|
let transmitter = ThreadMessageSender {
|
||||||
socket_thread: tx_socket,
|
socket_thread: tx_socket,
|
||||||
api_thread: tx_api,
|
api_thread: tx_api,
|
||||||
nebula_thread: tx_nebula
|
nebula_thread: tx_nebula,
|
||||||
|
timer_thread: tx_timer,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mainthread_transmitter = transmitter.clone();
|
let mainthread_transmitter = transmitter.clone();
|
||||||
|
@ -59,6 +62,12 @@ pub fn daemon_main(name: String, server: String) {
|
||||||
error!("Error sending shutdown message to socket worker thread: {}", e);
|
error!("Error sending shutdown message to socket worker thread: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
match mainthread_transmitter.timer_thread.send(TimerWorkerMessage::Shutdown) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => {
|
||||||
|
error!("Error sending shutdown message to timer worker thread: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}) {
|
}) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -69,8 +78,6 @@ pub fn daemon_main(name: String, server: String) {
|
||||||
|
|
||||||
info!("Starting API thread...");
|
info!("Starting API thread...");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let config_api = config.clone();
|
let config_api = config.clone();
|
||||||
let transmitter_api = transmitter.clone();
|
let transmitter_api = transmitter.clone();
|
||||||
let name_api = name.clone();
|
let name_api = name.clone();
|
||||||
|
@ -86,6 +93,12 @@ pub fn daemon_main(name: String, server: String) {
|
||||||
nebulaworker_main(config_nebula, transmitter_nebula, rx_nebula);
|
nebulaworker_main(config_nebula, transmitter_nebula, rx_nebula);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
info!("Starting timer thread...");
|
||||||
|
let timer_transmitter = transmitter.clone();
|
||||||
|
let timer_thread = thread::spawn(move || {
|
||||||
|
timer_main(timer_transmitter, rx_timer);
|
||||||
|
});
|
||||||
|
|
||||||
info!("Starting socket worker thread...");
|
info!("Starting socket worker thread...");
|
||||||
let socket_thread = thread::spawn(move || {
|
let socket_thread = thread::spawn(move || {
|
||||||
socketworker_main(config, name.clone(), transmitter, rx_socket);
|
socketworker_main(config, name.clone(), transmitter, rx_socket);
|
||||||
|
@ -111,6 +124,16 @@ pub fn daemon_main(name: String, server: String) {
|
||||||
}
|
}
|
||||||
info!("API thread exited");
|
info!("API thread exited");
|
||||||
|
|
||||||
|
info!("Waiting for timer thread to exit...");
|
||||||
|
match timer_thread.join() {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(_) => {
|
||||||
|
error!("Error waiting for timer thread to exit.");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info!("Timer thread exited");
|
||||||
|
|
||||||
info!("Waiting for Nebula thread to exit...");
|
info!("Waiting for Nebula thread to exit...");
|
||||||
match nebula_thread.join() {
|
match nebula_thread.join() {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
|
@ -128,5 +151,6 @@ pub fn daemon_main(name: String, server: String) {
|
||||||
pub struct ThreadMessageSender {
|
pub struct ThreadMessageSender {
|
||||||
pub socket_thread: Sender<SocketWorkerMessage>,
|
pub socket_thread: Sender<SocketWorkerMessage>,
|
||||||
pub api_thread: Sender<APIWorkerMessage>,
|
pub api_thread: Sender<APIWorkerMessage>,
|
||||||
pub nebula_thread: Sender<NebulaWorkerMessage>
|
pub nebula_thread: Sender<NebulaWorkerMessage>,
|
||||||
|
pub timer_thread: Sender<TimerWorkerMessage>
|
||||||
}
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
/// Essentially a direct port of https://github.com/DefinedNet/dnapi
|
|
@ -25,6 +25,7 @@ pub mod apiworker;
|
||||||
pub mod socketworker;
|
pub mod socketworker;
|
||||||
pub mod api;
|
pub mod api;
|
||||||
pub mod socketclient;
|
pub mod socketclient;
|
||||||
|
pub mod timerworker;
|
||||||
|
|
||||||
pub mod nebula_bin {
|
pub mod nebula_bin {
|
||||||
include!(concat!(env!("OUT_DIR"), "/nebula.bin.rs"));
|
include!(concat!(env!("OUT_DIR"), "/nebula.bin.rs"));
|
||||||
|
|
|
@ -12,6 +12,7 @@ use crate::apiworker::APIWorkerMessage;
|
||||||
use crate::config::{load_cdata, TFClientConfig};
|
use crate::config::{load_cdata, TFClientConfig};
|
||||||
use crate::daemon::ThreadMessageSender;
|
use crate::daemon::ThreadMessageSender;
|
||||||
use crate::nebulaworker::NebulaWorkerMessage;
|
use crate::nebulaworker::NebulaWorkerMessage;
|
||||||
|
use crate::timerworker::TimerWorkerMessage;
|
||||||
|
|
||||||
pub enum SocketWorkerMessage {
|
pub enum SocketWorkerMessage {
|
||||||
Shutdown
|
Shutdown
|
||||||
|
@ -214,6 +215,12 @@ fn senthello_handle(client: &mut Client, transmitter: &ThreadMessageSender, comm
|
||||||
error!("Error sending shutdown message to socket worker thread: {}", e);
|
error!("Error sending shutdown message to socket worker thread: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
match transmitter.timer_thread.send(TimerWorkerMessage::Shutdown) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => {
|
||||||
|
error!("Error sending shutdown message to timer worker thread: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
JsonMessage::GetHostID {} => {
|
JsonMessage::GetHostID {} => {
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
use std::ops::Add;
|
||||||
|
use std::sync::mpsc::{Receiver, TryRecvError};
|
||||||
|
use std::time::{Duration, SystemTime};
|
||||||
|
use log::{error, info};
|
||||||
|
use crate::apiworker::APIWorkerMessage;
|
||||||
|
use crate::daemon::ThreadMessageSender;
|
||||||
|
|
||||||
|
pub enum TimerWorkerMessage {
|
||||||
|
Shutdown
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn timer_main(tx: ThreadMessageSender, rx: Receiver<TimerWorkerMessage>) {
|
||||||
|
let mut api_reload_timer = SystemTime::now().add(Duration::from_secs(60));
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match rx.try_recv() {
|
||||||
|
Ok(msg) => {
|
||||||
|
match msg {
|
||||||
|
TimerWorkerMessage::Shutdown => {
|
||||||
|
info!("recv on command socket: shutdown, stopping");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
match e {
|
||||||
|
TryRecvError::Empty => {}
|
||||||
|
TryRecvError::Disconnected => {
|
||||||
|
error!("timerworker command socket disconnected, shutting down to prevent orphaning");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if SystemTime::now().gt(&api_reload_timer) {
|
||||||
|
info!("Timer triggered: API_RELOAD_TIMER");
|
||||||
|
api_reload_timer = SystemTime::now().add(Duration::from_secs(60));
|
||||||
|
match tx.api_thread.send(APIWorkerMessage::Timer) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => {
|
||||||
|
error!("Error sending timer message to api worker thread: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue