Skip to main content

SshConfigFile

Struct SshConfigFile 

Source
pub struct SshConfigFile {
    pub elements: Vec<ConfigElement>,
    pub path: PathBuf,
    pub crlf: bool,
    pub bom: bool,
}
Expand description

Represents the entire SSH config file as a sequence of elements. Preserves the original structure for round-trip fidelity.

Fields§

§elements: Vec<ConfigElement>§path: PathBuf§crlf: bool

Whether the original file used CRLF line endings.

§bom: bool

Whether the original file started with a UTF-8 BOM.

Implementations§

Source§

impl SshConfigFile

Source

pub fn host_entries(&self) -> Vec<HostEntry>

Get all host entries as convenience views (including from Include files). Pattern-inherited directives (ProxyJump, User, IdentityFile) are merged using SSH-faithful alias-only matching so indicators like ↗ reflect what SSH will actually apply when connecting via ssh <alias>.

Source

pub fn raw_host_entry(&self, alias: &str) -> Option<HostEntry>

Get a single host entry by alias without pattern inheritance applied. Returns the raw directives from the host’s own block only. Used by the edit form so inherited values can be shown as dimmed placeholders.

Source

pub fn inherited_hints(&self, alias: &str) -> InheritedHints

Compute pattern-provided field hints for a host alias. Returns first-match values and their source patterns for ProxyJump, User and IdentityFile. These are returned regardless of whether the host has its own values for those fields. The caller (form rendering) decides visibility based on whether the field is empty. Matches by alias only (SSH-faithful).

Source

pub fn pattern_entries(&self) -> Vec<PatternEntry>

Get all pattern entries as convenience views (including from Include files).

Source

pub fn matching_patterns(&self, alias: &str) -> Vec<PatternEntry>

Find all pattern blocks that match a given host alias and hostname. Returns entries in config order (first match first).

Source

pub fn include_paths(&self) -> Vec<PathBuf>

Collect all resolved Include file paths (recursively).

Source

pub fn include_glob_dirs(&self) -> Vec<PathBuf>

Collect parent directories of Include glob patterns. When a file is added/removed under a glob dir, the directory’s mtime changes.

Source

pub fn has_host(&self, alias: &str) -> bool

Check if a host alias already exists (including in Include files). Walks the element tree directly without building HostEntry structs.

Source

pub fn siblings_of(&self, alias: &str) -> Vec<String>

Return the sibling aliases that share a Host block with alias.

An empty vector means alias lives in its own single-alias block (or is not present). A non-empty vector lists the other tokens in the block in source order, so the UI can render indicators like +N or spell the aliases out in a confirm dialog before a destructive action. Does not recurse into Included files: those are read-only and their hosts cannot be edited from purple anyway.

Source

pub fn has_host_block(&self, pattern: &str) -> bool

Check if a host block with exactly this host_pattern exists (top-level only). Unlike has_host which splits multi-host patterns and checks individual parts, this matches the full Host line pattern string (e.g. “web-* db-*”). Does not search Include files (patterns from includes are read-only).

Source

pub fn is_included_host(&self, alias: &str) -> bool

Check if a host alias is from an included file (read-only). Handles multi-pattern Host lines by splitting on whitespace.

Source

pub fn add_host(&mut self, entry: &HostEntry)

Add a new host entry to the config. Inserts before any trailing wildcard/pattern Host blocks (e.g. Host *) so that SSH “first match wins” semantics are preserved. If wildcards are only at the top of the file (acting as global defaults), appends at end.

Source

pub fn last_element_has_trailing_blank(&self) -> bool

Check if the last element already ends with a blank line.

Source

pub fn update_host(&mut self, old_alias: &str, entry: &HostEntry)

Update an existing host entry by alias. Merges changes into the existing block, preserving unknown directives.

Alias matching uses whitespace-tokenized equality, so a host visible under a multi-alias block like Host web-01 web-01.prod is reachable from any of its aliases. Directives are shared across all tokens in the block (per SSH semantics): updating User on web-01.prod therefore also affects web-01.

On rename of a multi-alias block only the matching token is replaced in the Host line; sibling aliases are preserved verbatim.

Source

pub fn set_host_provider_id( &mut self, alias: &str, id: &ProviderConfigId, server_id: &str, ) -> bool

Set provider on a host block by alias using a full ProviderConfigId. Emits a 3-segment marker when the id has a label, 2-segment otherwise.

Refuses pattern aliases and multi-alias blocks: claiming a sibling alias as provider-owned cascades into stale-marking and bulk-purge, which would silently delete the user’s hand-curated entries.

Source

pub fn rewrite_legacy_markers_to_label( &mut self, provider_name: &str, label: &str, ) -> usize

Rewrite every 2-segment legacy marker for provider_name to a 3-segment marker keyed to (provider_name, label). Used by the lazy-migration flow so existing hosts of a now-labeled config stay owned (and don’t get re-claimed or stale-marked) on the next sync.

Only top-level host blocks are rewritten; Include files are read-only per the project’s invariant. Returns the count of host blocks touched.

Source

pub fn find_hosts_by_provider( &self, provider_name: &str, ) -> Vec<(String, String)>

Find all hosts with a specific provider, returning (alias, server_id) pairs. Searches both top-level elements and Include files so that provider hosts in included configs are recognized during sync (prevents duplicate additions).

Source

pub fn find_hosts_by_id(&self, id: &ProviderConfigId) -> Vec<(String, String)>

Find hosts owned by an exact ProviderConfigId. Used during multi-config sync so two labeled configs of the same provider don’t claim each other’s hosts. Legacy 2-segment markers match a bare id (label=None) for backward compatibility.

Source

pub fn find_hosts_by_provider_raw( &self, provider_name: &str, ) -> Vec<(String, String)>

Like find_hosts_by_provider, but returns the FULL server_id from the raw marker (everything after the first colon), without trying to interpret the middle segment as a label. Used by sync of BARE configs so server_ids containing colons (Proxmox qemu:300) are matched against the API response one-to-one instead of being mis-parsed as labeled markers.

Source

pub fn add_forward(&mut self, alias: &str, directive_key: &str, value: &str)

Add a forwarding directive to a host block. Inserts at content_end() (before trailing blanks), using detected indentation. Uses split_whitespace matching for multi-pattern Host lines.

Source

pub fn remove_forward( &mut self, alias: &str, directive_key: &str, value: &str, ) -> bool

Remove a specific forwarding directive from a host block. Matches key (case-insensitive) and value (whitespace-normalized). Uses split_whitespace matching for multi-pattern Host lines. Returns true if a directive was actually removed.

Source

pub fn has_forward(&self, alias: &str, directive_key: &str, value: &str) -> bool

Check if a host block has a specific forwarding directive. Uses whitespace-normalized value comparison and split_whitespace host matching.

Source

pub fn find_tunnel_directives(&self, alias: &str) -> Vec<TunnelRule>

Find tunnel directives for a host alias, searching all elements including Include files. Uses split_whitespace matching like has_host() for multi-pattern Host lines.

Source

pub fn deduplicate_alias(&self, base: &str) -> String

Generate a unique alias by appending -2, -3, etc. if the base alias is taken.

Source

pub fn deduplicate_alias_excluding( &self, base: &str, exclude: Option<&str>, ) -> String

Generate a unique alias, optionally excluding one alias from collision detection. Used during rename so the host being renamed doesn’t collide with itself.

Source

pub fn set_host_tags(&mut self, alias: &str, tags: &[String]) -> bool

Set tags on a host block by alias.

Refuses pattern aliases and multi-alias blocks symmetric with the vault/certificate setters: a tag on a shared block silently applies to every sibling alias, which is rarely the user’s intent.

Source

pub fn set_host_provider_tags(&mut self, alias: &str, tags: &[String]) -> bool

Set provider-synced tags on a host block by alias.

Same multi-alias and pattern refusal as the other purple-marker setters. Provider tags drive sync decisions, so a wrong-block mutation can cascade into delete/stale.

Source

pub fn set_host_askpass(&mut self, alias: &str, source: &str) -> bool

Set askpass source on a host block by alias.

Askpass is an authentication credential source; applying it to a sibling alias in a shared block would route the wrong credential.

Source

pub fn set_host_vault_ssh(&mut self, alias: &str, role: &str) -> bool

Set or remove the Vault SSH role comment on a host block by alias. Empty role removes the comment.

Mirrors the safety invariants of set_host_certificate_file and set_host_vault_addr: wildcard aliases are refused so a Host *.prod pattern can never have a Vault role silently assigned to every host it resolves, and multi-alias blocks (Host web-01 web-01.prod) are refused so the role is never applied to sibling aliases the user did not authorise. Returns true on a successful mutation, false when the alias is invalid, missing, or lives in an Include file.

Callers that run asynchronously (form submit handlers, sync workers) MUST check the return value so a silent config mutation failure is surfaced instead of pretending the role was wired up.

Source

pub fn set_host_vault_addr(&mut self, alias: &str, url: &str) -> bool

Set or remove the Vault SSH endpoint comment on a host block by alias. Empty url removes the comment.

Mirrors the safety invariants of set_host_certificate_file: wildcard aliases are refused to avoid accidentally applying a vault address to every host resolved through a pattern, and Match blocks are not touched (they live as inert GlobalLines). Returns true on a successful mutation, false when the alias is invalid or the block is not found.

Callers that run asynchronously (e.g. form submit handlers that resolve the alias before writing) MUST check the return value so a silent config mutation failure is surfaced instead of pretending the vault address was wired up.

Source

pub fn set_host_certificate_file(&mut self, alias: &str, path: &str) -> bool

Set or remove the CertificateFile directive on a host block by alias. Empty path removes the directive. Set the CertificateFile directive on the host block that matches alias exactly. Returns true if a matching block was found and updated, false if no top-level HostBlock matched (alias was renamed, deleted or lives only inside an Included file).

Only touches CertificateFile directives that are purple-managed (path contains .purple/certs/). User-set custom CertificateFile entries (e.g. a corporate or personal cert at ~/.ssh/corp-cert.pub) are never modified or removed: empty-path clears only the purple managed line; non-empty path updates the purple-managed line in place or inserts a new one if absent. A host with both a corporate cert and a Vault-signed cert ends up with both lines present, in OpenSSH’s documented cumulative semantics.

Callers that run asynchronously (e.g. the Vault SSH bulk-sign worker) MUST check the return value so a silent config mutation failure is surfaced to the user instead of pretending the cert was wired up.

Source

pub fn set_host_meta(&mut self, alias: &str, meta: &[(String, String)]) -> bool

Set provider metadata on a host block by alias.

Refuses pattern aliases and multi-alias blocks; same rationale as the other # purple:* setters.

Source

pub fn set_host_stale(&mut self, alias: &str, timestamp: u64) -> bool

Mark a host as stale by alias.

Stale markers drive the X purge flow which deletes the full block, so a wrong-block mutation here cascades into data loss for a sibling alias the user added by hand. Refuse pattern and multi-alias blocks.

Source

pub fn clear_host_stale(&mut self, alias: &str) -> bool

Clear stale marking from a host by alias.

Symmetric guard with set_host_stale. Clearing on a shared block is benign but the asymmetry would be confusing; reject for consistency.

Source

pub fn stale_hosts(&self) -> Vec<(String, u64)>

Collect all stale hosts with their timestamps.

Source

pub fn delete_host(&mut self, alias: &str)

Delete a host entry by alias.

For a single-alias block this removes the whole block (and cleans up any orphaned # purple:group header left behind). For a multi-alias block like Host web-01 web-01.prod 10.0.1.5 only the matching alias token is stripped from the Host line; sibling aliases and all directives are preserved so that delete_host("web-01.prod") does not silently wipe configuration for web-01 and 10.0.1.5.

Callers that want to remove the entire block regardless of sibling aliases should surface an explicit confirmation in the UI and then delete each sibling alias in turn.

Source

pub fn delete_host_undoable( &mut self, alias: &str, ) -> Option<(ConfigElement, usize)>

Delete a host and return the removed element and its position for undo. Does NOT collapse blank lines or remove group headers so the position stays valid for re-insertion via insert_host_at(). Orphaned group headers (if any) are cleaned up at next startup.

For multi-alias blocks this returns None: undoable-delete of a single alias out of a shared Host line cannot be round-tripped via insert_host_at because sibling aliases would be lost. Callers should fall back to delete_host in that case (which strips only the requested token).

Source

pub fn insert_host_at(&mut self, element: ConfigElement, position: usize)

Insert a host block at a specific position (for undo).

Source

pub fn find_provider_insert_position( &self, provider_name: &str, ) -> Option<usize>

Find the position after the last HostBlock that belongs to a provider. Returns None if no hosts for this provider exist in the config. Used by the sync engine to insert new hosts adjacent to existing provider hosts.

Source

pub fn swap_hosts(&mut self, alias_a: &str, alias_b: &str) -> bool

Swap two host blocks in the config by alias. Returns true if swap was performed.

Source§

impl SshConfigFile

Source

pub fn parse(path: &Path) -> Result<Self>

Parse an SSH config file from the given path. Preserves all formatting, comments, and unknown directives for round-trip fidelity.

Source

pub fn from_content(content: &str, synthetic_path: PathBuf) -> Self

Create an SshConfigFile from raw content string (for demo/test use). Uses a synthetic path; the file is never read from or written to disk.

Source

pub fn parse_content(content: &str) -> Vec<ConfigElement>

Parse SSH config content from a string (without Include resolution). Used by tests to create SshConfigFile from inline strings.

Source§

impl SshConfigFile

Source

pub fn remove_all_orphaned_group_headers(&mut self) -> usize

Remove all # purple:group <DisplayName> GlobalLines that point at a provider with no remaining Host blocks. Returns the count removed.

Source

pub fn repair_absorbed_group_comments(&mut self) -> usize

Repair configs where # purple:group comments were absorbed into the preceding host block’s directives instead of being stored as GlobalLines. Returns the number of blocks that were repaired.

Only relocates lines whose suffix matches a known provider’s display name. A user-authored comment like # purple:group canary notes is left in place: the suffix canary notes is not a provider name, so the comment is treated as free-form prose rather than a misparsed group header. This protects hand-written comments from being silently scrubbed by the next-startup remove_all_orphaned_group_headers pass.

Source§

impl SshConfigFile

Source

pub fn write(&self) -> Result<()>

Write the config back to disk. Creates a backup before writing and uses atomic write (temp file + rename). Resolves symlinks so the rename targets the real file, not the link. Acquires an advisory lock to prevent concurrent writes from multiple purple processes or background sync threads.

Source

pub fn serialize(&self) -> String

Serialize the config to a string. Collapses consecutive blank lines to prevent accumulation after deletions.

Trait Implementations§

Source§

impl Clone for SshConfigFile

Source§

fn clone(&self) -> SshConfigFile

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for SshConfigFile

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V