Skip to content

cli: redesign proposal - #3375

Open
kradalby wants to merge 6 commits into
juanfont:mainfrom
kradalby:cli-restructure-proposal
Open

cli: redesign proposal #3375
kradalby wants to merge 6 commits into
juanfont:mainfrom
kradalby:cli-restructure-proposal

Conversation

@kradalby

@kradalby kradalby commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

This pr is not to be merged, its end result is to serve as a "design doc" for how we organise our cli. Breaking changes allowed.

Comments, suggestions and proposals from humans in the community encouraged.

@kradalby
kradalby force-pushed the cli-restructure-proposal branch from 5ceb01c to ddaa04f Compare July 9, 2026 15:52
@kradalby kradalby changed the title wip cli: redesign proposal Jul 9, 2026
Comment thread docs/proposals/001-cli.md Outdated
headscale nodes disable-expiry 12
```

An alternative shape is a top-level `expire` group instead of `nodes set-expiry`. We want reviewer input on which to pick:

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I currently like this, where we split out the very much used node ones, but expiry is a bit weird because it also exist in different scopes.

Maybe only routes and tags?

@kradalby
kradalby marked this pull request as ready for review July 10, 2026 14:08
@nblock

nblock commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

auth is the authentication of nodes to headscale

Authentication of nodes and also SSH check.

headscale routes approve 12 "" # remove all from 12
headscale routes disapprove-all 12 # remove all from 12

An approve subcommand with an empty last target argument is confusing to some users. Because its unclear whether the user forgot the last argument or all routes should really be disapproved. We also have this now when using -r "". It came up a few times.

Maybe add a pair of subcommands to solve this in general? approve / disapprove or approve / no-approve and always require the last argument (in this case the route(s)).

headscale users set # optional fields are flags

I like the "optional fields as flags". Its flexible and can be extended as needed.

We will support outputting json, json-line and a human readable table. Default output mode is always the human
readable table. json formats will always show all data.

Do we even need json-line? If not, we could only provide json as universal machine readable output format and combine it with jsonpath somehow? Otherwise we'd have four formats:

  • text (mangled by --column and --filter)
  • json, json-line (mangled by --filter)
  • json-path (arbitary data in json format)

expire vs. nodes expire

I like the "nodes expire" form better. Adding one more word to the argument list does not really matter but it imho
improves readability and discoverability:

  • Which operations can I perform for a node/user/…: headscale nodes --help
  • The target - in this case node needs to be supplied anyways. If there are more possible target groups, we'd need the distinction anyways. An example would be nodes and apikeys for expire: headscale expire node <...> / headscale expire apikeys <...>.

Other questions:

  • Are global arguments kept as is? --config comes to mind.
  • Are environment variables still supported as-is (HEADSCALE_CONFIG, …)

@nblock

nblock commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

How should we do the formatting of the text output? Some columns are single valued (node name, user name) and some are lists (ip addresses, routes)? Should we keep it fixed (without stability guarantees) and direct users to jsonpath for script processing?

The text version should favor human readability and allow for some flexibility for upcoming changes (add, remove, reorder default columns). While its nice to use grep and friends to work with the CLI, there's json and jsonpath which is better suited for scripting.

The "one line per list entry" output is good for variable long lists such as routes. For the node's IP addresses its a bit hard to read as its almost always two lines which produces alternating rows and make it visually harder to read.

@ArcticLampyrid

Copy link
Copy Markdown
Contributor

Do we even need json-line? If not, we could only provide json as universal machine readable output format and combine it with jsonpath somehow?

Perhaps we could omit the jsonpath option too. Providing only table and json formats is sufficient. When users require specific data extraction, they can handle it in the pipeline using tools such as jq. In modern IT operations, tools like jq are virtually always pre-installed.

@kradalby
kradalby requested a review from juanfont as a code owner July 31, 2026 08:08
kradalby and others added 5 commits July 31, 2026 08:31
Route/tag verbs become set; drop json-line; keep expire/routes/tags
under nodes; document global flags and env behaviour.
Replace the stub structure with every leaf command; keys use revoke,
nodes use expire. api examples gain key CRUD and oauth2 naming.
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
Drop jsonpath for jq, client-side filter only, note preauth prefix
exists, backfill server cleanup, --list-columns, tags/users list
mockups. Abandoned text struck through, not deleted.
@kradalby
kradalby force-pushed the cli-restructure-proposal branch from 15a9b72 to 24ebbb5 Compare July 31, 2026 08:31
COUNT + NODES columns, terminal-width truncation, no --expand.
Full data via --output json | jq, encouraged in every command's help.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants