mirror of
https://github.com/NoiTheCat/BirthdayBot.git
synced 2024-11-24 01:14:12 +00:00
Load database config on initialization
Adapted from RegexBot. This allows `dotnet ef` tools to make use of actual SQL credentials rather than dummy ones.
This commit is contained in:
parent
92be9ca073
commit
261d54197d
3 changed files with 69 additions and 80 deletions
|
@ -1,8 +1,6 @@
|
|||
using BirthdayBot.Data;
|
||||
using CommandLine;
|
||||
using CommandLine.Text;
|
||||
using CommandLine;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Npgsql;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
|
@ -13,10 +11,6 @@ namespace BirthdayBot;
|
|||
/// Loads and holds configuration values.
|
||||
/// </summary>
|
||||
class Configuration {
|
||||
const string KeySqlHost = "SqlHost";
|
||||
const string KeySqlUsername = "SqlUsername";
|
||||
const string KeySqlPassword = "SqlPassword";
|
||||
const string KeySqlDatabase = "SqlDatabase";
|
||||
const string KeyShardRange = "ShardRange";
|
||||
|
||||
public string BotToken { get; }
|
||||
|
@ -27,32 +21,44 @@ class Configuration {
|
|||
public int ShardAmount { get; }
|
||||
public int ShardTotal { get; }
|
||||
|
||||
public string DatabaseConnectionString { get; }
|
||||
public string? SqlHost { get; }
|
||||
public string? SqlDatabase { get; }
|
||||
public string SqlUsername { get; }
|
||||
public string SqlPassword { get; }
|
||||
internal string SqlApplicationName { get; }
|
||||
|
||||
public Configuration(string[] args) {
|
||||
var cmdline = CmdLineOpts.Parse(args);
|
||||
public Configuration() {
|
||||
var args = CommandLineParameters.Parse(Environment.GetCommandLineArgs());
|
||||
var path = args?.ConfigFile ?? Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location)
|
||||
+ Path.DirectorySeparatorChar + "." + Path.DirectorySeparatorChar + "settings.json";
|
||||
|
||||
// Looks for configuration file
|
||||
var confPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + Path.DirectorySeparatorChar;
|
||||
confPath += cmdline.Config!;
|
||||
if (!File.Exists(confPath)) throw new Exception("Settings file not found in path: " + confPath);
|
||||
JObject jc;
|
||||
try {
|
||||
var conftxt = File.ReadAllText(path);
|
||||
jc = JObject.Parse(conftxt);
|
||||
} catch (Exception ex) {
|
||||
string pfx;
|
||||
if (ex is JsonException) pfx = "Unable to parse configuration: ";
|
||||
else pfx = "Unable to access configuration: ";
|
||||
|
||||
var jc = JObject.Parse(File.ReadAllText(confPath));
|
||||
throw new Exception(pfx + ex.Message, ex);
|
||||
}
|
||||
|
||||
BotToken = ReadConfKey<string>(jc, nameof(BotToken), true);
|
||||
DBotsToken = ReadConfKey<string>(jc, nameof(DBotsToken), false);
|
||||
QuitOnFails = ReadConfKey<bool?>(jc, nameof(QuitOnFails), false) ?? false;
|
||||
|
||||
ShardTotal = cmdline.ShardTotal ?? ReadConfKey<int?>(jc, nameof(ShardTotal), false) ?? 1;
|
||||
ShardTotal = args.ShardTotal ?? ReadConfKey<int?>(jc, nameof(ShardTotal), false) ?? 1;
|
||||
if (ShardTotal < 1) throw new Exception($"'{nameof(ShardTotal)}' must be a positive integer.");
|
||||
|
||||
string shardRangeInput = cmdline.ShardRange ?? ReadConfKey<string>(jc, KeyShardRange, false);
|
||||
var shardRangeInput = args.ShardRange ?? ReadConfKey<string>(jc, KeyShardRange, false);
|
||||
if (!string.IsNullOrWhiteSpace(shardRangeInput)) {
|
||||
Regex srPicker = new(@"(?<low>\d{1,2})[-,]{1}(?<high>\d{1,2})");
|
||||
var m = srPicker.Match(shardRangeInput);
|
||||
if (m.Success) {
|
||||
ShardStart = int.Parse(m.Groups["low"].Value);
|
||||
int high = int.Parse(m.Groups["high"].Value);
|
||||
var high = int.Parse(m.Groups["high"].Value);
|
||||
ShardAmount = high - (ShardStart - 1);
|
||||
} else {
|
||||
throw new Exception($"Shard range not properly formatted in '{KeyShardRange}'.");
|
||||
|
@ -63,20 +69,11 @@ class Configuration {
|
|||
ShardAmount = ShardTotal;
|
||||
}
|
||||
|
||||
var sqlhost = ReadConfKey<string>(jc, KeySqlHost, false) ?? "localhost"; // Default to localhost
|
||||
var sqluser = ReadConfKey<string>(jc, KeySqlUsername, false);
|
||||
var sqlpass = ReadConfKey<string>(jc, KeySqlPassword, false);
|
||||
if (string.IsNullOrWhiteSpace(sqluser) || string.IsNullOrWhiteSpace(sqlpass))
|
||||
throw new Exception("'SqlUsername', 'SqlPassword' must be specified.");
|
||||
var csb = new NpgsqlConnectionStringBuilder() {
|
||||
Host = sqlhost,
|
||||
Username = sqluser,
|
||||
Password = sqlpass,
|
||||
ApplicationName = $"ClientShard{ShardStart}+{ShardAmount}"
|
||||
};
|
||||
var sqldb = ReadConfKey<string>(jc, KeySqlDatabase, false);
|
||||
if (sqldb != null) csb.Database = sqldb; // Optional database setting
|
||||
DatabaseConnectionString = csb.ToString();
|
||||
SqlHost = ReadConfKey<string>(jc, nameof(SqlHost), false);
|
||||
SqlDatabase = ReadConfKey<string?>(jc, nameof(SqlDatabase), false);
|
||||
SqlUsername = ReadConfKey<string>(jc, nameof(SqlUsername), true);
|
||||
SqlPassword = ReadConfKey<string>(jc, nameof(SqlPassword), true);
|
||||
SqlApplicationName = $"ClientShard{ShardStart}+{ShardAmount}";
|
||||
}
|
||||
|
||||
private static T? ReadConfKey<T>(JObject jc, string key, [DoesNotReturnIf(true)] bool failOnEmpty) {
|
||||
|
@ -85,33 +82,27 @@ class Configuration {
|
|||
return default;
|
||||
}
|
||||
|
||||
private class CmdLineOpts {
|
||||
[Option('c', "config", Default = "settings.json",
|
||||
HelpText = "Custom path to instance configuration, relative from executable directory.")]
|
||||
public string? Config { get; set; }
|
||||
class CommandLineParameters {
|
||||
[Option('c', "config")]
|
||||
public string? ConfigFile { get; set; }
|
||||
|
||||
[Option("shardtotal",
|
||||
HelpText = "Total number of shards online. MUST be the same for all instances.\n"
|
||||
+ "This value overrides the config file value.")]
|
||||
[Option("shardtotal")]
|
||||
public int? ShardTotal { get; set; }
|
||||
|
||||
[Option("shardrange", HelpText = "Shard range for this instance to handle.\n"
|
||||
+ "This value overrides the config file value.")]
|
||||
[Option("shardrange")]
|
||||
public string? ShardRange { get; set; }
|
||||
|
||||
public static CmdLineOpts Parse(string[] args) {
|
||||
// Do not automatically print help message
|
||||
var clp = new Parser(c => c.HelpWriter = null);
|
||||
public static CommandLineParameters? Parse(string[] args) {
|
||||
CommandLineParameters? result = null;
|
||||
|
||||
CmdLineOpts? result = null;
|
||||
var r = clp.ParseArguments<CmdLineOpts>(args);
|
||||
r.WithParsed(parsed => result = parsed);
|
||||
r.WithNotParsed(err => {
|
||||
var ht = HelpText.AutoBuild(r);
|
||||
Console.WriteLine(ht.ToString());
|
||||
Environment.Exit((int)Program.ExitCodes.BadCommand);
|
||||
});
|
||||
return result!;
|
||||
new Parser(settings => {
|
||||
settings.IgnoreUnknownArguments = true;
|
||||
settings.AutoHelp = false;
|
||||
settings.AutoVersion = false;
|
||||
}).ParseArguments<CommandLineParameters>(args)
|
||||
.WithParsed(p => result = p)
|
||||
.WithNotParsed(e => { /* ignore */ });
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,22 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Npgsql;
|
||||
|
||||
namespace BirthdayBot.Data;
|
||||
|
||||
public class BotDatabaseContext : DbContext {
|
||||
private static string? _npgsqlConnectionString;
|
||||
internal static string NpgsqlConnectionString {
|
||||
#if DEBUG
|
||||
get {
|
||||
if (_npgsqlConnectionString != null) return _npgsqlConnectionString;
|
||||
Program.Log(nameof(BotDatabaseContext), "Using hardcoded connection string!");
|
||||
return _npgsqlConnectionString ?? "Host=localhost;Username=birthdaybot;Password=bb";
|
||||
}
|
||||
#else
|
||||
get => _npgsqlConnectionString!;
|
||||
#endif
|
||||
set => _npgsqlConnectionString ??= value;
|
||||
private static readonly string _connectionString;
|
||||
|
||||
static BotDatabaseContext() {
|
||||
// Get our own config loaded just for the SQL stuff
|
||||
var conf = new Configuration();
|
||||
_connectionString = new NpgsqlConnectionStringBuilder() {
|
||||
Host = conf.SqlHost ?? "localhost", // default to localhost
|
||||
Database = conf.SqlDatabase,
|
||||
Username = conf.SqlUsername,
|
||||
Password = conf.SqlPassword,
|
||||
ApplicationName = conf.SqlApplicationName,
|
||||
MaxPoolSize = Math.Max((int)Math.Ceiling(conf.ShardAmount * 2 * 0.6), 8)
|
||||
}.ToString();
|
||||
}
|
||||
|
||||
public DbSet<BlocklistEntry> BlocklistEntries { get; set; } = null!;
|
||||
|
@ -22,11 +24,8 @@ public class BotDatabaseContext : DbContext {
|
|||
public DbSet<UserEntry> UserEntries { get; set; } = null!;
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
=> optionsBuilder
|
||||
.UseNpgsql(NpgsqlConnectionString)
|
||||
#if DEBUG
|
||||
.LogTo((string line) => Program.Log("EF", line), Microsoft.Extensions.Logging.LogLevel.Information)
|
||||
#endif
|
||||
=> optionsBuilder
|
||||
.UseNpgsql(_connectionString)
|
||||
.UseSnakeCaseNamingConvention();
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder) {
|
||||
|
|
19
Program.cs
19
Program.cs
|
@ -14,21 +14,20 @@ class Program {
|
|||
static async Task Main(string[] args) {
|
||||
Configuration? cfg = null;
|
||||
try {
|
||||
cfg = new Configuration(args);
|
||||
cfg = new Configuration();
|
||||
} catch (Exception ex) {
|
||||
Console.WriteLine(ex);
|
||||
Environment.Exit((int)ExitCodes.ConfigError);
|
||||
}
|
||||
|
||||
BotDatabaseContext.NpgsqlConnectionString = cfg.DatabaseConnectionString;
|
||||
|
||||
Database.DBConnectionString = cfg.DatabaseConnectionString;
|
||||
try {
|
||||
await Database.DoInitialDatabaseSetupAsync();
|
||||
} catch (Npgsql.NpgsqlException e) {
|
||||
Console.WriteLine("Error when attempting to connect to database: " + e.Message);
|
||||
Environment.Exit((int)ExitCodes.DatabaseError);
|
||||
}
|
||||
Database.DBConnectionString = new Npgsql.NpgsqlConnectionStringBuilder() {
|
||||
Host = cfg.SqlHost ?? "localhost", // default to localhost
|
||||
Database = cfg.SqlDatabase,
|
||||
Username = cfg.SqlUsername,
|
||||
Password = cfg.SqlPassword,
|
||||
ApplicationName = cfg.SqlApplicationName,
|
||||
MaxPoolSize = Math.Max((int)Math.Ceiling(cfg.ShardAmount * 2 * 0.6), 8)
|
||||
}.ToString();
|
||||
|
||||
Console.CancelKeyPress += OnCancelKeyPressed;
|
||||
_bot = new ShardManager(cfg);
|
||||
|
|
Loading…
Reference in a new issue