using System.Diagnostics; using System.Runtime.InteropServices; using System.Text.Json; namespace EpsonPrintService; public record UsbKeyResult(bool Found, string? Base64Content, string? DecodedApiUrl, string? DecodedRestaurantId, string? Error); public static class UsbKeyScanner { private const string KeyFileName = "print_server_key.txt"; public static async Task ScanForKeyAsync() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return new UsbKeyResult(false, null, null, null, "USB scanning is only supported on Linux."); try { var lsblkJson = await RunCommandAsync("lsblk", "-J -o NAME,RM,MOUNTPOINT,TYPE"); if (lsblkJson == null) return new UsbKeyResult(false, null, null, null, "Failed to run lsblk."); var doc = JsonDocument.Parse(lsblkJson); var devices = doc.RootElement.GetProperty("blockdevices"); foreach (var device in devices.EnumerateArray()) { var result = await ScanDeviceAsync(device); if (result != null) return result; // Check children (partitions) if (device.TryGetProperty("children", out var children)) { foreach (var child in children.EnumerateArray()) { result = await ScanDeviceAsync(child); if (result != null) return result; } } } return new UsbKeyResult(false, null, null, null, null); } catch (Exception ex) { return new UsbKeyResult(false, null, null, null, $"USB scan failed: {ex.Message}"); } } private static async Task ScanDeviceAsync(JsonElement device) { var isRemovable = device.TryGetProperty("rm", out var rm) && (rm.ValueKind == JsonValueKind.True || (rm.ValueKind == JsonValueKind.Number && rm.GetInt32() == 1)); if (!isRemovable) return null; var type = device.TryGetProperty("type", out var t) ? t.GetString() : null; if (type != "part") return null; var name = device.TryGetProperty("name", out var n) ? n.GetString() : null; if (name == null) return null; var mountpoint = device.TryGetProperty("mountpoint", out var mp) && mp.ValueKind == JsonValueKind.String ? mp.GetString() : null; if (!string.IsNullOrEmpty(mountpoint)) { return CheckMountpoint(mountpoint); } // Try to mount and check var tempMount = $"/tmp/usb_scan_{name}"; try { Directory.CreateDirectory(tempMount); var mountResult = await RunCommandAsync("mount", $"-o ro /dev/{name} {tempMount}"); if (mountResult == null) return null; return CheckMountpoint(tempMount); } finally { try { await RunCommandAsync("umount", tempMount); } catch { } try { Directory.Delete(tempMount); } catch { } } } private static UsbKeyResult? CheckMountpoint(string mountpoint) { var keyPath = Path.Combine(mountpoint, KeyFileName); if (!File.Exists(keyPath)) return null; var content = File.ReadAllText(keyPath).Trim(); if (string.IsNullOrWhiteSpace(content)) return null; var validation = ConfigurationLoader.TryLoadFromBase64(content); if (!validation.Success) return new UsbKeyResult(true, content, null, null, $"Key file found but invalid: {validation.Error}"); return new UsbKeyResult(true, content, validation.Config!.ApiUrl, validation.Config.RestaurantId, null); } private static async Task RunCommandAsync(string command, string arguments) { try { using var process = new Process { StartInfo = new ProcessStartInfo { FileName = command, Arguments = arguments, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true } }; process.Start(); var output = await process.StandardOutput.ReadToEndAsync(); await process.WaitForExitAsync(); return process.ExitCode == 0 ? output : null; } catch { return null; } } }