first commit
This commit is contained in:
137
Tui/CronSetupTui.cs
Normal file
137
Tui/CronSetupTui.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
using HanaToolbox.Config;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace HanaToolbox.Tui;
|
||||
|
||||
/// <summary>
|
||||
/// TUI wizard for configuring cron task schedules and settings.
|
||||
/// Saves results back to the provided AppConfig (caller is responsible for persisting).
|
||||
/// </summary>
|
||||
public sealed class CronSetupTui
|
||||
{
|
||||
public AppConfig Run(AppConfig current)
|
||||
{
|
||||
AnsiConsole.Clear();
|
||||
AnsiConsole.Write(new Rule("[cyan]Cron Task Configuration[/]").RuleStyle("cyan"));
|
||||
AnsiConsole.MarkupLine("[grey]Configure which tasks run automatically and when.[/]\n");
|
||||
|
||||
ConfigureBackup(current.Backup);
|
||||
ConfigureCleaner(current.Cleaner);
|
||||
ConfigureAurora(current.Aurora, current.Hana);
|
||||
ConfigureFirewall(current.Firewall);
|
||||
ConfigureMonitor(current.Monitor);
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
private static void ConfigureBackup(BackupConfig b)
|
||||
{
|
||||
AnsiConsole.Write(new Rule("[green]Backup[/]").RuleStyle("green"));
|
||||
b.Enabled = AnsiConsole.Confirm("Enable scheduled backup?", b.Enabled);
|
||||
if (!b.Enabled) { AnsiConsole.WriteLine(); return; }
|
||||
|
||||
b.ScheduleHour = AnsiConsole.Prompt(new TextPrompt<int>("Run hour (0-23):").DefaultValue(b.ScheduleHour));
|
||||
b.ScheduleMinute = AnsiConsole.Prompt(new TextPrompt<int>("Run minute (0-59):").DefaultValue(b.ScheduleMinute));
|
||||
|
||||
var typeStr = AnsiConsole.Prompt(
|
||||
new SelectionPrompt<string>()
|
||||
.Title("Backup type:")
|
||||
.AddChoices("All", "Tenant", "Schema")
|
||||
.HighlightStyle("cyan"));
|
||||
b.Type = Enum.Parse<BackupType>(typeStr);
|
||||
b.UserKey = AnsiConsole.Prompt(new TextPrompt<string>("hdbuserstore key:").DefaultValue(b.UserKey));
|
||||
b.BackupBasePath = AnsiConsole.Prompt(new TextPrompt<string>("Tenant backup path:").DefaultValue(b.BackupBasePath));
|
||||
b.Compress = AnsiConsole.Confirm("Compress tenant backup?", b.Compress);
|
||||
|
||||
if (b.Type is BackupType.Schema or BackupType.All)
|
||||
{
|
||||
b.SchemaBackupPath = AnsiConsole.Prompt(new TextPrompt<string>("Schema backup path:").DefaultValue(b.SchemaBackupPath));
|
||||
b.CompressSchema = AnsiConsole.Confirm("Compress schema backup?", b.CompressSchema);
|
||||
var schemas = AnsiConsole.Prompt(new TextPrompt<string>("Schema names (comma-separated):")
|
||||
.DefaultValue(string.Join(",", b.SchemaNames)));
|
||||
b.SchemaNames = schemas.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(s => s.Trim()).ToList();
|
||||
}
|
||||
|
||||
b.BackupSystemDb = AnsiConsole.Confirm("Also backup SYSTEMDB?", b.BackupSystemDb);
|
||||
if (b.BackupSystemDb)
|
||||
b.SystemDbUserKey = AnsiConsole.Prompt(
|
||||
new TextPrompt<string>("SYSTEMDB hdbuserstore key:").DefaultValue(b.SystemDbUserKey));
|
||||
|
||||
AnsiConsole.WriteLine();
|
||||
}
|
||||
|
||||
private static void ConfigureCleaner(CleanerConfig c)
|
||||
{
|
||||
AnsiConsole.Write(new Rule("[green]Cleaner[/]").RuleStyle("green"));
|
||||
c.Enabled = AnsiConsole.Confirm("Enable scheduled cleanup?", c.Enabled);
|
||||
if (!c.Enabled) { AnsiConsole.WriteLine(); return; }
|
||||
|
||||
c.ScheduleHour = AnsiConsole.Prompt(new TextPrompt<int>("Run hour (0-23):").DefaultValue(c.ScheduleHour));
|
||||
c.ScheduleMinute = AnsiConsole.Prompt(new TextPrompt<int>("Run minute (0-59):").DefaultValue(c.ScheduleMinute));
|
||||
c.TenantBackupPath = AnsiConsole.Prompt(new TextPrompt<string>("Tenant backup path:").DefaultValue(c.TenantBackupPath));
|
||||
c.TenantRetentionDays = AnsiConsole.Prompt(new TextPrompt<int>("Tenant retention (days):").DefaultValue(c.TenantRetentionDays));
|
||||
c.LogRetentionDays = AnsiConsole.Prompt(new TextPrompt<int>("Log backup retention (days):").DefaultValue(c.LogRetentionDays));
|
||||
|
||||
AnsiConsole.MarkupLine("[grey]Current log backup paths:[/]");
|
||||
c.LogBackupPaths.ForEach(p => AnsiConsole.MarkupLine($" [grey]- {p}[/]"));
|
||||
|
||||
while (AnsiConsole.Confirm("Add a log backup path?", defaultValue: c.LogBackupPaths.Count == 0))
|
||||
{
|
||||
var p = AnsiConsole.Prompt(new TextPrompt<string>("Log backup path:"));
|
||||
if (!string.IsNullOrWhiteSpace(p)) c.LogBackupPaths.Add(p);
|
||||
}
|
||||
|
||||
AnsiConsole.WriteLine();
|
||||
}
|
||||
|
||||
private static void ConfigureAurora(AuroraConfig a, HanaConfig hana)
|
||||
{
|
||||
AnsiConsole.Write(new Rule("[green]Aurora[/]").RuleStyle("green"));
|
||||
a.Enabled = AnsiConsole.Confirm("Enable Aurora schema refresh?", a.Enabled);
|
||||
if (!a.Enabled) { AnsiConsole.WriteLine(); return; }
|
||||
|
||||
a.ScheduleHour = AnsiConsole.Prompt(new TextPrompt<int>("Run hour (0-23):").DefaultValue(a.ScheduleHour));
|
||||
a.ScheduleMinute = AnsiConsole.Prompt(new TextPrompt<int>("Run minute (0-59):").DefaultValue(a.ScheduleMinute));
|
||||
a.AdminUserKey = AnsiConsole.Prompt(new TextPrompt<string>("Admin hdbuserstore key:").DefaultValue(a.AdminUserKey));
|
||||
a.SourceSchema = AnsiConsole.Prompt(new TextPrompt<string>("Source schema name:").DefaultValue(a.SourceSchema));
|
||||
a.AuroraUser = AnsiConsole.Prompt(new TextPrompt<string>("Aurora target user:").DefaultValue(a.AuroraUser));
|
||||
a.BackupBasePath = AnsiConsole.Prompt(new TextPrompt<string>("Temp export base path:").DefaultValue(a.BackupBasePath));
|
||||
AnsiConsole.WriteLine();
|
||||
}
|
||||
|
||||
private static void ConfigureFirewall(FirewallConfig f)
|
||||
{
|
||||
AnsiConsole.Write(new Rule("[green]Firewall[/]").RuleStyle("green"));
|
||||
f.Enabled = AnsiConsole.Confirm("Enable scheduled firewall rule application?", f.Enabled);
|
||||
if (!f.Enabled) { AnsiConsole.WriteLine(); return; }
|
||||
|
||||
f.ScheduleHour = AnsiConsole.Prompt(new TextPrompt<int>("Run hour (0-23):").DefaultValue(f.ScheduleHour));
|
||||
f.ScheduleMinute = AnsiConsole.Prompt(new TextPrompt<int>("Run minute (0-59):").DefaultValue(f.ScheduleMinute));
|
||||
AnsiConsole.MarkupLine("[grey]Note: Firewall rules are configured via [cyan]hanatoolbox firewall[/][/]");
|
||||
AnsiConsole.WriteLine();
|
||||
}
|
||||
|
||||
private static void ConfigureMonitor(MonitorConfig m)
|
||||
{
|
||||
AnsiConsole.Write(new Rule("[green]Monitor[/]").RuleStyle("green"));
|
||||
m.Enabled = AnsiConsole.Confirm("Enable monitor (runs every cron tick)?", m.Enabled);
|
||||
if (!m.Enabled) { AnsiConsole.WriteLine(); return; }
|
||||
|
||||
m.HanaUserKey = AnsiConsole.Prompt(new TextPrompt<string>("Monitor hdbuserstore key:").DefaultValue(m.HanaUserKey));
|
||||
m.CompanyName = AnsiConsole.Prompt(new TextPrompt<string>("Company name (for alerts):").DefaultValue(m.CompanyName));
|
||||
m.SapcontrolPath = AnsiConsole.Prompt(new TextPrompt<string>("sapcontrol path:").DefaultValue(m.SapcontrolPath));
|
||||
m.DiskUsageThresholdPercent = AnsiConsole.Prompt(new TextPrompt<int>("Disk usage alert threshold (%):").DefaultValue(m.DiskUsageThresholdPercent));
|
||||
m.BackupThresholdHours = AnsiConsole.Prompt(new TextPrompt<int>("Max backup age (hours):").DefaultValue(m.BackupThresholdHours));
|
||||
|
||||
AnsiConsole.MarkupLine("[grey]Current monitored directories:[/]");
|
||||
m.DirectoriesToMonitor.ForEach(d => AnsiConsole.MarkupLine($" [grey]- {d}[/]"));
|
||||
while (AnsiConsole.Confirm("Add a directory to monitor?", defaultValue: false))
|
||||
{
|
||||
var d = AnsiConsole.Prompt(new TextPrompt<string>("Directory path:"));
|
||||
if (!string.IsNullOrWhiteSpace(d)) m.DirectoriesToMonitor.Add(d);
|
||||
}
|
||||
|
||||
AnsiConsole.WriteLine();
|
||||
}
|
||||
}
|
||||
149
Tui/FirewallTui.cs
Normal file
149
Tui/FirewallTui.cs
Normal file
@@ -0,0 +1,149 @@
|
||||
using HanaToolbox.Config;
|
||||
using HanaToolbox.Logging;
|
||||
using HanaToolbox.Services;
|
||||
using HanaToolbox.Services.Interfaces;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace HanaToolbox.Tui;
|
||||
|
||||
/// <summary>
|
||||
/// Interactive Firewall TUI using Spectre.Console.
|
||||
/// Mirrors the firewalld.sh interactive flow.
|
||||
/// </summary>
|
||||
public sealed class FirewallTui(
|
||||
FirewallService firewallService,
|
||||
AppLogger _logger)
|
||||
{
|
||||
public async Task<FirewallConfig?> RunAsync(
|
||||
FirewallConfig current, CancellationToken ct = default)
|
||||
{
|
||||
AnsiConsole.Clear();
|
||||
AnsiConsole.Write(new Rule("[cyan]SAP B1 Firewall Configurator[/]").RuleStyle("cyan"));
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
// Flush question
|
||||
var flush = AnsiConsole.Confirm("Flush (remove) all current firewall rules before applying? [creates clean slate]",
|
||||
defaultValue: current.FlushBeforeApply);
|
||||
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
// Per-service configuration
|
||||
var entries = new List<FirewallServiceEntry>();
|
||||
foreach (var svc in current.Services)
|
||||
{
|
||||
AnsiConsole.Write(new Rule($"[green]{svc.Name}[/] [grey]({string.Join(", ", svc.Ports)})[/]").RuleStyle("grey"));
|
||||
|
||||
var choice = AnsiConsole.Prompt(
|
||||
new SelectionPrompt<string>()
|
||||
.Title("Access rule:")
|
||||
.AddChoices("Allow from ANYWHERE (Public)", "Restrict to SPECIFIC IPs", "Skip / Block")
|
||||
.HighlightStyle("cyan"));
|
||||
|
||||
var decision = choice switch
|
||||
{
|
||||
"Allow from ANYWHERE (Public)" => FirewallDecision.All,
|
||||
"Restrict to SPECIFIC IPs" => FirewallDecision.Ip,
|
||||
_ => FirewallDecision.Skip
|
||||
};
|
||||
|
||||
var ips = new List<string>();
|
||||
if (decision == FirewallDecision.Ip)
|
||||
{
|
||||
var existing = string.Join(", ", svc.AllowedIps);
|
||||
var ipInput = AnsiConsole.Prompt(
|
||||
new TextPrompt<string>("Enter IPs or subnets (comma-separated):")
|
||||
.DefaultValue(existing.Length > 0 ? existing : "192.168.1.1")
|
||||
.AllowEmpty());
|
||||
|
||||
ips = ipInput
|
||||
.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(i => i.Trim())
|
||||
.Where(i => i.Length > 0)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
entries.Add(new FirewallServiceEntry
|
||||
{
|
||||
Name = svc.Name,
|
||||
Ports = svc.Ports,
|
||||
Decision = decision,
|
||||
AllowedIps = ips
|
||||
});
|
||||
|
||||
AnsiConsole.WriteLine();
|
||||
}
|
||||
|
||||
// Summary table
|
||||
AnsiConsole.Write(new Rule("[yellow]Summary[/]").RuleStyle("yellow"));
|
||||
var table = new Table()
|
||||
.AddColumn("Service")
|
||||
.AddColumn("Action")
|
||||
.AddColumn("Details");
|
||||
|
||||
foreach (var e in entries)
|
||||
{
|
||||
var (action, details) = e.Decision switch
|
||||
{
|
||||
FirewallDecision.All => ("[red]Open Public[/]", "0.0.0.0/0"),
|
||||
FirewallDecision.Ip => ("[green]Restricted[/]", string.Join(", ", e.AllowedIps)),
|
||||
_ => ("[grey]Blocked/Skip[/]", "-")
|
||||
};
|
||||
table.AddRow(e.Name.Length > 35 ? e.Name[..35] : e.Name, action, details);
|
||||
}
|
||||
AnsiConsole.Write(table);
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
if (!AnsiConsole.Confirm("Apply and save these rules?", defaultValue: true))
|
||||
{
|
||||
AnsiConsole.MarkupLine("[yellow]Aborted.[/]");
|
||||
return null;
|
||||
}
|
||||
|
||||
var updated = new FirewallConfig
|
||||
{
|
||||
FlushBeforeApply = flush,
|
||||
Services = entries,
|
||||
Enabled = current.Enabled,
|
||||
ScheduleHour = current.ScheduleHour,
|
||||
ScheduleMinute = current.ScheduleMinute
|
||||
};
|
||||
|
||||
// Apply rules
|
||||
await firewallService.ApplyAsync(updated, ct);
|
||||
|
||||
// Safety revert window
|
||||
AnsiConsole.MarkupLine("[yellow]Rules applied. You have 15 seconds to confirm your connection still works.[/]");
|
||||
AnsiConsole.MarkupLine("Press [cyan]ENTER[/] to keep changes permanently, or wait to auto-revert.");
|
||||
|
||||
var confirmed = await WaitForConfirmAsync(15, ct);
|
||||
if (!confirmed)
|
||||
{
|
||||
AnsiConsole.MarkupLine("[red]Timeout — reverting firewall to permanent config...[/]");
|
||||
await AnsiConsole.Status().StartAsync("Reverting...", async _ =>
|
||||
{
|
||||
await Task.Run(() =>
|
||||
System.Diagnostics.Process.Start("/bin/bash", "-c firewall-cmd --reload")?.WaitForExit(), ct);
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
AnsiConsole.MarkupLine("[green]Changes confirmed and saved permanently.[/]");
|
||||
return updated;
|
||||
}
|
||||
|
||||
private static async Task<bool> WaitForConfirmAsync(int seconds, CancellationToken ct)
|
||||
{
|
||||
var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
cts.CancelAfter(TimeSpan.FromSeconds(seconds));
|
||||
|
||||
try
|
||||
{
|
||||
await Task.Run(() => Console.ReadLine(), cts.Token);
|
||||
return true;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
134
Tui/KeyManagerTui.cs
Normal file
134
Tui/KeyManagerTui.cs
Normal file
@@ -0,0 +1,134 @@
|
||||
using HanaToolbox.Config;
|
||||
using HanaToolbox.Logging;
|
||||
using HanaToolbox.Services;
|
||||
using HanaToolbox.Services.Interfaces;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace HanaToolbox.Tui;
|
||||
|
||||
/// <summary>
|
||||
/// Interactive TUI for managing hdbuserstore keys.
|
||||
/// Mirrors keymanager.sh flow: Create / Delete / Test.
|
||||
/// </summary>
|
||||
public sealed class KeyManagerTui(
|
||||
KeyManagerService keyService,
|
||||
IHdbClientLocator locator,
|
||||
AppLogger _logger)
|
||||
{
|
||||
public async Task RunAsync(HanaConfig hana, string sid, CancellationToken ct = default)
|
||||
{
|
||||
var hdbsql = locator.LocateHdbsql(hana.HdbsqlPath, sid, hana.InstanceNumber);
|
||||
|
||||
while (true)
|
||||
{
|
||||
AnsiConsole.Clear();
|
||||
AnsiConsole.Write(new Rule("[blue]SAP HANA Secure User Store Key Manager[/]").RuleStyle("blue"));
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
var choice = AnsiConsole.Prompt(
|
||||
new SelectionPrompt<string>()
|
||||
.Title("Select an action:")
|
||||
.AddChoices("Create a New Key", "Delete an Existing Key",
|
||||
"Test an Existing Key", "Exit")
|
||||
.HighlightStyle("cyan"));
|
||||
|
||||
switch (choice)
|
||||
{
|
||||
case "Create a New Key":
|
||||
await CreateKeyAsync(hdbsql, hana, sid, ct);
|
||||
break;
|
||||
case "Delete an Existing Key":
|
||||
await DeleteKeyAsync(sid, ct);
|
||||
break;
|
||||
case "Test an Existing Key":
|
||||
await TestKeyAsync(hdbsql, sid, ct);
|
||||
break;
|
||||
case "Exit":
|
||||
return;
|
||||
}
|
||||
|
||||
AnsiConsole.MarkupLine("\n[grey]Press any key to continue...[/]");
|
||||
Console.ReadKey(intercept: true);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CreateKeyAsync(
|
||||
string hdbsql, HanaConfig hana, string sid, CancellationToken ct)
|
||||
{
|
||||
AnsiConsole.Write(new Rule("[blue]Create New Key[/]").RuleStyle("blue"));
|
||||
|
||||
var keyName = AnsiConsole.Prompt(new TextPrompt<string>("Key name:").DefaultValue("CRONKEY"));
|
||||
var host = AnsiConsole.Prompt(new TextPrompt<string>("HANA host:").DefaultValue(System.Net.Dns.GetHostName()));
|
||||
var instance = AnsiConsole.Prompt(new TextPrompt<string>("Instance number:").DefaultValue(hana.InstanceNumber));
|
||||
var isSystemDb = AnsiConsole.Confirm("Connecting to SYSTEMDB?", defaultValue: false);
|
||||
|
||||
string connStr;
|
||||
if (isSystemDb)
|
||||
{
|
||||
connStr = $"{host}:3{instance}13";
|
||||
}
|
||||
else
|
||||
{
|
||||
var tenant = AnsiConsole.Prompt(new TextPrompt<string>("Tenant DB name:").DefaultValue(sid.ToUpperInvariant()));
|
||||
connStr = $"{host}:3{instance}15@{tenant}";
|
||||
}
|
||||
|
||||
var user = AnsiConsole.Prompt(new TextPrompt<string>("Database user:").DefaultValue("SYSTEM"));
|
||||
var pass = AnsiConsole.Prompt(new TextPrompt<string>("Password:").Secret());
|
||||
|
||||
AnsiConsole.MarkupLine($"\n[yellow]Command preview:[/] hdbuserstore SET \"{keyName}\" \"{connStr}\" \"{user}\" <password>");
|
||||
if (!AnsiConsole.Confirm("Execute?", defaultValue: true)) return;
|
||||
|
||||
var created = await keyService.CreateKeyAsync(keyName, connStr, user, pass, sid, ct);
|
||||
if (!created) return;
|
||||
|
||||
// Auto-test and rollback on failure
|
||||
var ok = await keyService.TestKeyAsync(hdbsql, keyName, sid, ct);
|
||||
if (!ok)
|
||||
{
|
||||
AnsiConsole.MarkupLine("[yellow]Rolling back: deleting key due to connection failure...[/]");
|
||||
await keyService.DeleteKeyAsync(keyName, sid, ct);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteKeyAsync(string sid, CancellationToken ct)
|
||||
{
|
||||
AnsiConsole.Write(new Rule("[red]Delete Key[/]").RuleStyle("red"));
|
||||
|
||||
var keys = await keyService.ListKeysAsync(sid, ct);
|
||||
if (keys.Count == 0)
|
||||
{
|
||||
AnsiConsole.MarkupLine("[yellow]No keys found.[/]");
|
||||
return;
|
||||
}
|
||||
|
||||
var key = AnsiConsole.Prompt(
|
||||
new SelectionPrompt<string>()
|
||||
.Title("Select key to delete:")
|
||||
.AddChoices(keys)
|
||||
.HighlightStyle("red"));
|
||||
|
||||
if (!AnsiConsole.Confirm($"Permanently delete '{key}'?", defaultValue: false)) return;
|
||||
await keyService.DeleteKeyAsync(key, sid, ct);
|
||||
}
|
||||
|
||||
private async Task TestKeyAsync(string hdbsql, string sid, CancellationToken ct)
|
||||
{
|
||||
AnsiConsole.Write(new Rule("[blue]Test Key[/]").RuleStyle("blue"));
|
||||
|
||||
var keys = await keyService.ListKeysAsync(sid, ct);
|
||||
if (keys.Count == 0)
|
||||
{
|
||||
AnsiConsole.MarkupLine("[yellow]No keys found.[/]");
|
||||
return;
|
||||
}
|
||||
|
||||
var key = AnsiConsole.Prompt(
|
||||
new SelectionPrompt<string>()
|
||||
.Title("Select key to test:")
|
||||
.AddChoices(keys)
|
||||
.HighlightStyle("cyan"));
|
||||
|
||||
await keyService.TestKeyAsync(hdbsql, key, sid, ct);
|
||||
}
|
||||
}
|
||||
112
Tui/OnboardTui.cs
Normal file
112
Tui/OnboardTui.cs
Normal file
@@ -0,0 +1,112 @@
|
||||
using HanaToolbox.Config;
|
||||
using HanaToolbox.Logging;
|
||||
using HanaToolbox.Services;
|
||||
using HanaToolbox.Services.Interfaces;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace HanaToolbox.Tui;
|
||||
|
||||
/// <summary>
|
||||
/// Full guided onboarding wizard.
|
||||
/// Walks through every setting, creates hdbuserstore keys, and writes hanatoolbox.json.
|
||||
/// </summary>
|
||||
public sealed class OnboardTui(
|
||||
KeyManagerTui keyManagerTui,
|
||||
CronSetupTui cronSetupTui,
|
||||
FirewallTui firewallTui,
|
||||
AppLogger _logger)
|
||||
{
|
||||
public async Task RunAsync(CancellationToken ct = default)
|
||||
{
|
||||
AnsiConsole.Clear();
|
||||
AnsiConsole.Write(new FigletText("HanaToolbox").Color(Color.Cyan1));
|
||||
AnsiConsole.Write(new Rule("[cyan]Initial Setup Wizard[/]").RuleStyle("cyan"));
|
||||
AnsiConsole.MarkupLine("[grey]This wizard will configure HanaToolbox for this system.[/]\n");
|
||||
|
||||
// Root check
|
||||
if (Environment.UserName != "root")
|
||||
{
|
||||
AnsiConsole.MarkupLine("[red]Warning: Not running as root. Some operations may fail.[/]");
|
||||
if (!AnsiConsole.Confirm("Continue anyway?", defaultValue: false)) return;
|
||||
}
|
||||
|
||||
var config = ConfigService.Exists() ? ConfigService.Load() : new AppConfig();
|
||||
|
||||
// Step 1: HANA global settings
|
||||
AnsiConsole.Write(new Rule("[blue]Step 1 of 7 — HANA Settings[/]").RuleStyle("blue"));
|
||||
config.Hana.Sid = AnsiConsole.Prompt(new TextPrompt<string>("HANA SID:").DefaultValue(config.Hana.Sid));
|
||||
config.Hana.InstanceNumber = AnsiConsole.Prompt(new TextPrompt<string>("Instance number:").DefaultValue(config.Hana.InstanceNumber));
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
// Step 2: hdbuserstore keys
|
||||
AnsiConsole.Write(new Rule("[blue]Step 2 of 7 — Key Manager[/]").RuleStyle("blue"));
|
||||
AnsiConsole.MarkupLine("[grey]Set up hdbuserstore keys needed for automated operations.[/]");
|
||||
if (AnsiConsole.Confirm("Open Key Manager now?", defaultValue: true))
|
||||
await keyManagerTui.RunAsync(config.Hana, config.Hana.Sid, ct);
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
// Step 3: Cron tasks
|
||||
AnsiConsole.Write(new Rule("[blue]Step 3 of 7 — Cron Task Settings[/]").RuleStyle("blue"));
|
||||
config = cronSetupTui.Run(config);
|
||||
|
||||
// Step 4: Firewall
|
||||
AnsiConsole.Write(new Rule("[blue]Step 4 of 7 — Firewall[/]").RuleStyle("blue"));
|
||||
if (AnsiConsole.Confirm("Configure firewall rules now?", defaultValue: true))
|
||||
{
|
||||
var updated = await firewallTui.RunAsync(config.Firewall, ct);
|
||||
if (updated != null) config.Firewall = updated;
|
||||
}
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
// Step 5: Binary paths (optional overrides)
|
||||
AnsiConsole.Write(new Rule("[blue]Step 5 of 7 — Binary Paths (optional)[/]").RuleStyle("blue"));
|
||||
AnsiConsole.MarkupLine("[grey]Leave empty to use auto-detection (which, /usr/sap/hdbclient, etc.)[/]");
|
||||
var hdbsqlOverride = AnsiConsole.Prompt(
|
||||
new TextPrompt<string>("hdbsql path override (empty = auto):").AllowEmpty().DefaultValue(""));
|
||||
var hdbusOverride = AnsiConsole.Prompt(
|
||||
new TextPrompt<string>("hdbuserstore path override (empty = auto):").AllowEmpty().DefaultValue(""));
|
||||
if (!string.IsNullOrWhiteSpace(hdbsqlOverride)) config.Hana.HdbsqlPath = hdbsqlOverride;
|
||||
if (!string.IsNullOrWhiteSpace(hdbusOverride)) config.Hana.HdbuserstorePath = hdbusOverride;
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
// Step 6: ntfy notifications
|
||||
AnsiConsole.Write(new Rule("[blue]Step 6 of 7 — Notifications (ntfy)[/]").RuleStyle("blue"));
|
||||
AnsiConsole.MarkupLine("[grey]HanaToolbox sends alerts via ntfy.sh (or a self-hosted ntfy server).[/]");
|
||||
AnsiConsole.MarkupLine("[grey]Leave the token empty to disable notifications.[/]");
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
config.Ntfy.Url = AnsiConsole.Prompt(
|
||||
new TextPrompt<string>("ntfy server URL (topic included):")
|
||||
.DefaultValue(config.Ntfy.Url));
|
||||
|
||||
config.Ntfy.Token = AnsiConsole.Prompt(
|
||||
new TextPrompt<string>("ntfy access token (empty = no auth):")
|
||||
.Secret()
|
||||
.AllowEmpty()
|
||||
.DefaultValue(string.IsNullOrWhiteSpace(config.Ntfy.Token) ? string.Empty : "(existing)"));
|
||||
|
||||
// If user accepted "(existing)" prompt without typing, keep the real token
|
||||
if (config.Ntfy.Token == "(existing)")
|
||||
config.Ntfy.Token = ConfigService.Exists() ? ConfigService.Load().Ntfy.Token : string.Empty;
|
||||
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
// Step 7: Save
|
||||
AnsiConsole.Write(new Rule("[blue]Step 7 of 7 — Finalize[/]").RuleStyle("blue"));
|
||||
|
||||
var crontabLine = $"* * * * * root /usr/local/bin/hanatoolbox cron";
|
||||
AnsiConsole.MarkupLine("[grey]Add the following line to your system crontab ([cyan]/etc/crontab[/] or [cyan]/etc/cron.d/hanatoolbox[/]):[/]");
|
||||
AnsiConsole.MarkupLine($"[cyan]{crontabLine}[/]");
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
if (!AnsiConsole.Confirm("Save configuration to /etc/hanatoolbox/hanatoolbox.json?", defaultValue: true))
|
||||
{
|
||||
AnsiConsole.MarkupLine("[yellow]Aborted. No changes saved.[/]");
|
||||
return;
|
||||
}
|
||||
|
||||
ConfigService.Save(config);
|
||||
AnsiConsole.MarkupLine("[green]✅ Configuration saved successfully![/]");
|
||||
AnsiConsole.MarkupLine($"[grey]Config file: /etc/hanatoolbox/hanatoolbox.json[/]");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user