Files
gott/Endpoints/ServerSettingsEndpoint.cs
T
Meleki cc46cf2bbc feat: HTTP/SOCKS Proxy-Support für alle HttpClient-Requests
- Program.cs: ProxyUrl aus Config lesen, WebProxy auf allen HttpClients via ConfigureHttpClientDefaults
- ServerSettingsEndpoint: ProxyUrl Setting im Frontend (Text-Feld)
- appsettings.json: ProxyUrl Default (leer = kein Proxy)
- Unterstützt HTTP und SOCKS5 Proxies (z.B. socks5://127.0.0.1:1080)
- Betrifft alle ausgehenden Requests: Anthropic, OpenAI, GitHub, Serper, Telegram, Krea
2026-03-13 10:09:45 +01:00

188 lines
7.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using Gott.Extensions;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
namespace Gott.Endpoints;
public static class ServerSettingsEndpoint
{
// Pfade werden beim MapEndpoints gesetzt – basierend auf ContentRootPath,
// da .NET die Config-Dateien vom ContentRoot lädt (= WorkingDirectory),
// nicht von AppContext.BaseDirectory (= Executable-Verzeichnis).
private static string ProductionConfigPath;
private static string FallbackConfigPath;
// Definiert alle konfigurierbaren Properties mit Typ und Standardwert
private static readonly List<SettingDefinition> Definitions = new List<SettingDefinition>
{
new SettingDefinition("Port", "int", 19000, "Server-Port (erfordert Neustart)", "number"),
new SettingDefinition("MaxIterations", "int", 100, "Max Tool-Loop Iterationen pro Chat", "number"),
new SettingDefinition("MaxHistory", "int", 120, "Max History-Messages pro Request", "number"),
new SettingDefinition("ReminderInterval", "int", 30, "System-Prompt Reminder alle N Messages (0 = deaktiviert)", "number"),
new SettingDefinition("ExecuteTimeout", "int", 180, "Timeout für Shell-Befehle in Sekunden", "number"),
new SettingDefinition("PayloadLogging", "bool", false, "API-Payloads loggen (Debug)", "toggle"),
new SettingDefinition("PayloadLoggingGzip", "bool", false, "Payload-Logs als .gz komprimieren", "toggle"),
new SettingDefinition("KreaToken", "string", "", "Krea API Token", "password"),
new SettingDefinition("KreaApiKey", "string", "", "API-Key für Krea-Endpoints (Automation)", "password"),
new SettingDefinition("GitHubToken", "string", "", "GitHub PAT für Key-Scanner", "password"),
new SettingDefinition("SerperApiKey", "string", "", "Serper.dev API-Key für Google-Suche (Key-Scanner)", "password"),
new SettingDefinition("ProxyUrl", "string", "", "HTTP/SOCKS Proxy für alle Requests (z.B. socks5://127.0.0.1:1080)", "text"),
new SettingDefinition("ScanIntervalHours", "int", 0, "Key-Scanner Intervall in Stunden (0 = nur manuell)", "number"),
};
public static void MapServerSettingsEndpoints(this WebApplication app)
{
var contentRoot = app.Environment.ContentRootPath;
ProductionConfigPath = Path.Combine(contentRoot, "appsettings.Production.json");
FallbackConfigPath = Path.Combine(contentRoot, "appsettings.json");
app.MapGet("/api/admin/server-settings", HandleGet);
app.MapPut("/api/admin/server-settings", HandleUpdate);
}
private static async Task HandleGet(HttpContext ctx, IConfiguration config)
{
if (!ctx.IsAdmin())
{
ctx.Response.StatusCode = 403;
await ctx.Response.WriteAsJsonAsync(new { error = "Admin access required" });
return;
}
// Production-Config lesen (falls vorhanden)
JsonObject prodConfig = null;
if (File.Exists(ProductionConfigPath))
{
var json = await File.ReadAllTextAsync(ProductionConfigPath);
prodConfig = JsonNode.Parse(json) as JsonObject;
}
// Fallback-Config lesen
JsonObject fallbackConfig = null;
if (File.Exists(FallbackConfigPath))
{
var json = await File.ReadAllTextAsync(FallbackConfigPath);
fallbackConfig = JsonNode.Parse(json) as JsonObject;
}
var settings = new List<object>();
foreach (var def in Definitions)
{
object value = def.DefaultValue;
// Fallback: appsettings.json
if (fallbackConfig != null && fallbackConfig.ContainsKey(def.Key))
{
value = ExtractValue(fallbackConfig[def.Key], def.Type);
}
// Override: appsettings.Production.json
if (prodConfig != null && prodConfig.ContainsKey(def.Key))
{
value = ExtractValue(prodConfig[def.Key], def.Type);
}
settings.Add(new
{
key = def.Key,
type = def.Type,
value = value,
label = def.Label,
inputType = def.InputType,
});
}
await ctx.Response.WriteAsJsonAsync(new { settings });
}
private static async Task HandleUpdate(HttpContext ctx)
{
if (!ctx.IsAdmin())
{
ctx.Response.StatusCode = 403;
await ctx.Response.WriteAsJsonAsync(new { error = "Admin access required" });
return;
}
var body = await JsonSerializer.DeserializeAsync<JsonElement>(ctx.Request.Body);
// Bestehende Production-Config laden oder neue erstellen
JsonObject prodConfig;
if (File.Exists(ProductionConfigPath))
{
var existing = await File.ReadAllTextAsync(ProductionConfigPath);
prodConfig = JsonNode.Parse(existing) as JsonObject ?? new JsonObject();
}
else
{
prodConfig = new JsonObject();
}
// Nur definierte Keys akzeptieren
foreach (var def in Definitions)
{
if (!body.TryGetProperty(def.Key, out var prop)) continue;
// Alten Wert entfernen falls vorhanden
prodConfig.Remove(def.Key);
switch (def.Type)
{
case "string":
prodConfig.Add(def.Key, prop.GetString() ?? "");
break;
case "int":
prodConfig.Add(def.Key, prop.TryGetInt32(out var i) ? i : (int)def.DefaultValue);
break;
case "bool":
prodConfig.Add(def.Key, prop.ValueKind == JsonValueKind.True);
break;
}
}
var options = new JsonSerializerOptions { WriteIndented = true };
var json = prodConfig.ToJsonString(options);
await File.WriteAllTextAsync(ProductionConfigPath, json);
await ctx.Response.WriteAsJsonAsync(new { ok = true, hint = "Die Änderungen erfordern einen Neustart des Servers." });
}
private static object ExtractValue(JsonNode node, string type)
{
if (node == null) return null;
switch (type)
{
case "string": return node.GetValue<string>();
case "int": return node.GetValue<int>();
case "bool": return node.GetValue<bool>();
default: return node.ToString();
}
}
private class SettingDefinition
{
public string Key { get; }
public string Type { get; }
public object DefaultValue { get; }
public string Label { get; }
public string InputType { get; }
public SettingDefinition(string key, string type, object defaultValue, string label, string inputType)
{
Key = key;
Type = type;
DefaultValue = defaultValue;
Label = label;
InputType = inputType;
}
}
}