Files
confetti/src/main.rs
T
uelen bcfd328c04 Less unwraps for certificates
Oops missed a few. Should print real errors now
2026-06-01 11:51:10 -07:00

272 lines
8.0 KiB
Rust

// suppress warnings from unimplemented paths
// remove once actually done
#![allow(dead_code)]
mod client;
mod config;
mod connection;
mod db;
mod state;
use diesel_async::pooled_connection::AsyncDieselConnectionManager;
use diesel_async::pooled_connection::deadpool::Pool;
use quinn::rustls::pki_types::{PrivateKeyDer,CertificateDer,pem::PemObject};
use quinn::Endpoint;
use std::io;
use std::fs;
use std::net::{IpAddr,SocketAddr};
use std::process::ExitCode;
use std::str::FromStr;
use std::sync::Arc;
use serde::{Deserialize,Serialize};
use tracing::{error,instrument,warn,debug,info,Level};
use tokio::sync::{RwLock,broadcast,mpsc};
use crate::config::Config;
use crate::state::{State,StateError};
#[derive(Hash,Eq,PartialEq,Clone,Serialize,Deserialize,Debug)]
pub struct Coordinate(Vec<i64>);
impl Coordinate {
pub fn to_roomid(self) -> fedichat::RoomId {
fedichat::RoomId{coordinates: self.0}
}
}
impl From<fedichat::RoomId> for Coordinate {
fn from(other: fedichat::RoomId) -> Coordinate {
Coordinate(other.coordinates)
}
}
#[tokio::main]
#[instrument]
async fn main() -> ExitCode {
// NOTE: This doesn't work as you can only initialize the global logger once
// Initial logger so we have something during config
//tracing::subscriber::set_global_default(
// tracing_subscriber::fmt().with_max_level(Level::WARN).finish()
//).expect("Failed to setup logger");
// Read in config
let config = match Config::load() {
Ok(c) => c,
Err(e) => {
eprintln!("Problem while reading config file");
eprintln!("{:?}",e);
return ExitCode::FAILURE;
}
};
let level = match config.loglevel {
Some(ref s) => {
let loglevel = s.to_lowercase();
match loglevel.as_str() {
"trace" => Level::TRACE,
"debug" => Level::DEBUG,
"info" => Level::INFO,
"warn" => Level::WARN,
"error" => Level::ERROR,
_ => {
eprintln!("Invalid loglevel in config: {}",&loglevel);
Level::INFO
},
}
},
// Default to info level
None => Level::INFO
};
tracing::subscriber::set_global_default(
tracing_subscriber::fmt().with_max_level(level).finish()
).expect("Failed to setup logger");
// Check to make sure media directory exists
match std::fs::exists(&config.media_directory) {
Ok(true) => {},
// NOTE: maybe shouldnt shadow this error
_ => {
error!("Media directory {} does not exist. Check to make sure it is a directory and is writable.",config.media_directory);
return ExitCode::FAILURE;
}
}
// Set up database connection
let db_string = format!("postgres://{}:{}@{}/{}",config.database.user,config.database.password,config.database.url,config.database.db_name);
let db_config = AsyncDieselConnectionManager::<diesel_async::AsyncPgConnection>::new(db_string);
let db_pool = match Pool::builder(db_config)
.max_size(config.database.num_connections)
.build()
{
Ok(val) => val,
Err(e) => {
error!("Error while creating database connection pool");
error!("{:?}",e);
return ExitCode::FAILURE;
}
};
// Read certificate file
debug!("Reading certificate file");
let certs = match CertificateDer::pem_file_iter(&config.certfile) {
Ok(certs) => match certs.collect::<Result<Vec<_>,_>>() {
Ok(k) => k,
Err(e) => {
error!("Could not read certificates {}",&config.certfile);
error!("{}",e);
return ExitCode::FAILURE
}
},
Err(e) => {
error!("Could not read certificates.");
error!("{}",e);
return ExitCode::FAILURE
}
};
let key = match PrivateKeyDer::from_pem_file(&config.keyfile){
Ok(val) => val,
Err(e) => {
error!("Could not read key file {}",&config.keyfile);
error!("{}",e);
return ExitCode::FAILURE
}
};
let address = match IpAddr::from_str(&config.listen_address) {
Ok(val) => val,
Err(e) => {
error!("Could not parse IP address: {:?}",e);
return ExitCode::FAILURE;
}
};
let quinn_config = match quinn::ServerConfig::with_single_cert(certs, key){
Ok(val) => val,
Err(e) => {
error!("Unable to intialize quinn server configuration: {:?}",e);
return ExitCode::FAILURE;
}
};
// Bind this endpoint to a UDP socket on the given server address.
let endpoint = match Endpoint::server(
quinn_config,
SocketAddr::new(address,config.port)
) {
Ok(val) => val,
Err(e) => {
error!("Could not create incoming socket");
error!("{:?}",e);
return ExitCode::FAILURE;
}
};
debug!("Bound to {address} on port {}",config.port);
// Load or create new state
let state = match State::load_from_file(&config.statefile) {
Ok(state) => state,
// Create file if it does not exist
Err(StateError::IOError(e)) if e.kind() == io::ErrorKind::NotFound => {
match fs::File::create(&config.statefile) {
// If the statefile is writable then create an empty state
// and use that
Ok(_) => {
debug!("Creating fresh state");
State::new()
},
Err(e) => {
error!("Could not open or create statefile. Check your config.");
error!("{:?}",e);
return ExitCode::FAILURE;
}
}
},
Err(e) => {
error!("Could not open or create statefile. Check your config.");
error!("{:?}",e);
return ExitCode::FAILURE;
}
};
let statehandle = Arc::new(RwLock::new(state));
// Global connections
let (close_send,close_recv) = broadcast::channel(1);
let (message_send,message_recv) = broadcast::channel(128);
let (message_ack_send,_message_ack_recv) = mpsc::channel(128);
debug!("Setting ctrl-c handler");
match ctrlc_async::set_async_handler(
async move { match close_send.send(()) {
Ok(_val) => debug!("Propogating ctrl-c"),
Err(e) => {
error!("Shutdown handler is broken. Cannot gracefully exit.");
error!("{:?}",e);
}
}
}
) {
Ok(()) => (),
Err(e) => {
error!("Could not set up signal handler");
error!("{:?}",e);
return ExitCode::FAILURE;
}
};
let mut join_handles = Vec::new();
// Create client listener
debug!("Setting up client handler");
let statehandle_cloned = statehandle.clone();
let config_cloned = config.clone();
join_handles.push(tokio::spawn(async move {
connection::client_handler(
statehandle_cloned,
(message_send.clone(),message_recv.resubscribe()),
message_ack_send,
db_pool,
endpoint,
close_recv.resubscribe(),
config_cloned
).await;
} ));
info!("Successfully started confetti");
// Wait for child threads to exit
for handle in join_handles {
match handle.await {
Ok(()) => (),
Err(e) => {
warn!("Problem while cleaning up threads");
warn!("{:?}",e);
}
}
}
info!("Shutting down");
//Save state
match statehandle.write().await.write_to_file(&config.statefile).await {
Ok(()) => debug!("Successfully wrote state to {:?}",config.statefile),
Err(e) => {
error!("Problem while writing to statefile");
error!("{:?}",e);
}
}
ExitCode::SUCCESS
}