mirror of
https://github.com/PluralKit/PluralKit.git
synced 2026-02-13 17:20:14 +00:00
feat: add remote config over http/redis
This commit is contained in:
parent
c4db95796d
commit
a72afb35a0
12 changed files with 326 additions and 4 deletions
|
|
@ -24,6 +24,8 @@ public class BotConfig
|
||||||
public string? HttpCacheUrl { get; set; }
|
public string? HttpCacheUrl { get; set; }
|
||||||
public bool HttpUseInnerCache { get; set; } = false;
|
public bool HttpUseInnerCache { get; set; } = false;
|
||||||
|
|
||||||
|
public string? HttpListenerAddr { get; set; }
|
||||||
|
|
||||||
public string? DiscordBaseUrl { get; set; }
|
public string? DiscordBaseUrl { get; set; }
|
||||||
public string? AvatarServiceUrl { get; set; }
|
public string? AvatarServiceUrl { get; set; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,13 @@ public class Init
|
||||||
// Init the bot instance itself, register handlers and such to the client before beginning to connect
|
// Init the bot instance itself, register handlers and such to the client before beginning to connect
|
||||||
bot.Init();
|
bot.Init();
|
||||||
|
|
||||||
|
// load runtime config from redis
|
||||||
|
await services.Resolve<RuntimeConfigService>().LoadConfig();
|
||||||
|
|
||||||
|
// Start HTTP server
|
||||||
|
if (config.HttpListenerAddr != null)
|
||||||
|
services.Resolve<HttpListenerService>().Start(config.HttpListenerAddr);
|
||||||
|
|
||||||
// Start the Discord shards themselves (handlers already set up)
|
// Start the Discord shards themselves (handlers already set up)
|
||||||
logger.Information("Connecting to Discord");
|
logger.Information("Connecting to Discord");
|
||||||
await StartCluster(services);
|
await StartCluster(services);
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,8 @@ public class BotModule: Module
|
||||||
builder.RegisterType<CommandMessageService>().AsSelf().SingleInstance();
|
builder.RegisterType<CommandMessageService>().AsSelf().SingleInstance();
|
||||||
builder.RegisterType<InteractionDispatchService>().AsSelf().SingleInstance();
|
builder.RegisterType<InteractionDispatchService>().AsSelf().SingleInstance();
|
||||||
builder.RegisterType<AvatarHostingService>().AsSelf().SingleInstance();
|
builder.RegisterType<AvatarHostingService>().AsSelf().SingleInstance();
|
||||||
|
builder.RegisterType<HttpListenerService>().AsSelf().SingleInstance();
|
||||||
|
builder.RegisterType<RuntimeConfigService>().AsSelf().SingleInstance();
|
||||||
|
|
||||||
// Sentry stuff
|
// Sentry stuff
|
||||||
builder.Register(_ => new Scope(null)).AsSelf().InstancePerLifetimeScope();
|
builder.Register(_ => new Scope(null)).AsSelf().InstancePerLifetimeScope();
|
||||||
|
|
|
||||||
|
|
@ -24,5 +24,6 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
||||||
<PackageReference Include="Sentry" Version="4.13.0" />
|
<PackageReference Include="Sentry" Version="4.13.0" />
|
||||||
|
<PackageReference Include="Watson.Lite" Version="6.3.5" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
56
PluralKit.Bot/Services/HttpListenerService.cs
Normal file
56
PluralKit.Bot/Services/HttpListenerService.cs
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
using WatsonWebserver.Lite;
|
||||||
|
using WatsonWebserver.Core;
|
||||||
|
|
||||||
|
namespace PluralKit.Bot;
|
||||||
|
|
||||||
|
public class HttpListenerService
|
||||||
|
{
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly RuntimeConfigService _runtimeConfig;
|
||||||
|
|
||||||
|
public HttpListenerService(ILogger logger, RuntimeConfigService runtimeConfig)
|
||||||
|
{
|
||||||
|
_logger = logger.ForContext<HttpListenerService>();
|
||||||
|
_runtimeConfig = runtimeConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Start(string host)
|
||||||
|
{
|
||||||
|
var server = new WebserverLite(new WebserverSettings(host, 5002), DefaultRoute);
|
||||||
|
|
||||||
|
server.Routes.PreAuthentication.Static.Add(WatsonWebserver.Core.HttpMethod.GET, "/runtime_config", RuntimeConfigGet);
|
||||||
|
server.Routes.PreAuthentication.Parameter.Add(WatsonWebserver.Core.HttpMethod.POST, "/runtime_config/{key}", RuntimeConfigSet);
|
||||||
|
server.Routes.PreAuthentication.Parameter.Add(WatsonWebserver.Core.HttpMethod.DELETE, "/runtime_config/{key}", RuntimeConfigDelete);
|
||||||
|
|
||||||
|
server.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DefaultRoute(HttpContextBase ctx)
|
||||||
|
=> await ctx.Response.Send("hellorld");
|
||||||
|
|
||||||
|
private async Task RuntimeConfigGet(HttpContextBase ctx)
|
||||||
|
{
|
||||||
|
var config = _runtimeConfig.GetAll();
|
||||||
|
ctx.Response.Headers.Add("content-type", "application/json");
|
||||||
|
await ctx.Response.Send(JsonConvert.SerializeObject(config));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RuntimeConfigSet(HttpContextBase ctx)
|
||||||
|
{
|
||||||
|
var key = ctx.Request.Url.Parameters["key"];
|
||||||
|
var value = ctx.Request.DataAsString;
|
||||||
|
await _runtimeConfig.Set(key, value);
|
||||||
|
await RuntimeConfigGet(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RuntimeConfigDelete(HttpContextBase ctx)
|
||||||
|
{
|
||||||
|
var key = ctx.Request.Url.Parameters["key"];
|
||||||
|
await _runtimeConfig.Delete(key);
|
||||||
|
await RuntimeConfigGet(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
58
PluralKit.Bot/Services/RuntimeConfigService.cs
Normal file
58
PluralKit.Bot/Services/RuntimeConfigService.cs
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
using StackExchange.Redis;
|
||||||
|
|
||||||
|
using PluralKit.Core;
|
||||||
|
|
||||||
|
namespace PluralKit.Bot;
|
||||||
|
|
||||||
|
public class RuntimeConfigService
|
||||||
|
{
|
||||||
|
private readonly RedisService _redis;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
private Dictionary<string, string> settings = new();
|
||||||
|
|
||||||
|
private string RedisKey;
|
||||||
|
|
||||||
|
public RuntimeConfigService(ILogger logger, RedisService redis, BotConfig config)
|
||||||
|
{
|
||||||
|
_logger = logger.ForContext<RuntimeConfigService>();
|
||||||
|
_redis = redis;
|
||||||
|
|
||||||
|
var clusterId = config.Cluster?.NodeIndex ?? 0;
|
||||||
|
RedisKey = $"remote_config:dotnet_bot:{clusterId}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LoadConfig()
|
||||||
|
{
|
||||||
|
var redisConfig = await _redis.Connection.GetDatabase().HashGetAllAsync(RedisKey);
|
||||||
|
foreach (var entry in redisConfig)
|
||||||
|
settings.Add(entry.Name, entry.Value);
|
||||||
|
|
||||||
|
var configStr = JsonConvert.SerializeObject(settings);
|
||||||
|
_logger.Information($"starting with runtime config: {configStr}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Set(string key, string value)
|
||||||
|
{
|
||||||
|
await _redis.Connection.GetDatabase().HashSetAsync(RedisKey, new[] { new HashEntry(key, new RedisValue(value)) });
|
||||||
|
settings.Add(key, value);
|
||||||
|
_logger.Information($"updated runtime config: {key}={value}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Delete(string key)
|
||||||
|
{
|
||||||
|
await _redis.Connection.GetDatabase().HashDeleteAsync(RedisKey, key);
|
||||||
|
settings.Remove(key);
|
||||||
|
_logger.Information($"updated runtime config: {key} removed");
|
||||||
|
}
|
||||||
|
|
||||||
|
public object? Get(string key) => settings.GetValueOrDefault(key);
|
||||||
|
|
||||||
|
public bool Exists(string key) => settings.ContainsKey(key);
|
||||||
|
|
||||||
|
public Dictionary<string, string> GetAll() => settings;
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,16 @@
|
||||||
"resolved": "4.13.0",
|
"resolved": "4.13.0",
|
||||||
"contentHash": "Wfw3M1WpFcrYaGzPm7QyUTfIOYkVXQ1ry6p4WYjhbLz9fPwV23SGQZTFDpdox67NHM0V0g1aoQ4YKLm4ANtEEg=="
|
"contentHash": "Wfw3M1WpFcrYaGzPm7QyUTfIOYkVXQ1ry6p4WYjhbLz9fPwV23SGQZTFDpdox67NHM0V0g1aoQ4YKLm4ANtEEg=="
|
||||||
},
|
},
|
||||||
|
"Watson.Lite": {
|
||||||
|
"type": "Direct",
|
||||||
|
"requested": "[6.3.5, )",
|
||||||
|
"resolved": "6.3.5",
|
||||||
|
"contentHash": "YF8+se3IVenn8YlyNeb4wSJK6QMnVD0QHIOEiZ22wS4K2wkwoSDzWS+ZAjk1MaPeB+XO5gRoENUN//pOc+wI2g==",
|
||||||
|
"dependencies": {
|
||||||
|
"CavemanTcp": "2.0.5",
|
||||||
|
"Watson.Core": "6.3.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"App.Metrics": {
|
"App.Metrics": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "4.3.0",
|
"resolved": "4.3.0",
|
||||||
|
|
@ -107,6 +117,11 @@
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.1"
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"CavemanTcp": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.0.5",
|
||||||
|
"contentHash": "90wywmGpjrj26HMAkufYZwuZI8sVYB1mRwEdqugSR3kgDnPX+3l0jO86gwtFKsPvsEpsS4Dn/1EbhguzUxMU8Q=="
|
||||||
|
},
|
||||||
"Dapper": {
|
"Dapper": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "2.1.35",
|
"resolved": "2.1.35",
|
||||||
|
|
@ -130,6 +145,11 @@
|
||||||
"System.Diagnostics.DiagnosticSource": "5.0.0"
|
"System.Diagnostics.DiagnosticSource": "5.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"IpMatcher": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "1.0.5",
|
||||||
|
"contentHash": "WXNlWERj+0GN699AnMNsuJ7PfUAbU4xhOHP3nrNXLHqbOaBxybu25luSYywX1133NSlitA4YkSNmJuyPvea4sw=="
|
||||||
|
},
|
||||||
"IPNetwork2": {
|
"IPNetwork2": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "3.0.667",
|
"resolved": "3.0.667",
|
||||||
|
|
@ -391,6 +411,11 @@
|
||||||
"resolved": "8.5.0",
|
"resolved": "8.5.0",
|
||||||
"contentHash": "VYYMZNitZ85UEhwOKkTQI63WEMvzUqwQc74I2mm8h/DBVAMcBBxqYPni4DmuRtbCwngmuONuK2yBJfWNRKzI+A=="
|
"contentHash": "VYYMZNitZ85UEhwOKkTQI63WEMvzUqwQc74I2mm8h/DBVAMcBBxqYPni4DmuRtbCwngmuONuK2yBJfWNRKzI+A=="
|
||||||
},
|
},
|
||||||
|
"RegexMatcher": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "1.0.9",
|
||||||
|
"contentHash": "RkQGXIrqHjD5h1mqefhgCbkaSdRYNRG5rrbzyw5zeLWiS0K1wq9xR3cNhQdzYR2MsKZ3GN523yRUsEQIMPxh3Q=="
|
||||||
|
},
|
||||||
"Serilog": {
|
"Serilog": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "4.2.0",
|
"resolved": "4.2.0",
|
||||||
|
|
@ -714,6 +739,28 @@
|
||||||
"System.Runtime": "4.3.0"
|
"System.Runtime": "4.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Timestamps": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "1.0.11",
|
||||||
|
"contentHash": "SnWhXm3FkEStQGgUTfWMh9mKItNW032o/v8eAtFrOGqG0/ejvPPA1LdLZx0N/qqoY0TH3x11+dO00jeVcM8xNQ=="
|
||||||
|
},
|
||||||
|
"UrlMatcher": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "3.0.1",
|
||||||
|
"contentHash": "hHBZVzFSfikrx4XsRsnCIwmGLgbNKtntnlqf4z+ygcNA6Y/L/J0x5GiZZWfXdTfpxhy5v7mlt2zrZs/L9SvbOA=="
|
||||||
|
},
|
||||||
|
"Watson.Core": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "6.3.5",
|
||||||
|
"contentHash": "Y5YxKOCSLe2KDmfwvI/J0qApgmmZR77LwyoufRVfKH7GLdHiE7fY0IfoNxWTG7nNv8knBfgwyOxdehRm+4HaCg==",
|
||||||
|
"dependencies": {
|
||||||
|
"IpMatcher": "1.0.5",
|
||||||
|
"RegexMatcher": "1.0.9",
|
||||||
|
"System.Text.Json": "8.0.5",
|
||||||
|
"Timestamps": "1.0.11",
|
||||||
|
"UrlMatcher": "3.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"myriad": {
|
"myriad": {
|
||||||
"type": "Project",
|
"type": "Project",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,11 @@
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.1"
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"CavemanTcp": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.0.5",
|
||||||
|
"contentHash": "90wywmGpjrj26HMAkufYZwuZI8sVYB1mRwEdqugSR3kgDnPX+3l0jO86gwtFKsPvsEpsS4Dn/1EbhguzUxMU8Q=="
|
||||||
|
},
|
||||||
"Dapper": {
|
"Dapper": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "2.1.35",
|
"resolved": "2.1.35",
|
||||||
|
|
@ -156,6 +161,11 @@
|
||||||
"resolved": "2.14.1",
|
"resolved": "2.14.1",
|
||||||
"contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw=="
|
"contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw=="
|
||||||
},
|
},
|
||||||
|
"IpMatcher": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "1.0.5",
|
||||||
|
"contentHash": "WXNlWERj+0GN699AnMNsuJ7PfUAbU4xhOHP3nrNXLHqbOaBxybu25luSYywX1133NSlitA4YkSNmJuyPvea4sw=="
|
||||||
|
},
|
||||||
"IPNetwork2": {
|
"IPNetwork2": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "3.0.667",
|
"resolved": "3.0.667",
|
||||||
|
|
@ -510,6 +520,11 @@
|
||||||
"resolved": "8.5.0",
|
"resolved": "8.5.0",
|
||||||
"contentHash": "VYYMZNitZ85UEhwOKkTQI63WEMvzUqwQc74I2mm8h/DBVAMcBBxqYPni4DmuRtbCwngmuONuK2yBJfWNRKzI+A=="
|
"contentHash": "VYYMZNitZ85UEhwOKkTQI63WEMvzUqwQc74I2mm8h/DBVAMcBBxqYPni4DmuRtbCwngmuONuK2yBJfWNRKzI+A=="
|
||||||
},
|
},
|
||||||
|
"RegexMatcher": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "1.0.9",
|
||||||
|
"contentHash": "RkQGXIrqHjD5h1mqefhgCbkaSdRYNRG5rrbzyw5zeLWiS0K1wq9xR3cNhQdzYR2MsKZ3GN523yRUsEQIMPxh3Q=="
|
||||||
|
},
|
||||||
"Sentry": {
|
"Sentry": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "4.13.0",
|
"resolved": "4.13.0",
|
||||||
|
|
@ -887,6 +902,37 @@
|
||||||
"System.Runtime": "4.3.0"
|
"System.Runtime": "4.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Timestamps": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "1.0.11",
|
||||||
|
"contentHash": "SnWhXm3FkEStQGgUTfWMh9mKItNW032o/v8eAtFrOGqG0/ejvPPA1LdLZx0N/qqoY0TH3x11+dO00jeVcM8xNQ=="
|
||||||
|
},
|
||||||
|
"UrlMatcher": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "3.0.1",
|
||||||
|
"contentHash": "hHBZVzFSfikrx4XsRsnCIwmGLgbNKtntnlqf4z+ygcNA6Y/L/J0x5GiZZWfXdTfpxhy5v7mlt2zrZs/L9SvbOA=="
|
||||||
|
},
|
||||||
|
"Watson.Core": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "6.3.5",
|
||||||
|
"contentHash": "Y5YxKOCSLe2KDmfwvI/J0qApgmmZR77LwyoufRVfKH7GLdHiE7fY0IfoNxWTG7nNv8knBfgwyOxdehRm+4HaCg==",
|
||||||
|
"dependencies": {
|
||||||
|
"IpMatcher": "1.0.5",
|
||||||
|
"RegexMatcher": "1.0.9",
|
||||||
|
"System.Text.Json": "8.0.5",
|
||||||
|
"Timestamps": "1.0.11",
|
||||||
|
"UrlMatcher": "3.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Watson.Lite": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "6.3.5",
|
||||||
|
"contentHash": "YF8+se3IVenn8YlyNeb4wSJK6QMnVD0QHIOEiZ22wS4K2wkwoSDzWS+ZAjk1MaPeB+XO5gRoENUN//pOc+wI2g==",
|
||||||
|
"dependencies": {
|
||||||
|
"CavemanTcp": "2.0.5",
|
||||||
|
"Watson.Core": "6.3.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"xunit.abstractions": {
|
"xunit.abstractions": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "2.0.3",
|
"resolved": "2.0.3",
|
||||||
|
|
@ -954,7 +1000,8 @@
|
||||||
"Humanizer.Core": "[2.14.1, )",
|
"Humanizer.Core": "[2.14.1, )",
|
||||||
"Myriad": "[1.0.0, )",
|
"Myriad": "[1.0.0, )",
|
||||||
"PluralKit.Core": "[1.0.0, )",
|
"PluralKit.Core": "[1.0.0, )",
|
||||||
"Sentry": "[4.13.0, )"
|
"Sentry": "[4.13.0, )",
|
||||||
|
"Watson.Lite": "[6.3.5, )"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pluralkit.core": {
|
"pluralkit.core": {
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,10 @@ use axum::{
|
||||||
extract::{Path, State},
|
extract::{Path, State},
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
response::{IntoResponse, Response},
|
response::{IntoResponse, Response},
|
||||||
routing::get,
|
routing::{delete, get, post},
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
|
use libpk::runtime_config::RuntimeConfig;
|
||||||
use serde_json::{json, to_string};
|
use serde_json::{json, to_string};
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
use twilight_model::id::Id;
|
use twilight_model::id::Id;
|
||||||
|
|
@ -21,7 +22,11 @@ fn status_code(code: StatusCode, body: String) -> Response {
|
||||||
|
|
||||||
// this function is manually formatted for easier legibility of route_services
|
// this function is manually formatted for easier legibility of route_services
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
pub async fn run_server(cache: Arc<DiscordCache>) -> anyhow::Result<()> {
|
pub async fn run_server(cache: Arc<DiscordCache>, runtime_config: Arc<RuntimeConfig>) -> anyhow::Result<()> {
|
||||||
|
// hacky fix for `move`
|
||||||
|
let runtime_config_for_post = runtime_config.clone();
|
||||||
|
let runtime_config_for_delete = runtime_config.clone();
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route(
|
.route(
|
||||||
"/guilds/:guild_id",
|
"/guilds/:guild_id",
|
||||||
|
|
@ -171,6 +176,20 @@ pub async fn run_server(cache: Arc<DiscordCache>) -> anyhow::Result<()> {
|
||||||
status_code(StatusCode::FOUND, to_string(&stats).unwrap())
|
status_code(StatusCode::FOUND, to_string(&stats).unwrap())
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
.route("/runtime_config", get(|| async move {
|
||||||
|
status_code(StatusCode::FOUND, to_string(&runtime_config.get_all().await).unwrap())
|
||||||
|
}))
|
||||||
|
.route("/runtime_config/:key", post(|Path(key): Path<String>, body: String| async move {
|
||||||
|
let runtime_config = runtime_config_for_post;
|
||||||
|
runtime_config.set(key, body).await.expect("failed to update runtime config");
|
||||||
|
status_code(StatusCode::FOUND, to_string(&runtime_config.get_all().await).unwrap())
|
||||||
|
}))
|
||||||
|
.route("/runtime_config/:key", delete(|Path(key): Path<String>| async move {
|
||||||
|
let runtime_config = runtime_config_for_delete;
|
||||||
|
runtime_config.delete(key).await.expect("failed to update runtime config");
|
||||||
|
status_code(StatusCode::FOUND, to_string(&runtime_config.get_all().await).unwrap())
|
||||||
|
}))
|
||||||
|
|
||||||
.layer(axum::middleware::from_fn(crate::logger::logger))
|
.layer(axum::middleware::from_fn(crate::logger::logger))
|
||||||
.with_state(cache);
|
.with_state(cache);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,9 @@
|
||||||
#![feature(if_let_guard)]
|
#![feature(if_let_guard)]
|
||||||
|
|
||||||
use chrono::Timelike;
|
use chrono::Timelike;
|
||||||
|
use discord::gateway::cluster_config;
|
||||||
use fred::{clients::RedisPool, interfaces::*};
|
use fred::{clients::RedisPool, interfaces::*};
|
||||||
|
use libpk::runtime_config::RuntimeConfig;
|
||||||
use signal_hook::{
|
use signal_hook::{
|
||||||
consts::{SIGINT, SIGTERM},
|
consts::{SIGINT, SIGTERM},
|
||||||
iterator::Signals,
|
iterator::Signals,
|
||||||
|
|
@ -28,6 +30,14 @@ async fn real_main() -> anyhow::Result<()> {
|
||||||
|
|
||||||
let redis = libpk::db::init_redis().await?;
|
let redis = libpk::db::init_redis().await?;
|
||||||
|
|
||||||
|
let runtime_config = Arc::new(
|
||||||
|
RuntimeConfig::new(
|
||||||
|
redis.clone(),
|
||||||
|
format!("gateway:{}", cluster_config().node_id),
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
|
);
|
||||||
|
|
||||||
let shard_state = discord::shard_state::new(redis.clone());
|
let shard_state = discord::shard_state::new(redis.clone());
|
||||||
let cache = Arc::new(discord::cache::new());
|
let cache = Arc::new(discord::cache::new());
|
||||||
|
|
||||||
|
|
@ -57,7 +67,7 @@ async fn real_main() -> anyhow::Result<()> {
|
||||||
// todo: probably don't do it this way
|
// todo: probably don't do it this way
|
||||||
let api_shutdown_tx = shutdown_tx.clone();
|
let api_shutdown_tx = shutdown_tx.clone();
|
||||||
set.spawn(tokio::spawn(async move {
|
set.spawn(tokio::spawn(async move {
|
||||||
match cache_api::run_server(cache).await {
|
match cache_api::run_server(cache, runtime_config).await {
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
tracing::error!(?error, "failed to serve cache api");
|
tracing::error!(?error, "failed to serve cache api");
|
||||||
let _ = api_shutdown_tx.send(());
|
let _ = api_shutdown_tx.send(());
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilte
|
||||||
use sentry_tracing::event_from_event;
|
use sentry_tracing::event_from_event;
|
||||||
|
|
||||||
pub mod db;
|
pub mod db;
|
||||||
|
pub mod runtime_config;
|
||||||
pub mod state;
|
pub mod state;
|
||||||
|
|
||||||
pub mod _config;
|
pub mod _config;
|
||||||
|
|
|
||||||
72
crates/libpk/src/runtime_config.rs
Normal file
72
crates/libpk/src/runtime_config.rs
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
use fred::{clients::RedisPool, interfaces::HashesInterface};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
|
pub struct RuntimeConfig {
|
||||||
|
redis: RedisPool,
|
||||||
|
settings: RwLock<HashMap<String, String>>,
|
||||||
|
redis_key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RuntimeConfig {
|
||||||
|
pub async fn new(redis: RedisPool, component_key: String) -> anyhow::Result<Self> {
|
||||||
|
let redis_key = format!("remote_config:{component_key}");
|
||||||
|
|
||||||
|
let mut c = RuntimeConfig {
|
||||||
|
redis,
|
||||||
|
settings: RwLock::new(HashMap::new()),
|
||||||
|
redis_key,
|
||||||
|
};
|
||||||
|
|
||||||
|
c.load().await?;
|
||||||
|
|
||||||
|
Ok(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn load(&mut self) -> anyhow::Result<()> {
|
||||||
|
let redis_config: HashMap<String, String> = self.redis.hgetall(&self.redis_key).await?;
|
||||||
|
|
||||||
|
let mut settings = self.settings.write().await;
|
||||||
|
|
||||||
|
for (key, value) in redis_config {
|
||||||
|
settings.insert(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("starting with runtime config: {:?}", self.settings);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set(&self, key: String, value: String) -> anyhow::Result<()> {
|
||||||
|
self.redis
|
||||||
|
.hset::<(), &str, (String, String)>(&self.redis_key, (key.clone(), value.clone()))
|
||||||
|
.await?;
|
||||||
|
self.settings
|
||||||
|
.write()
|
||||||
|
.await
|
||||||
|
.insert(key.clone(), value.clone());
|
||||||
|
info!("updated runtime config: {key}={value}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete(&self, key: String) -> anyhow::Result<()> {
|
||||||
|
self.redis
|
||||||
|
.hdel::<(), &str, String>(&self.redis_key, key.clone())
|
||||||
|
.await?;
|
||||||
|
self.settings.write().await.remove(&key.clone());
|
||||||
|
info!("updated runtime config: {key} removed");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get(&self, key: String) -> Option<String> {
|
||||||
|
self.settings.read().await.get(&key).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn exists(&self, key: &str) -> bool {
|
||||||
|
self.settings.read().await.contains_key(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_all(&self) -> HashMap<String, String> {
|
||||||
|
self.settings.read().await.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue