first commit

This commit is contained in:
2026-03-02 20:53:28 +01:00
commit d27c205106
63 changed files with 4593 additions and 0 deletions

14
Config/AppConfig.cs Normal file
View File

@@ -0,0 +1,14 @@
using System.Text.Json.Serialization;
namespace HanaToolbox.Config;
public sealed class AppConfig
{
public HanaConfig Hana { get; set; } = new();
public BackupConfig Backup { get; set; } = new();
public CleanerConfig Cleaner { get; set; } = new();
public MonitorConfig Monitor { get; set; } = new();
public FirewallConfig Firewall { get; set; } = new();
public AuroraConfig Aurora { get; set; } = new();
public NtfyConfig Ntfy { get; set; } = new();
}

View File

@@ -0,0 +1,26 @@
using System.Text.Json.Serialization;
namespace HanaToolbox.Config;
/// <summary>
/// AOT-compatible JSON source generation context.
/// All config types serialized by the app must be listed here.
/// </summary>
[JsonSerializable(typeof(AppConfig))]
[JsonSerializable(typeof(HanaConfig))]
[JsonSerializable(typeof(BackupConfig))]
[JsonSerializable(typeof(CleanerConfig))]
[JsonSerializable(typeof(MonitorConfig))]
[JsonSerializable(typeof(FirewallConfig))]
[JsonSerializable(typeof(FirewallServiceEntry))]
[JsonSerializable(typeof(AuroraConfig))]
[JsonSerializable(typeof(NtfyConfig))]
[JsonSerializable(typeof(List<string>))]
[JsonSerializable(typeof(List<FirewallServiceEntry>))]
[JsonSourceGenerationOptions(
WriteIndented = true,
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
UseStringEnumConverter = true)]
internal partial class AppConfigJsonContext : JsonSerializerContext
{
}

23
Config/AuroraConfig.cs Normal file
View File

@@ -0,0 +1,23 @@
namespace HanaToolbox.Config;
public sealed class AuroraConfig
{
public bool Enabled { get; set; } = false;
public int ScheduleHour { get; set; } = 5;
public int ScheduleMinute { get; set; } = 0;
/// <summary>hdbuserstore key with admin rights (DROP SCHEMA, EXPORT, IMPORT, GRANT).</summary>
public string AdminUserKey { get; set; } = "CRONKEY";
/// <summary>Source schema to export and re-import as &lt;SourceSchema&gt;_AURORA.</summary>
public string SourceSchema { get; set; } = string.Empty;
/// <summary>DB user that receives ALL PRIVILEGES on the Aurora schema.</summary>
public string AuroraUser { get; set; } = string.Empty;
/// <summary>Directory used for temporary export files during the Aurora refresh.</summary>
public string BackupBasePath { get; set; } = "/hana/backup/aurora";
/// <summary>Thread count for export/import. 0 = auto (nproc/2).</summary>
public int Threads { get; set; } = 0;
}

35
Config/BackupConfig.cs Normal file
View File

@@ -0,0 +1,35 @@
namespace HanaToolbox.Config;
public enum BackupType { Tenant, Schema, All }
public sealed class BackupConfig
{
public bool Enabled { get; set; } = false;
public int ScheduleHour { get; set; } = 2;
public int ScheduleMinute { get; set; } = 0;
/// <summary>What to back up: Tenant, Schema, or All.</summary>
public BackupType Type { get; set; } = BackupType.All;
/// <summary>hdbuserstore key for the tenant DB.</summary>
public string UserKey { get; set; } = "CRONKEY";
/// <summary>Base directory for tenant backup files.</summary>
public string BackupBasePath { get; set; } = "/hana/backup/tenant";
public bool Compress { get; set; } = true;
public bool BackupSystemDb { get; set; } = false;
public string SystemDbUserKey { get; set; } = string.Empty;
/// <summary>Schema names to export when Type is Schema or All.</summary>
public List<string> SchemaNames { get; set; } = [];
/// <summary>Base directory for schema export files.</summary>
public string SchemaBackupPath { get; set; } = "/hana/backup/schema";
public bool CompressSchema { get; set; } = true;
/// <summary>Thread count for export/import. 0 = auto (nproc/2).</summary>
public int Threads { get; set; } = 0;
}

15
Config/CleanerConfig.cs Normal file
View File

@@ -0,0 +1,15 @@
namespace HanaToolbox.Config;
public sealed class CleanerConfig
{
public bool Enabled { get; set; } = false;
public int ScheduleHour { get; set; } = 3;
public int ScheduleMinute { get; set; } = 0;
public string TenantBackupPath { get; set; } = "/hana/backup/tenant";
public int TenantRetentionDays { get; set; } = 7;
/// <summary>One or more log backup directories. Each is cleaned with LogRetentionDays.</summary>
public List<string> LogBackupPaths { get; set; } = [];
public int LogRetentionDays { get; set; } = 1;
}

40
Config/ConfigService.cs Normal file
View File

@@ -0,0 +1,40 @@
using System.Text.Json;
namespace HanaToolbox.Config;
public sealed class ConfigService
{
private const string ConfigDir = "/etc/hanatoolbox";
private const string ConfigFile = "/etc/hanatoolbox/hanatoolbox.json";
public static AppConfig Load()
{
if (!File.Exists(ConfigFile))
return new AppConfig();
try
{
var json = File.ReadAllText(ConfigFile);
return JsonSerializer.Deserialize(json, AppConfigJsonContext.Default.AppConfig)
?? new AppConfig();
}
catch (Exception ex)
{
throw new InvalidOperationException(
$"Failed to load config from {ConfigFile}: {ex.Message}", ex);
}
}
public static void Save(AppConfig config)
{
Directory.CreateDirectory(ConfigDir);
Directory.CreateDirectory(StateDirectory);
var json = JsonSerializer.Serialize(config, AppConfigJsonContext.Default.AppConfig);
File.WriteAllText(ConfigFile, json);
}
public static bool Exists() => File.Exists(ConfigFile);
public static string StateDirectory => "/etc/hanatoolbox/state";
}

36
Config/FirewallConfig.cs Normal file
View File

@@ -0,0 +1,36 @@
using System.Text.Json.Serialization;
namespace HanaToolbox.Config;
[JsonConverter(typeof(JsonStringEnumConverter<FirewallDecision>))]
public enum FirewallDecision { Skip, All, Ip }
public sealed class FirewallServiceEntry
{
public string Name { get; set; } = string.Empty;
public List<string> Ports { get; set; } = [];
public FirewallDecision Decision { get; set; } = FirewallDecision.Skip;
public List<string> AllowedIps { get; set; } = [];
}
public sealed class FirewallConfig
{
public bool Enabled { get; set; } = false;
public int ScheduleHour { get; set; } = 4;
public int ScheduleMinute { get; set; } = 0;
/// <summary>Whether to flush all existing rules before applying. Saved in config.</summary>
public bool FlushBeforeApply { get; set; } = false;
public List<FirewallServiceEntry> Services { get; set; } =
[
new() { Name = "SAP Web Client", Ports = ["443"], Decision = FirewallDecision.Skip },
new() { Name = "SAP HANA Database (System & Company DB)", Ports = ["30013","30015"], Decision = FirewallDecision.Skip },
new() { Name = "SAP Business One SLD", Ports = ["40000"], Decision = FirewallDecision.Skip },
new() { Name = "SAP Business One Auth", Ports = ["40020"], Decision = FirewallDecision.Skip },
new() { Name = "SAP Business One Service Layer/Cockpit", Ports = ["50000","4300"], Decision = FirewallDecision.Skip },
new() { Name = "SAP Host Agent", Ports = ["1128","1129"], Decision = FirewallDecision.Skip },
new() { Name = "SSH Remote Access", Ports = ["22"], Decision = FirewallDecision.Skip },
new() { Name = "SMB / B1_SHR (File Sharing)", Ports = ["139","445"], Decision = FirewallDecision.Skip },
];
}

14
Config/HanaConfig.cs Normal file
View File

@@ -0,0 +1,14 @@
namespace HanaToolbox.Config;
public sealed class HanaConfig
{
/// <summary>HANA System ID, e.g. NDB. Used to build the OS user &lt;sid&gt;adm.</summary>
public string Sid { get; set; } = "NDB";
public string InstanceNumber { get; set; } = "00";
/// <summary>Optional override for hdbsql binary path. Null = auto-detect.</summary>
public string? HdbsqlPath { get; set; }
/// <summary>Optional override for hdbuserstore binary path. Null = auto-detect.</summary>
public string? HdbuserstorePath { get; set; }
}

38
Config/MonitorConfig.cs Normal file
View File

@@ -0,0 +1,38 @@
namespace HanaToolbox.Config;
public sealed class MonitorConfig
{
public bool Enabled { get; set; } = false;
/// <summary>hdbuserstore key used for all monitoring SQL queries.</summary>
public string HanaUserKey { get; set; } = "CRONKEY";
public string HanaInstanceNumber { get; set; } = "00";
/// <summary>Full path to sapcontrol binary.</summary>
public string SapcontrolPath { get; set; } = "/usr/sap/NDB/HDB00/exe/sapcontrol";
/// <summary>Company name included in ntfy alert messages.</summary>
public string CompanyName { get; set; } = "MyCompany";
/// <summary>Disk usage alert threshold in percent.</summary>
public int DiskUsageThresholdPercent { get; set; } = 85;
/// <summary>Alert if truncated log segments exceed this percent of total.</summary>
public int TruncatedSegmentThresholdPercent { get; set; } = 80;
/// <summary>Alert if free log segments fall below this percent of total.</summary>
public int FreeSegmentThresholdPercent { get; set; } = 10;
/// <summary>Statement queue length above which a breach is counted.</summary>
public int StatementQueueThreshold { get; set; } = 10;
/// <summary>How many consecutive cron ticks above threshold before alerting.</summary>
public int StatementQueueConsecutiveRuns { get; set; } = 3;
/// <summary>Alert if the last successful backup is older than this many hours.</summary>
public int BackupThresholdHours { get; set; } = 26;
/// <summary>Directories checked for disk usage.</summary>
public List<string> DirectoriesToMonitor { get; set; } = ["/hana/data", "/hana/log"];
}

7
Config/NtfyConfig.cs Normal file
View File

@@ -0,0 +1,7 @@
namespace HanaToolbox.Config;
public sealed class NtfyConfig
{
public string Url { get; set; } = "https://ntfy.sh/your-topic";
public string Token { get; set; } = string.Empty;
}