Polishing the turd
This commit is contained in:
parent
baa14a47e6
commit
fcd2c3c35d
3 changed files with 142 additions and 59 deletions
|
@ -1,11 +1,12 @@
|
|||
#![allow(unused)]
|
||||
|
||||
pub const VERSION: u16 = 0;
|
||||
/// TODO semver
|
||||
pub const VERSION: u16 = 20; // setup reminder or git hook of some kind to inc this?
|
||||
|
||||
// This variable should never change for any reason, it will be used to identify this instance of the bot
|
||||
//pub const BOT_INTERNAL: &str = "npobot";
|
||||
/// Should be limited to lowercase alphanumeric characters with periods, dashes, and underscores only
|
||||
/// If you want to stylize the bot name use the fancy version
|
||||
/// If you want to stylize the bot name use `BOT_NAME_FANCY`
|
||||
pub const BOT_NAME: &str = "nopalmo";
|
||||
pub const BOT_NAME_FANCY: &str = "Nopalmo";
|
||||
|
||||
|
@ -29,32 +30,77 @@ Nopalmo command help page:
|
|||
Description:
|
||||
Lorem Ipsum Dolar Sit Amet...
|
||||
|
||||
Arguments:
|
||||
nodiscord Skips connecting to discord
|
||||
See Usage section for more information about how this help menu functions
|
||||
|
||||
Configuration:
|
||||
print_help Prints this help information
|
||||
h, help, -h, --help, -help, /h, /help, /?, ?
|
||||
|
||||
nodiscord Shuts down the program when the first discord connection usually takes place
|
||||
Eventually this will connect to a simulated discord instance
|
||||
-N, --no-discord
|
||||
|
||||
dbuser The username to be used when connecting to a database type that requires one
|
||||
-U, --db-user <string>
|
||||
dbpass The password used like dbuser
|
||||
-P, --db-pass <string>
|
||||
dbaddress The network address of the desired database
|
||||
-A, --db-address <int.int.int.int>
|
||||
dbport The port to use with dbaddress
|
||||
-P, --db-port <int>
|
||||
dbpath The path to a unix socket to substitute an address or the path to an SQLite database file
|
||||
dbkind The kind of database we will be using
|
||||
-F, --db-path <path>
|
||||
dbkind The kind of database we will be using, one of `sqlite` `mariadb` `postgresql` `access`
|
||||
-D, --db-kind <dbkind>
|
||||
|
||||
use_cli Enables a CLI management interface for the bot
|
||||
-i, --interactive
|
||||
use_gui The GUI version of the CLI
|
||||
-g, --gui
|
||||
|
||||
lua_engine_count The number of lua execution engines to create, This defaults to the number of cores the computer has
|
||||
This allows executing multiple lua tasks at once, including user commands and timer tasks
|
||||
lua_engine_count The number of lua execution engines to create, This defaults to the number of cores the computer has
|
||||
This allows executing multiple lua tasks at once, including user commands and timer tasks
|
||||
-l, --lua-engine-count <int>
|
||||
|
||||
connect_remote Connect to a remote instance of the bot
|
||||
host_remote Host an instance of the bot for remote connections
|
||||
remote_address The remote address of the bot, or the address you wish to host the bot on
|
||||
remote_port The port used like remote_address
|
||||
remote_key The path or literal value of the key required to authenticate with the bot on a remote connection
|
||||
remote_key_type Decides the key type for remote connections
|
||||
log_level The level the logging system will use, one of `trace` `debug` `info` `warning` `error`
|
||||
-L, --log-level <loglevel>
|
||||
|
||||
connect_remote Connect to a remote instance of the bot
|
||||
-C, --remote-connection
|
||||
host_remote Host an instance of the bot for remote connections
|
||||
-H, --host-remote-connection
|
||||
|
||||
remote_address The remote address of the bot, or the address you wish to host the bot on
|
||||
-a, --remote-address <int.int.int.int>
|
||||
remote_port The port used like remote_address
|
||||
-p, --remote-port <int>
|
||||
remote_key The path of the key required to authenticate with the bot on a remote connection
|
||||
The remote_key_type flag decides what this value is used for
|
||||
-k, --remote-key <path>
|
||||
remote_key_type Decides the key type for remote connections, one of `database` `password` or default `file`
|
||||
-K, --remote-key-type <keytype>
|
||||
|
||||
Remote Connections:
|
||||
Remote access is disabled by default and must be enabled via the `connect_remote` or `host_remote` flags
|
||||
A key is required for remote connections, if you just need something quick use the `password` key type, the password must be the same for both server and client
|
||||
The key can be managed by the database as well, and the key type `database` will disable the `remote_key` variable and instead load authentication information from the server
|
||||
|
||||
Usage:
|
||||
./nopalmo --help
|
||||
./nopalmo -H -a 0.0.0.0 -p 2022 -K file -k ./private.key
|
||||
./nopalmo -C -a 127.0.0.1 -p 2022 -K file -k ./public.key
|
||||
|
||||
The Configuration section is layout like this:
|
||||
Name Description
|
||||
Commandline opts Option arguments
|
||||
|
||||
Each configurable value can be changed in multiple ways for ease of use by the user
|
||||
The order of configuration application is like so:
|
||||
1. System Environment
|
||||
2. .env file (if enabled by your maintainer using the dot_env_file feature)
|
||||
3. Configuration file (currently not supported)
|
||||
4. Commandline options
|
||||
|
||||
Environment Variables:
|
||||
Environment Variables may be set in addition or in place of commandline arguments
|
||||
";
|
||||
|
|
|
@ -11,7 +11,7 @@ pub mod mariadb;
|
|||
pub mod postgres;
|
||||
pub mod sqlite;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)]
|
||||
pub enum DBKind {
|
||||
Access,
|
||||
SQLite,
|
||||
|
|
127
src/main.rs
127
src/main.rs
|
@ -23,6 +23,7 @@ use tracing::debug;
|
|||
use tracing::error;
|
||||
use tracing::info;
|
||||
use tracing::metadata::LevelFilter;
|
||||
use tracing::trace;
|
||||
use tracing::warn;
|
||||
|
||||
use constants::HELP_STRING;
|
||||
|
@ -52,11 +53,27 @@ fn string_to_log_level(str: String) -> Option<LevelFilter> {
|
|||
"info" => Some(tracing_subscriber::filter::LevelFilter::INFO),
|
||||
"warn" => Some(tracing_subscriber::filter::LevelFilter::WARN),
|
||||
"error" => Some(tracing_subscriber::filter::LevelFilter::ERROR),
|
||||
_ => None,
|
||||
_ => {
|
||||
error!("Invalid log level! {str} Choose one of `trace` `debug` `info` `warn` `error`");
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
fn string_to_db_kind(str: String) -> Option<DBKind> {
|
||||
match str.to_lowercase().trim() {
|
||||
"access" => Some(DBKind::Access),
|
||||
"postgresql" => Some(DBKind::PostgreSQL),
|
||||
"sqlite" => Some(DBKind::SQLite),
|
||||
"mariadb" => Some(DBKind::MariaDB),
|
||||
_ => {
|
||||
error!("Invalid database kind! {str} Choose one of `sqlite` `mariadb` `postgresql` `access`");
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// ### This macro has a "header" of sorts which is used to configure the macro
|
||||
/// The variables will also be initialized with the system enviroment values, so try not to use unwrap or expect, use a sane default value
|
||||
/// ---
|
||||
/// This is the first line of an example usage
|
||||
/// ```
|
||||
|
@ -145,9 +162,10 @@ macro_rules! program_properties {
|
|||
}
|
||||
|
||||
$(#[$fattr])* $fvis fn $fident() -> $sident {
|
||||
info!("Initializing with env variables.");
|
||||
debug!("Initializing with env variables.");
|
||||
let mut resval = $sident {
|
||||
$($itemident: {
|
||||
trace!("Reading env: {}", stringify!($itemident));
|
||||
#[allow(unused)]
|
||||
let $vname = std::env::var(stringify!($itemident)).ok();
|
||||
$envfixer
|
||||
|
@ -155,7 +173,7 @@ macro_rules! program_properties {
|
|||
};
|
||||
|
||||
#[cfg(feature = "dot_env_file")] {
|
||||
info!("Grabbing .env file input.");
|
||||
debug!("Grabbing .env file input.");
|
||||
|
||||
if let Ok(cdir) = std::env::current_dir() {
|
||||
if let Ok(string) = std::fs::read_to_string(cdir.join(".env")) {
|
||||
|
@ -185,6 +203,7 @@ macro_rules! program_properties {
|
|||
for (lhs,rhs) in eqvec {
|
||||
match lhs.trim() {
|
||||
$(stringify!($itemident) => {
|
||||
trace!("Reading .env file: {}", stringify!($itemident));
|
||||
#[allow(unused)]
|
||||
let $vname = Some(rhs.to_string());
|
||||
resval.$itemident = $envfixer;
|
||||
|
@ -202,7 +221,7 @@ macro_rules! program_properties {
|
|||
|
||||
}
|
||||
|
||||
info!("Grabbing commandline options.");
|
||||
debug!("Grabbing commandline options.");
|
||||
let mut args = std::env::args().peekable();
|
||||
|
||||
// The first arg is the path to the executable on all systems that i know of
|
||||
|
@ -217,6 +236,7 @@ macro_rules! program_properties {
|
|||
while let Some(item) = args.next() {
|
||||
match item.trim() {
|
||||
$($($options)|* | stringify!($itemident) => {
|
||||
trace!("Reading commandline options: {}", stringify!($itemident));
|
||||
#[allow(unused)]
|
||||
let $vargsname = &mut args;
|
||||
resval.$itemident = $argumentfixer;
|
||||
|
@ -247,63 +267,71 @@ program_properties! {
|
|||
envfixer: item.is_some_and(string_to_bool),
|
||||
argumentfixer: true;
|
||||
pub nodiscord: bool,
|
||||
optnames: [],
|
||||
optnames: ["-N", "--no-discord"],
|
||||
envfixer: item.is_some_and(string_to_bool),
|
||||
argumentfixer: true;
|
||||
pub dbuser : Option<String>,
|
||||
optnames: [],
|
||||
optnames: ["-U", "--db-user"],
|
||||
envfixer: item,
|
||||
argumentfixer: arglist.next();
|
||||
pub dbpass : Option<String>,
|
||||
optnames: [],
|
||||
optnames: ["-P","--db-pass"],
|
||||
envfixer: item,
|
||||
argumentfixer: arglist.next();
|
||||
pub dbaddress : Option<String>,
|
||||
optnames: [],
|
||||
optnames: ["-A", "--db-address"],
|
||||
envfixer: item,
|
||||
argumentfixer: arglist.next();
|
||||
pub dbport: Option<String>,
|
||||
optnames: [],
|
||||
optnames: ["-P", "--db-port"],
|
||||
envfixer: item,
|
||||
argumentfixer: arglist.next();
|
||||
pub dbpath: Option<String>,
|
||||
optnames: [],
|
||||
envfixer: item,
|
||||
argumentfixer: arglist.next();
|
||||
pub dbkind: Option<String>,
|
||||
optnames: [],
|
||||
optnames: ["-F","--db-path"],
|
||||
envfixer: item,
|
||||
argumentfixer: arglist.next();
|
||||
pub dbkind: Option<DBKind>,
|
||||
optnames: ["-D", "--db-kind"],
|
||||
envfixer: item.and_then(string_to_db_kind),
|
||||
argumentfixer: arglist.next().and_then(string_to_db_kind);
|
||||
pub use_cli: bool,
|
||||
optnames: [],
|
||||
envfixer: item.is_some_and(string_to_bool),
|
||||
optnames: ["-i", "--interactive"],
|
||||
envfixer: item.is_some_and(string_to_bool),
|
||||
argumentfixer: true;
|
||||
pub use_gui: bool,
|
||||
optnames: ["-g", "--gui"],
|
||||
envfixer: item.is_some_and(string_to_bool),
|
||||
argumentfixer: true;
|
||||
pub lua_engine_count: Option<String>,
|
||||
optnames: [],
|
||||
optnames: ["-l", "--lua-engine-count"],
|
||||
envfixer: item,
|
||||
argumentfixer: arglist.next();
|
||||
pub log_level: Option<LevelFilter>,
|
||||
optnames: [],
|
||||
envfixer: string_to_log_level(item.unwrap_or_default()),
|
||||
argumentfixer: string_to_log_level(arglist.next().unwrap_or_default());
|
||||
optnames: ["-L", "--log-level"],
|
||||
envfixer: item.and_then(string_to_log_level),
|
||||
argumentfixer: arglist.next().and_then(string_to_log_level);
|
||||
pub connect_remote: bool,
|
||||
optnames: [],
|
||||
optnames: ["-C", "--remote-connection"],
|
||||
envfixer: item.is_some_and(string_to_bool),
|
||||
argumentfixer: true;
|
||||
pub host_remote: bool,
|
||||
optnames: ["-H", "--host-remote-connection"],
|
||||
envfixer: item.is_some_and(string_to_bool),
|
||||
argumentfixer: true;
|
||||
pub remote_address: Option<String>,
|
||||
optnames: [],
|
||||
optnames: ["-a", "--remote-address"],
|
||||
envfixer: item,
|
||||
argumentfixer: arglist.next();
|
||||
pub remote_port: Option<String>,
|
||||
optnames: [],
|
||||
optnames: ["-p", "--remote-port"],
|
||||
envfixer: item,
|
||||
argumentfixer: arglist.next();
|
||||
pub remote_key: Option<String>,
|
||||
optnames: [],
|
||||
optnames: ["-k", "--remote-key"],
|
||||
envfixer: item,
|
||||
argumentfixer: arglist.next();
|
||||
pub remote_key_type: Option<String>,
|
||||
optnames: [],
|
||||
optnames: ["-K", "--remote-key-type"],
|
||||
envfixer: item,
|
||||
argumentfixer: arglist.next();
|
||||
}
|
||||
|
@ -328,8 +356,22 @@ pub fn shutdown_handler() {
|
|||
fn main() {
|
||||
// why do we need to set global default again?
|
||||
|
||||
let (level_filter, level_filter_reload_handle) =
|
||||
tracing_subscriber::reload::Layer::new(if cfg!(debug_assertions) { LevelFilter::DEBUG } else { LevelFilter::INFO });
|
||||
// TODO ansi color support
|
||||
|
||||
let (level_filter, level_filter_reload_handle) = tracing_subscriber::reload::Layer::new({
|
||||
if let Ok(var) = std::env::var("log_level") {
|
||||
let Some(var) = string_to_log_level(var) else {
|
||||
eprintln!("CRITICAL: Could not read `log_level` system environment variable for initialization phase!");
|
||||
eprintln!("CRITICAL: The value must be one of `trace` `debug` `info` `warn` `error` or it must not be present!");
|
||||
exit(-1);
|
||||
};
|
||||
var
|
||||
} else if cfg!(debug_assertions) {
|
||||
LevelFilter::DEBUG
|
||||
} else {
|
||||
LevelFilter::INFO
|
||||
}
|
||||
});
|
||||
{
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
|
@ -351,17 +393,23 @@ fn main() {
|
|||
.init();
|
||||
}
|
||||
|
||||
// TODO set logging output to a file via a reload handle like above, store logged data and flush to file when file is registered and opened for writing as to not lose any data
|
||||
|
||||
// Manually init the lazy lock here rather than later for reliability sake
|
||||
LazyLock::force(&PROPERTIES);
|
||||
#[cfg(debug_assertions)]
|
||||
debug!("{:?}", *PROPERTIES);
|
||||
|
||||
if let Some(val) = PROPERTIES.log_level {
|
||||
level_filter_reload_handle.modify(|filter| *filter = val).unwrap();
|
||||
// TODO make this a global function
|
||||
if let Some(nval) = PROPERTIES.log_level {
|
||||
if let Some(cval) = level_filter_reload_handle.clone_current() {
|
||||
if cval != nval {
|
||||
trace!("Switching from log level {cval} to {nval}");
|
||||
level_filter_reload_handle.modify(|filter| *filter = nval).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store non fatal errors from behind this point to be logged and handled afterwards, for example ansi failures, log file failures, or invalid properties
|
||||
// It is assumed there is no failure state before this point, if something goes wrong, shamble onwards
|
||||
|
||||
let (shutdown_tx, shutdown_rx) = std::sync::mpsc::channel();
|
||||
#[allow(clippy::unwrap_used)] // it is impossible for this to be an error
|
||||
SHUTDOWN_SENDER.set(shutdown_tx.clone()).unwrap();
|
||||
|
@ -377,23 +425,12 @@ fn main() {
|
|||
exit(0);
|
||||
}
|
||||
|
||||
let dbkind = if let Some(arg) = &PROPERTIES.dbkind {
|
||||
info!("Loading database accessor.");
|
||||
match arg.to_lowercase().trim() {
|
||||
"access" => DBKind::Access,
|
||||
"postgresql" => DBKind::PostgreSQL,
|
||||
"sqlite" => DBKind::SQLite,
|
||||
"mariadb" => DBKind::MariaDB,
|
||||
_ => {
|
||||
error!("Invalid database kind! Choose one of `sqlite` `mariadb` `postgresql` `access`");
|
||||
exit(-1);
|
||||
},
|
||||
}
|
||||
} else {
|
||||
let Some(dbkind) = PROPERTIES.dbkind else {
|
||||
warn!("You must select a database kind with `dbkind`");
|
||||
exit(-1);
|
||||
};
|
||||
|
||||
info!("Loading database accessor.");
|
||||
if let Err(err) = match dbkind {
|
||||
DBKind::Access => databases::access::create_interface(),
|
||||
DBKind::MariaDB => databases::mariadb::create_interface(),
|
||||
|
|
Loading…
Add table
Reference in a new issue