Polishing the turd

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

View file

@ -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
";

View file

@ -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,

View file

@ -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(),