Polishing the turd

This commit is contained in:
lever1209 2025-03-25 01:18:05 -03:00
commit fcd2c3c35d
Signed by: lever1209
GPG key ID: AD770D25A908AFF4
3 changed files with 142 additions and 59 deletions

View file

@ -1,11 +1,12 @@
#![allow(unused)] #![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 // 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"; //pub const BOT_INTERNAL: &str = "npobot";
/// Should be limited to lowercase alphanumeric characters with periods, dashes, and underscores only /// 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: &str = "nopalmo";
pub const BOT_NAME_FANCY: &str = "Nopalmo"; pub const BOT_NAME_FANCY: &str = "Nopalmo";
@ -29,32 +30,77 @@ Nopalmo command help page:
Description: Description:
Lorem Ipsum Dolar Sit Amet... Lorem Ipsum Dolar Sit Amet...
Arguments: See Usage section for more information about how this help menu functions
nodiscord Skips connecting to discord
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 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 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 dbpass The password used like dbuser
-P, --db-pass <string>
dbaddress The network address of the desired database dbaddress The network address of the desired database
-A, --db-address <int.int.int.int>
dbport The port to use with dbaddress 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 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 use_cli Enables a CLI management interface for the bot
-i, --interactive
use_gui The GUI version of the CLI 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 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 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 log_level The level the logging system will use, one of `trace` `debug` `info` `warning` `error`
host_remote Host an instance of the bot for remote connections -L, --log-level <loglevel>
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 connect_remote Connect to a remote instance of the bot
remote_key The path or literal value of the key required to authenticate with the bot on a remote connection -C, --remote-connection
remote_key_type Decides the key type for remote connections 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 Connections:
Remote access is disabled by default and must be enabled via the `connect_remote` or `host_remote` flags 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 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 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
"; ";

View file

@ -11,7 +11,7 @@ pub mod mariadb;
pub mod postgres; pub mod postgres;
pub mod sqlite; pub mod sqlite;
#[derive(Debug)] #[derive(Debug, PartialEq, PartialOrd, Clone, Copy)]
pub enum DBKind { pub enum DBKind {
Access, Access,
SQLite, SQLite,

View file

@ -23,6 +23,7 @@ use tracing::debug;
use tracing::error; use tracing::error;
use tracing::info; use tracing::info;
use tracing::metadata::LevelFilter; use tracing::metadata::LevelFilter;
use tracing::trace;
use tracing::warn; use tracing::warn;
use constants::HELP_STRING; use constants::HELP_STRING;
@ -52,11 +53,27 @@ fn string_to_log_level(str: String) -> Option<LevelFilter> {
"info" => Some(tracing_subscriber::filter::LevelFilter::INFO), "info" => Some(tracing_subscriber::filter::LevelFilter::INFO),
"warn" => Some(tracing_subscriber::filter::LevelFilter::WARN), "warn" => Some(tracing_subscriber::filter::LevelFilter::WARN),
"error" => Some(tracing_subscriber::filter::LevelFilter::ERROR), "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 /// ### 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 /// This is the first line of an example usage
/// ``` /// ```
@ -145,9 +162,10 @@ macro_rules! program_properties {
} }
$(#[$fattr])* $fvis fn $fident() -> $sident { $(#[$fattr])* $fvis fn $fident() -> $sident {
info!("Initializing with env variables."); debug!("Initializing with env variables.");
let mut resval = $sident { let mut resval = $sident {
$($itemident: { $($itemident: {
trace!("Reading env: {}", stringify!($itemident));
#[allow(unused)] #[allow(unused)]
let $vname = std::env::var(stringify!($itemident)).ok(); let $vname = std::env::var(stringify!($itemident)).ok();
$envfixer $envfixer
@ -155,7 +173,7 @@ macro_rules! program_properties {
}; };
#[cfg(feature = "dot_env_file")] { #[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(cdir) = std::env::current_dir() {
if let Ok(string) = std::fs::read_to_string(cdir.join(".env")) { 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 { for (lhs,rhs) in eqvec {
match lhs.trim() { match lhs.trim() {
$(stringify!($itemident) => { $(stringify!($itemident) => {
trace!("Reading .env file: {}", stringify!($itemident));
#[allow(unused)] #[allow(unused)]
let $vname = Some(rhs.to_string()); let $vname = Some(rhs.to_string());
resval.$itemident = $envfixer; 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(); let mut args = std::env::args().peekable();
// The first arg is the path to the executable on all systems that i know of // 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() { while let Some(item) = args.next() {
match item.trim() { match item.trim() {
$($($options)|* | stringify!($itemident) => { $($($options)|* | stringify!($itemident) => {
trace!("Reading commandline options: {}", stringify!($itemident));
#[allow(unused)] #[allow(unused)]
let $vargsname = &mut args; let $vargsname = &mut args;
resval.$itemident = $argumentfixer; resval.$itemident = $argumentfixer;
@ -247,63 +267,71 @@ program_properties! {
envfixer: item.is_some_and(string_to_bool), envfixer: item.is_some_and(string_to_bool),
argumentfixer: true; argumentfixer: true;
pub nodiscord: bool, pub nodiscord: bool,
optnames: [], optnames: ["-N", "--no-discord"],
envfixer: item.is_some_and(string_to_bool), envfixer: item.is_some_and(string_to_bool),
argumentfixer: true; argumentfixer: true;
pub dbuser : Option<String>, pub dbuser : Option<String>,
optnames: [], optnames: ["-U", "--db-user"],
envfixer: item, envfixer: item,
argumentfixer: arglist.next(); argumentfixer: arglist.next();
pub dbpass : Option<String>, pub dbpass : Option<String>,
optnames: [], optnames: ["-P","--db-pass"],
envfixer: item, envfixer: item,
argumentfixer: arglist.next(); argumentfixer: arglist.next();
pub dbaddress : Option<String>, pub dbaddress : Option<String>,
optnames: [], optnames: ["-A", "--db-address"],
envfixer: item, envfixer: item,
argumentfixer: arglist.next(); argumentfixer: arglist.next();
pub dbport: Option<String>, pub dbport: Option<String>,
optnames: [], optnames: ["-P", "--db-port"],
envfixer: item, envfixer: item,
argumentfixer: arglist.next(); argumentfixer: arglist.next();
pub dbpath: Option<String>, pub dbpath: Option<String>,
optnames: [], optnames: ["-F","--db-path"],
envfixer: item,
argumentfixer: arglist.next();
pub dbkind: Option<String>,
optnames: [],
envfixer: item, envfixer: item,
argumentfixer: arglist.next(); 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, pub use_cli: bool,
optnames: [], optnames: ["-i", "--interactive"],
envfixer: item.is_some_and(string_to_bool), 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; argumentfixer: true;
pub lua_engine_count: Option<String>, pub lua_engine_count: Option<String>,
optnames: [], optnames: ["-l", "--lua-engine-count"],
envfixer: item, envfixer: item,
argumentfixer: arglist.next(); argumentfixer: arglist.next();
pub log_level: Option<LevelFilter>, pub log_level: Option<LevelFilter>,
optnames: [], optnames: ["-L", "--log-level"],
envfixer: string_to_log_level(item.unwrap_or_default()), envfixer: item.and_then(string_to_log_level),
argumentfixer: string_to_log_level(arglist.next().unwrap_or_default()); argumentfixer: arglist.next().and_then(string_to_log_level);
pub connect_remote: bool, 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), envfixer: item.is_some_and(string_to_bool),
argumentfixer: true; argumentfixer: true;
pub remote_address: Option<String>, pub remote_address: Option<String>,
optnames: [], optnames: ["-a", "--remote-address"],
envfixer: item, envfixer: item,
argumentfixer: arglist.next(); argumentfixer: arglist.next();
pub remote_port: Option<String>, pub remote_port: Option<String>,
optnames: [], optnames: ["-p", "--remote-port"],
envfixer: item, envfixer: item,
argumentfixer: arglist.next(); argumentfixer: arglist.next();
pub remote_key: Option<String>, pub remote_key: Option<String>,
optnames: [], optnames: ["-k", "--remote-key"],
envfixer: item, envfixer: item,
argumentfixer: arglist.next(); argumentfixer: arglist.next();
pub remote_key_type: Option<String>, pub remote_key_type: Option<String>,
optnames: [], optnames: ["-K", "--remote-key-type"],
envfixer: item, envfixer: item,
argumentfixer: arglist.next(); argumentfixer: arglist.next();
} }
@ -328,8 +356,22 @@ pub fn shutdown_handler() {
fn main() { fn main() {
// why do we need to set global default again? // why do we need to set global default again?
let (level_filter, level_filter_reload_handle) = // TODO ansi color support
tracing_subscriber::reload::Layer::new(if cfg!(debug_assertions) { LevelFilter::DEBUG } else { LevelFilter::INFO });
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::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt; use tracing_subscriber::util::SubscriberInitExt;
@ -351,17 +393,23 @@ fn main() {
.init(); .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 // Manually init the lazy lock here rather than later for reliability sake
LazyLock::force(&PROPERTIES); LazyLock::force(&PROPERTIES);
#[cfg(debug_assertions)]
debug!("{:?}", *PROPERTIES); debug!("{:?}", *PROPERTIES);
if let Some(val) = PROPERTIES.log_level { // TODO make this a global function
level_filter_reload_handle.modify(|filter| *filter = val).unwrap(); 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(); let (shutdown_tx, shutdown_rx) = std::sync::mpsc::channel();
#[allow(clippy::unwrap_used)] // it is impossible for this to be an error #[allow(clippy::unwrap_used)] // it is impossible for this to be an error
SHUTDOWN_SENDER.set(shutdown_tx.clone()).unwrap(); SHUTDOWN_SENDER.set(shutdown_tx.clone()).unwrap();
@ -377,23 +425,12 @@ fn main() {
exit(0); exit(0);
} }
let dbkind = if let Some(arg) = &PROPERTIES.dbkind { let Some(dbkind) = PROPERTIES.dbkind else {
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 {
warn!("You must select a database kind with `dbkind`"); warn!("You must select a database kind with `dbkind`");
exit(-1); exit(-1);
}; };
info!("Loading database accessor.");
if let Err(err) = match dbkind { if let Err(err) = match dbkind {
DBKind::Access => databases::access::create_interface(), DBKind::Access => databases::access::create_interface(),
DBKind::MariaDB => databases::mariadb::create_interface(), DBKind::MariaDB => databases::mariadb::create_interface(),