by networked | 2025-04-28 21:18:39+00:00
> but [TOML] lacks the kind of functionality that the SSH config has to both declare a new block, define its internal name, and specify a config glob, all with the same string.

This isn't true. TOML allows table names with periods and asterisks. It also supports top-level keys. This is what the TOML may look like for your SSH config:

include = "~/.ssh/my-org/*" # We write `"*.co.uk"` rather than `host."*.co.uk"`. # `Host` in ssh_config(5) introduces sections, # and TOML has table headers for sections. # A more complete design would have something for `Match`. ["*.co.uk"] proxy-command = "ssh bastion@my-uk-server.co.uk nc %h %p 2> /dev/null" [newServer] hostname = "newServer.url" user = "adminuser" port = 2222 identity-file = "~/.ssh/id_rsa.key" ["anotherServer.tld"] hostname = "anotherServer.url" user = "mary" port = 2222

(Personally, I am not a fan of indenting TOML.)

> And you can't change how or when includes are loaded or what they overload without adding some kind of "priority" key-value, and then having to read each entry, do some math, change all the values to load different things at different places. (and looking back, I actually screwed up the TOML config, because it was so confusing!)

Right, your TOML is invalid. TOML requires you to quote strings. You might have it mixed up with another INI-derived format.

It could be because TOML is inherently more confusing than ssh_config(5), but I doubt it is actually more confusing to a newcomer. For example, a newcomer might think that indentation in SSH config is semantic when it isn't. I know because I once made this mistake. A newcomer must also remember that `Host` and `Match` are special and introduce new sections despite looking like other declarations. What is more likely is that you learned SSH config by studying the man page or reading a book like SSH Mastery and forgot the effort it took, and now you have "the curse of knowledge" about it (https://en.wikipedia.org/wiki/Curse_of_knowledge), but you haven't studied the TOML spec the same way.

A numeric "priority" key would indeed make for a pretty miserable user experience. Don't implement one if you can help it. There are different, better ways to express how an include should only affect certain keys.

The way I would do it in a TOML-based SSH config file is probably with dotted keys for tables. (I wouldn't necessarily choose TOML for this task, but TOML is your example.) For instance:

[my-org] include = "~/.ssh/my-org/*" [my-org."*.example.com"] # Host inherits from `my-org`. ["*.example.net"] # Host doesn't inherit from `my-org`.

> Don't choose a generic solution if it's going to give the user a pain in the ass.

I agree, with a caveat. The caveat is that you probably overestimate how unique your configuration needs are and underestimate the value of picking something standard. Using a standard format means you tap into its network effects. The user gets "free" syntax highlighting and completion in their editor, automatic formatting, and tools like https://github.com/kislyuk/yq to query and modify config files. This is also an important part of user experience. If the format is custom, you will have to implement syntax highlighting for Vim, Emacs, VS Code, etc., and the user will have to install it.

I think the right approach is to sort your concerns into the essential and the inessential, then choose a format based on the essential concerns. (Do you need includes to only apply to subsequent lines, or could you apply them by name or by nesting?) When you choose, prioritize standard formats. And if your needs are complex enough, consider embedding an interpreter like Lua or Starlark for configuration, or have the user write code in non-embedded Python or another language to generate JSON config that your software reads.