using DodoSSH.Client.Domain; namespace DodoSSH.Client.Import; /// /// One host an ssh_config describes, resolved and ready to be looked at. /// /// /// Deliberately not a . This is a candidate somebody has not agreed to import yet, /// and it carries things a stored host has no field for — the identity file's path, the jump alias by name, /// and the warnings that go beside a row in the preview. /// /// /// The name from the Host line, which is what the user types after ssh and so the name they /// will recognise. /// /// /// What HostName said, or the alias when it said nothing — which is OpenSSH's own default and the /// reason Host db.internal with no other directive works. /// /// What User said, if anything. /// What Port said, defaulting to 22. /// Every IdentityFile path, in the order they were given. /// The ProxyJump value verbatim, if any. /// Everything else, as SSH directives. /// What could not be represented, per host. public sealed record ImportedHost( string Alias, string Hostname, string? Username, int Port, IReadOnlyList IdentityFiles, string? ProxyJump, HostOptions Options, IReadOnlyList Warnings) { /// The address this would dial, for a preview row. public string Address => Username is { Length: > 0 } user ? $"{user}@{Hostname}:{Port}" : $"{Hostname}:{Port}"; /// Turns this into the host that would be stored. /// /// /// The identity file becomes a note and a directive, not a key. Reading somebody's /// ~/.ssh/id_ed25519 into a keychain is exactly the act this product exists to make deliberate, /// and doing it as a side effect of "import my config" is the wrong default. The path is recorded so it /// is not lost; importing the material is a separate, per-row choice. /// /// /// ProxyJump records intent and changes nothing about connecting. The SSH layer has no jump /// hosts — ISshConnection offers OpenShellAsync and nothing else, and /// SshConnectionRequest has no route field. So it is kept as a directive and a note, and the /// preview says so; a bastion topology that imported and quietly did not route would be worse than one /// that was not imported. /// /// public HostSecret ToSecret() { var options = new List(Options); var notes = new List(); if (IdentityFiles.Count > 0) { options.Add(new HostOption("IdentityFile", IdentityFiles[0])); notes.Add(IdentityFiles.Count == 1 ? $"ssh_config used the key at {IdentityFiles[0]}." : $"ssh_config listed {IdentityFiles.Count} keys, the first being {IdentityFiles[0]}."); } if (ProxyJump is { Length: > 0 } jump) { options.Add(new HostOption("ProxyJump", jump)); notes.Add($"ssh_config reached this through {jump}. DodoSSH does not route through a jump host yet."); } return new HostSecret { Label = Alias, Hostname = Hostname, Port = Port, Username = Username, Notes = notes.Count == 0 ? null : string.Join(" ", notes), Options = HostOptions.Create(options), }; } } /// /// Everything an ssh_config yielded: the hosts it can offer, and what it could not. /// /// The importable candidates, in file order. /// /// Host patterns that are patterns rather than names. They contribute defaults and are not /// importable: a bookmark called *.internal is one nothing can dial. /// /// Document-level notes, including the parser's own. public sealed record SshConfigImport( IReadOnlyList Hosts, IReadOnlyList SkippedPatterns, IReadOnlyList Warnings);